01tonythomas has uploaded a new change for review. https://gerrit.wikimedia.org/r/304692
Change subject: Introduce ContentHandler on the Newsletter CustomEditpage ...................................................................... Introduce ContentHandler on the Newsletter CustomEditpage * Added in the contenthandler model for Newsletter namespace * action=edit, now creates a Newsletter with the Newsletter Contentmodel Bug: T138462 Change-Id: I89809a4ec1b524148199ff5d11ee4e96ae716919 --- M Newsletter.hooks.php M extension.json M includes/Newsletter.php M includes/NewsletterDb.php M includes/NewsletterEditPage.php M includes/NewsletterStore.php A includes/content/NewsletterContent.php A includes/content/NewsletterContentHandler.php 8 files changed, 605 insertions(+), 3 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Newsletter refs/changes/92/304692/1 diff --git a/Newsletter.hooks.php b/Newsletter.hooks.php index 0f4f747..0076798 100755 --- a/Newsletter.hooks.php +++ b/Newsletter.hooks.php @@ -129,6 +129,31 @@ return true; } + /** + * @param EditPage $editPage + */ + public static function onAlternateEdit( EditPage $editPage ) { + global $wgOut; + $title = $editPage->getTitle(); + + if ( $title->inNamespace( NS_NEWSLETTER ) ) { + if ( $title->hasContentModel( 'NewsletterContent' ) ) { + $newsletter = Newsletter::newFromName( $title->getText() ); + if ( $newsletter ) { + $title = SpecialPage::getTitleFor( 'Newsletter', $newsletter->getId() . '/' . + 'manage' ); + $wgOut->redirect( $title->getFullURL() ); + } + } + } + } + + /** + * @param Article $article + * @param User $user + * @return bool + * @throws ReadOnlyError + */ public static function onCustomEditor( Article $article, User $user ) { if ( !$article->getTitle()->inNamespace( NS_NEWSLETTER ) ) { return true; @@ -136,7 +161,6 @@ $editPage = new NewsletterEditPage( $article->getContext() ); $editPage->edit(); - return false; } } diff --git a/extension.json b/extension.json index 775f2ee..383e95d 100644 --- a/extension.json +++ b/extension.json @@ -63,6 +63,9 @@ "ExtensionMessagesFiles": { "NewsletterAlias": "Newsletter.alias.php" }, + "ContentHandlers": { + "NewsletterContent": "NewsletterContentHandler" + }, "AutoloadClasses": { "Newsletter": "includes/Newsletter.php", "NewsletterDb": "includes/NewsletterDb.php", @@ -78,6 +81,8 @@ "NewsletterTablePager": "includes/specials/pagers/NewsletterTablePager.php", "ApiNewsletterManage": "includes/api/ApiNewsletterManage.php", "ApiNewsletterSubscribe": "includes/api/ApiNewsletterSubscribe.php", + "NewsletterContent": "includes/content/NewsletterContent.php", + "NewsletterContentHandler": "includes/content/NewsletterContentHandler.php", "EchoNewsletterUserLocator": "includes/Echo/EchoNewsletterUserLocator.php", "EchoNewsletterFormatter": "includes/Echo/EchoNewsletterFormatter.php", "BaseNewsletterPresentationModel": "includes/Echo/BaseNewsletterPresentationModel.php", @@ -145,6 +150,9 @@ "UserMergeAccountFields": [ "NewsletterHooks::onUserMergeAccountFields" ], + "AlternateEdit": [ + "NewsletterHooks::onAlternateEdit" + ], "CustomEditor": [ "NewsletterHooks::onCustomEditor" ] @@ -154,7 +162,8 @@ "id": 5500, "constant": "NS_NEWSLETTER", "name": "Newsletter", - "protection": "newsletter-create" + "protection": "newsletter-create", + "defaultcontentmodel": "NewsletterContent" }, { "id": 5501, diff --git a/includes/Newsletter.php b/includes/Newsletter.php index 26dbd74..a630511 100644 --- a/includes/Newsletter.php +++ b/includes/Newsletter.php @@ -62,6 +62,16 @@ } /** + * Fetch a new newsletter instance from given name + * + * @param string $name + * @return Newsletter|null + */ + public static function newFromName( $name ) { + return NewsletterStore::getDefaultInstance()->getNewsletterFromName( $name ); + } + + /** * @return int|null */ public function getId() { diff --git a/includes/NewsletterDb.php b/includes/NewsletterDb.php index cac547a..404bcf7 100644 --- a/includes/NewsletterDb.php +++ b/includes/NewsletterDb.php @@ -263,6 +263,30 @@ return $this->getNewsletterFromRow( $res->current() ); } + /** + * Fetch the newsletter matching the given name from the DB + * + * @param string $name + * @return Newsletter|null + */ + public function getNewsletterFromName( $name ) { + Assert::parameterType( 'string', $name, '$name' ); + + $dbr = $this->lb->getConnection( DB_SLAVE ); + $res = $dbr->select( + 'nl_newsletters', + array( 'nl_id', 'nl_name', 'nl_desc', 'nl_main_page_id' ), + array( 'nl_name' => $name, 'nl_active' => 1 ), + __METHOD__ + ); + $this->lb->reuseConnection( $dbr ); + + if ( $res->numRows() === 0 ) { + return null; + } + + return $this->getNewsletterFromRow( $res->current() ); + } /** * @param int $id diff --git a/includes/NewsletterEditPage.php b/includes/NewsletterEditPage.php index 43f196c..4077130 100644 --- a/includes/NewsletterEditPage.php +++ b/includes/NewsletterEditPage.php @@ -163,8 +163,18 @@ $mainPageId ); $newsletterCreated = $store->addNewsletter( $this->newsletter ); + $title = Title::makeTitleSafe( NS_NEWSLETTER, trim( $data['Name'] ) ); + $editSummaryMsg = $this->context->msg( 'newsletter-create-editsummary' ); + $result = NewsletterContentHandler::edit( + $title, + $data['Description'], + $input['mainpage'], + $this->user->getName(), + $editSummaryMsg->inContentLanguage()->plain(), + $this->context + ); - if ( $newsletterCreated ) { + if ( $newsletterCreated && $result->isGood() ) { $this->newsletter->subscribe( $this->user ); NewsletterStore::getDefaultInstance()->addPublisher( $this->newsletter, $this->user ); diff --git a/includes/NewsletterStore.php b/includes/NewsletterStore.php index 1605fb8..20e2dae 100644 --- a/includes/NewsletterStore.php +++ b/includes/NewsletterStore.php @@ -153,6 +153,13 @@ return $this->db->getNewsletter( $id ); } + /** + * @param string $name + * @return Newsletter|null + */ + public function getNewsletterFromName( $name ) { + return $this->db->getNewsletterFromName( $name ); + } /** * @param int $id diff --git a/includes/content/NewsletterContent.php b/includes/content/NewsletterContent.php new file mode 100644 index 0000000..27d5a07 --- /dev/null +++ b/includes/content/NewsletterContent.php @@ -0,0 +1,426 @@ +<?php +/** + * @license GNU GPL v2+ + * @author tonythomas + */ + +class NewsletterContent extends JsonContent { + /** Subpage actions */ + const NEWSLETTER_ANNOUNCE = 'announce'; + const NEWSLETTER_DELETE = 'delete'; + const NEWSLETTER_MANAGE = 'manage'; + const NEWSLETTER_SUBSCRIBE = 'subscribe'; + const NEWSLETTER_UNSUBSCRIBE = 'unsubscribe'; + + /** + * @var string + */ + private $description; + + /** + * @var string + */ + private $mainPage; + + /** + * @var Newsletter + */ + private $newsletter; + + /** + * @var array + */ + protected $publishers; + + /** + * Whether $description and $targets have been populated + * @var bool + */ + private $decoded = false; + + /** + * @param string $text + */ + public function __construct( $text ) { + parent::__construct( $text, 'NewsletterContent' ); + } + + /** + * @return bool + */ + public function isValid() { + $this->decode(); + + if ( !is_string( $this->description ) || !is_string( $this->mainPage ) ) { + return false; + } + + if ( !Title::newFromText( $this->mainPage ) ) { + return false; + } + + return true; + } + + /** + * Decode the JSON encoded args + */ + protected function decode() { + if ( $this->decoded ) { + return true; + } + $jsonParse = $this->getData(); + $data = $jsonParse->isGood() ? $jsonParse->getValue() : null; + if ( $data ) { + $this->description = isset( $data->description ) ? $data->description : null; + $this->mainPage = isset( $data->mainpage ) ? $data->mainpage : null; + } + + $lines = explode( "\n", $data->publishers ); + // Strip whitespace, then remove blank lines and duplicates + $lines = array_unique( array_filter( array_map( 'trim', $lines ) ) ); + + // Ask for confirmation before removing all the publishers + if ( count( $lines ) === 0 ) { + return Status::newFatal( 'newsletter-manage-no-publishers' ); + } + + $publishers = []; + /** @var User[] $newPublishers */ + foreach ( $lines as $publisherName ) { + $user = User::newFromName( $publisherName ); + if ( !$user || !$user->getId() ) { + // Input contains an invalid username + return Status::newFatal( 'newsletter-manage-invalid-publisher', $publisherName ); + } + $publishers[] = $user->getId(); + } + + $this->publishers = UserArray::newFromIDs( $publishers ); + $this->decoded = true; + + return true; + } + + public function onSuccess() { + // No-op: We have already redirected. + } + + protected function fillParserOutput( Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output ) { + if ( $generateHtml ) { + $this->newsletter = Newsletter::newFromName( $title->getText() ); + $user = $options->getUser(); + + $newsletterActionButtons = ''; + + if ( $user->isLoggedIn() ) { + // buttons are only shown for logged-in users + $newsletterActionButtons = $this->getNewsletterActionButtons( $options ); + } + + $mainTitle = Title::newFromText( $this->mainPage ); + + $fields = array( + 'name' => array( + 'type' => 'info', + 'label-message' => 'newsletter-view-name', + 'default' => $this->newsletter->getName(), + ), + 'mainpage' => array( + 'type' => 'info', + 'label-message' => 'newsletter-view-mainpage', + 'default' => Linker::link( $mainTitle ), + 'raw' => true, + ), + 'description' => array( + 'type' => 'info', + 'label-message' => 'newsletter-view-description', + 'default' => $this->description, + 'rows' => 6, + 'readonly' => true, + ), + 'publishers' => array( + 'type' => 'info', + 'label' => wfMessage( 'newsletter-view-publishers' )->inLanguage( + $options->getUserLangObj() ) + ->numParams( count( $this->publishers ) ) + ->parse(), + ), + 'subscribers' => array( + 'type' => 'info', + 'label-message' => 'newsletter-view-subscriber-count', + 'default' => $options->getUserLangObj()->formatNum( $this->newsletter->getSubscriberCount() ), + ), + ); + if ( count( $this->publishers ) > 0 ) { + // Have this here to avoid calling unneeded functions + $this->doLinkCacheQuery( $this->publishers ); + $fields['publishers']['default'] = $this->buildUserList( $this->publishers ); + $fields['publishers']['raw'] = true; + } else { + // Show a message if there are no publishers instead of nothing + $fields['publishers']['default'] = wfMessage( 'newsletter-view-no-publishers' ) + ->inLanguage( $options->getUserLangObj() ) + ->escaped(); + } + // Show the 10 most recent issues if there have been announcements + $logs = ''; + $logCount = LogEventsList::showLogExtract( + $logs, // by reference + 'newsletter', + SpecialPage::getTitleFor( 'Newsletter', $this->newsletter->getId() ), + '', + array( + 'lim' => 10, + 'showIfEmpty' => false, + 'conds' => array( 'log_action' => 'issue-added' ), + 'extraUrlParams' => array( 'subtype' => 'issue-added' ), + ) + ); + if ( $logCount !== 0 ) { + $fields['issues'] = array( + 'type' => 'info', + 'raw' => true, + 'default' => $logs, + 'label' => wfMessage( 'newsletter-view-issues-log' ) + ->inLanguage( $options->getUserLangObj() ) + ->numParams( $logCount )->escaped(), + ); + } + $form = $this->getHTMLForm( + $fields, + function() { + return false; + } // nothing to submit - the buttons on this page are just links + ); + + $form->suppressDefaultSubmit(); + $form->prepareForm(); + + if ( $options->getUser()->isLoggedIn() ) { + $output->setText( $this->getNavigationLinks( $options ) . $newsletterActionButtons . + "<br><br>" . $form->getBody() ); + } else { + $output->setText( $this->getNavigationLinks( $options ) . $form->getBody() ); + } + return $output; + } + } + + /** + * Create a common HTMLForm which can be used by specific page actions + * + * @param array $fields array of form fields + * @param callback $submit submit callback + * + * @return HTMLForm + */ + private function getHTMLForm( array $fields, /* callable */ $submit ) { + global $wgOut; + $form = HTMLForm::factory( + 'ooui', + $fields, + $wgOut->getContext() + ); + $form->setSubmitCallback( $submit ); + return $form; + } + + /** + * Build a group of buttons: Delete, Manage, Subscribe|Unsubscribe + * Buttons will be showed to the user only if they are relevant to the current user. + * + * @return string HTML for the button group + */ + protected function getNewsletterActionButtons( ParserOptions &$options ) { + global $wgOut; + + $user = $options->getUser(); + $id = $this->newsletter->getId(); + $buttons = array(); + $wgOut->enableOOUI(); + + if ( $this->newsletter->canManage( $user ) ) { + $buttons[] = new OOUI\ButtonWidget( + array( + 'label' => $wgOut->msg( 'newsletter-manage-button' )->escaped(), + 'icon' => 'settings', + 'href' => SpecialPage::getTitleFor( 'Newsletter', $id. '/' . + self::NEWSLETTER_MANAGE )->getFullURL() + + ) + ); + } + if ( $this->newsletter->isPublisher( $user ) ) { + $buttons[] = new OOUI\ButtonWidget( + array( + 'label' => $wgOut->msg( 'newsletter-announce-button' )->escaped(), + 'icon' => 'comment', + 'href' => SpecialPage::getTitleFor( 'Newsletter', $id. '/' . + self::NEWSLETTER_ANNOUNCE )->getFullURL() + ) + ); + } + if ( $this->newsletter->isSubscribed( $user ) ) { + $buttons[] = new OOUI\ButtonWidget( + array( + 'label' => $wgOut->msg( 'newsletter-unsubscribe-button' )->escaped(), + 'flags' => array( 'destructive' ), + 'href' => SpecialPage::getTitleFor( 'Newsletter', $id. '/' . + self::NEWSLETTER_UNSUBSCRIBE )->getFullURL() + + ) + ); + } else { + $buttons[] = new OOUI\ButtonWidget( + array( + 'label' => $wgOut->msg( 'newsletter-subscribe-button' )->escaped(), + 'flags' => array( 'constructive' ), + 'href' => SpecialPage::getTitleFor( 'Newsletter', $id. '/' . + self::NEWSLETTER_SUBSCRIBE )->getFullURL() + + ) + ); + } + $widget = new OOUI\ButtonGroupWidget( array( 'items' => $buttons ) ); + return $widget->toString(); + } + + /** + * Batch query to determine whether user pages and user talk pages exist + * or not and add them to LinkCache + * + * @param Iterator $users + * + * @return string + */ + private function doLinkCacheQuery( Iterator $users ) { + $batch = new LinkBatch(); + foreach ( $users as $user ) { + $batch->addObj( $user->getUserPage() ); + $batch->addObj( $user->getTalkPage() ); + } + $batch->execute(); + } + + /** + * Get a list of users with user-related links next to each username + * + * @param Iterator $users + * + * @return string + */ + private function buildUserList( Iterator $users ) { + $str = ''; + foreach ( $users as $user ) { + $str .= Html::rawElement( + 'li', + array(), + Linker::userLink( $user->getId(), $user->getName() ) . + Linker::userToolLinks( $user->getId(), $user->getName() ) + ); + } + return Html::rawElement( 'ul', array(), $str ); + } + + protected function getNavigationLinks( ParserOptions $options ) { + global $wgOut; + $listLink = Linker::linkKnown( + SpecialPage::getTitleFor( 'Newsletters' ), + wfMessage( 'backlinksubtitle', + wfMessage( 'newsletter-subtitlelinks-list' )->text() + )->escaped() + ); + + $user = $options->getUser(); + $actions = array(); + if ( $user->isLoggedIn() ) { + $actions[] = $this->newsletter->isSubscribed( $user ) ? self::NEWSLETTER_UNSUBSCRIBE : self::NEWSLETTER_SUBSCRIBE; + + if ( $this->newsletter->isPublisher( $user ) ) { + $actions[] = self::NEWSLETTER_ANNOUNCE; + } + if ( $this->newsletter->canManage( $user ) ) { + $actions[] = self::NEWSLETTER_MANAGE; + } + + $links = array(); + foreach ( $actions as $action ) { + $title = SpecialPage::getTitleFor( 'Newsletter', $this->newsletter->getId() . '/' . $action ); + // Messages used here: 'newsletter-subtitlelinks-announce', + // 'newsletter-subtitlelinks-subscribe', 'newsletter-subtitlelinks-unsubscribe' + // 'newsletter-subtitlelinks-manage' + $msg = wfMessage( 'newsletter-subtitlelinks-' . $action )->escaped(); + $links[] = Linker::linkKnown( $title, $msg ); + } + + $newsletterLinks = Linker::makeSelfLinkObj( SpecialPage::getTitleFor( 'Newsletter', $this->newsletter->getId() ), $this->getEscapedName() ) . ' ' . wfMessage( 'parentheses' )->rawParams( $options->getUserLangObj()->pipeList( $links ) )->escaped(); + } else { + $newsletterLinks = Linker::makeSelfLinkObj( SpecialPage::getTitleFor( 'Newsletter', $this->newsletter->getId() ), $this->getEscapedName() ); + } + + return $wgOut->setSubtitle( $options->getUserLangObj()->pipeList( array( $listLink, $newsletterLinks ) ) ); + } + + /** + * @param WikiPage $page + * @param ParserOutput|null $parserOutput + * @return LinksDeletionUpdate[] + */ + public function getDeletionUpdates( WikiPage $page, ParserOutput $parserOutput = null ) { + return array_merge( + parent::getDeletionUpdates( $page, $parserOutput ), + array( new NewsletterDeletionUpdate( $page->getTitle()->getText() ) ) + ); + } + + /** + * @return string + */ + public function getDescription() { + $this->decode(); + return $this->description; + } + + /** + * @return string + */ + public function getMainPage() { + $this->decode(); + return $this->mainPage; + } + + /** + * @return array + */ + public function getPublishers() { + $this->decode(); + return $this->publishers; + } + + /** + * We need the escaped newsletter name several times so + * extract the method here. + * + * @return string + */ + protected function getEscapedName() { + return htmlspecialchars( $this->newsletter->getName() ); + } + + /** + * Override TextContent::getTextForSummary + * @param int $maxLength + * @return string + */ + public function getTextForSummary( $maxLength = 250 ) { + global $wgContLang; + + $truncatedtext = $wgContLang->truncate( + preg_replace( "/[\n\r]/", ' ', $this->getDescription() ) + , max( 0, $maxLength ) + ); + + return $truncatedtext; + } +} \ No newline at end of file diff --git a/includes/content/NewsletterContentHandler.php b/includes/content/NewsletterContentHandler.php new file mode 100644 index 0000000..5acffc2 --- /dev/null +++ b/includes/content/NewsletterContentHandler.php @@ -0,0 +1,92 @@ +<?php +/** + * @license GNU GPL v2+ + * @author tonythomas + */ + +class NewsletterContentHandler extends JsonContentHandler { + /** + * @param string $modelId + */ + public function __construct( $modelId = 'NewsletterContent' ) { + parent::__construct( $modelId ); + } + + /** + * @param string $text + * @param string $format + * @return MassMessageListContent + * @throws MWContentSerializationException + */ + public function unserializeContent( $text, $format = null ) { + $this->checkFormat( $format ); + $content = new NewsletterContent( $text ); + if ( !$content->isValid() ) { + throw new MWContentSerializationException( 'The delivery list content is invalid.' ); + } + return $content; + } + + /** + * @return string + */ + protected function getContentClass() { + return 'NewsletterContent'; + } + + /** + * @return bool + */ + public function isParserCacheSupported() { + return false; + } + + /** + * @param Title $title + * @param string $description + * @param string $mainPage + * @param string $publishers + * @param string $summary + * @param IContextSource $context + * @return Status + */ + public static function edit( Title $title, $description, $mainPage, $publishers, $summary, + IContextSource $context + ) { + $jsonText = FormatJson::encode( + [ 'description' => $description, 'mainpage' => $mainPage, 'publishers' => $publishers ] + ); + if ( $jsonText === null ) { + return Status::newFatal( 'newsletter-ch-tojsonerror' ); + } + + // Ensure that a valid context is provided to the API in unit tests + $der = new DerivativeContext( $context ); + $request = new DerivativeRequest( + $context->getRequest(), + [ + 'action' => 'edit', + 'title' => $title->getFullText(), + 'contentmodel' => 'NewsletterContent', + 'text' => $jsonText, + 'summary' => $summary, + 'token' => $context->getUser()->getEditToken(), + ], + true // Treat data as POSTed + ); + $der->setRequest( $request ); + + try { + $api = new ApiMain( $der, true ); + $api->execute(); + } catch ( UsageException $e ) { + return Status::newFatal( $context->msg( 'newsletter-ch-apierror', + $e->getCodeString() ) ); + } + return Status::newGood(); + } + + protected function getDiffEngineClass() { + return 'NewsletterDiffEngine'; + } +} -- To view, visit https://gerrit.wikimedia.org/r/304692 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I89809a4ec1b524148199ff5d11ee4e96ae716919 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Newsletter Gerrit-Branch: master Gerrit-Owner: 01tonythomas <01tonytho...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits