Mwalker has uploaded a new change for review. https://gerrit.wikimedia.org/r/57090
Change subject: WIP: Rewriting the CN UI ...................................................................... WIP: Rewriting the CN UI Change-Id: I02b1f7f5f5dff7e95528b7c55105f7447227b329 --- M CentralNotice.i18n.php M CentralNotice.php M includes/Banner.php A includes/CN_BannerPager.php A modules/ext.centralNotice.adminUi.bannerManager/bannermanager.css A modules/ext.centralNotice.adminUi.bannerManager/bannermanager.js R modules/ext.centralNotice.adminUi/centralnotice.css R modules/ext.centralNotice.adminUi/centralnotice.js R modules/ext.centralNotice.adminUi/down-arrow-ltr.png R modules/ext.centralNotice.adminUi/down-arrow-rtl.png R modules/ext.centralNotice.adminUi/up-arrow-ltr.png R modules/ext.centralNotice.adminUi/up-arrow-rtl.png M special/SpecialBannerAllocation.php M special/SpecialCentralNotice.php A special/SpecialCentralNoticeBanners.php M special/SpecialCentralNoticeLogs.php M special/SpecialNoticeTemplate.php 17 files changed, 797 insertions(+), 9 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/CentralNotice refs/changes/90/57090/1 diff --git a/CentralNotice.i18n.php b/CentralNotice.i18n.php index 783c042..3ea5138 100644 --- a/CentralNotice.i18n.php +++ b/CentralNotice.i18n.php @@ -37,6 +37,8 @@ 'centralnotice-manage-templates' => 'Manage banners', 'centralnotice-add' => 'Add', 'centralnotice-add-notice' => 'Add a campaign', + 'centralnotice-add-notice-button' => 'Create', + 'centralnotice-add-notice-cancel-button' => 'Cancel', 'centralnotice-edit-notice' => 'Edit campaign', 'centralnotice-add-template' => 'Add a banner', 'centralnotice-show-notices' => 'Show campaigns', @@ -176,7 +178,7 @@ 'centralnotice-apply-filters' => 'Apply filters', 'centralnotice-clear-filters' => 'Clear filters', 'centralnotice-banner-messages' => 'Banner messages', - 'centralnotice-filter-template-prompt' => 'Banner name contains:', + 'centralnotice-filter-template-prompt' => 'Filter banners (e.g. Jimmy 2008)', 'centralnotice-filter-template-submit' => 'Apply filter', 'centralnotice-filter-template-banner' => 'Filter banners', 'centralnotice-priority-low' => 'low', @@ -197,6 +199,8 @@ 'centralnotice-bucket-letter' => 'Bucket "$1"', 'centralnotice-devicetype-desktop' => 'Desktop Computer', 'centralnotice-all' => 'All', + 'centralnotice-generic-error' => 'You do not have the correct permissions to perform the requested action or the action itself was invalid.', + 'centralnotice-banner-name-error' => 'Invalid banner name provided. Valid names contain only alpha-numeric and underscore characters.' 'centralnotice-all-except' => 'All except $1', 'centralnotice-excluding-list' => '$1 (excluding $2)', 'centralnotice-user-role' => 'User role', diff --git a/CentralNotice.php b/CentralNotice.php index 460551a..d6e5584 100644 --- a/CentralNotice.php +++ b/CentralNotice.php @@ -56,21 +56,35 @@ 'scripts' => 'jquery.ui.multiselect/ui.multiselect.js', 'styles' => 'jquery.ui.multiselect/ui.multiselect.css', ); -$wgResourceModules[ 'ext.centralNotice.interface' ] = array( +$wgResourceModules[ 'ext.centralNotice.adminUi' ] = array( 'localBasePath' => $dir . '/modules', 'remoteExtPath' => 'CentralNotice/modules', 'dependencies' => array( 'jquery.ui.datepicker', 'jquery.ui.multiselect' ), - 'scripts' => 'ext.centralNotice.interface/centralnotice.js', - 'styles' => 'ext.centralNotice.interface/centralnotice.css', + 'scripts' => 'ext.centralNotice.adminUi/centralnotice.js', + 'styles' => 'ext.centralNotice.adminUi/centralnotice.css', 'messages' => array( 'centralnotice-documentwrite-error', 'centralnotice-close-title', 'centralnotice-select-all', 'centralnotice-remove-all', 'centralnotice-items-selected' + ) +); +$wgResourceModules[ 'ext.centralNotice.adminUi.bannerManager' ] = array( + 'localBasePath' => $dir . '/modules', + 'remoteExtPath' => 'CentralNotice/modules', + 'dependencies' => array( + 'ext.centralNotice.adminUi', + 'jquery.ui.dialog' + ), + 'scripts' => 'ext.centralNotice.adminUi.bannerManager/bannermanager.js', + 'styles' => 'ext.centralNotice.adminUi.bannerManager/bannermanager.css', + 'messages' => array( + 'centralnotice-add-notice-button', + 'centralnotice-add-notice-cancel-button' ) ); $wgResourceModules[ 'ext.centralNotice.bannerStats' ] = array( @@ -299,6 +313,8 @@ $wgAutoloadClasses[ 'SpecialGlobalAllocation' ] = $specialDir . 'SpecialGlobalAllocation.php'; $wgAutoloadClasses[ 'SpecialBannerAllocation' ] = $specialDir . 'SpecialBannerAllocation.php'; $wgAutoloadClasses[ 'SpecialCentralNoticeLogs' ] = $specialDir . 'SpecialCentralNoticeLogs.php'; + $wgAutoloadClasses[ 'SpecialCentralNoticeBanners' ] = $specialDir . 'SpecialCentralNoticeBanners.php'; + $wgAutoloadClasses[ 'CN_BannerPager' ] = $includeDir . 'CN_BannerPager.php'; $wgAutoloadClasses[ 'CNDeviceTarget' ] = $includeDir . 'CNDeviceTarget.php'; if ( $wgNoticeUseTranslateExtension ) { @@ -313,6 +329,7 @@ $wgSpecialPages[ 'GlobalAllocation' ] = 'SpecialGlobalAllocation'; $wgSpecialPages[ 'BannerAllocation' ] = 'SpecialBannerAllocation'; $wgSpecialPages[ 'CentralNoticeLogs' ] = 'SpecialCentralNoticeLogs'; + $wgSpecialPages[ 'CentralNoticeBanners'] = 'SpecialCentralNoticeBanners'; } } @@ -548,3 +565,4 @@ } $wgHooks[ 'LoadExtensionSchemaUpdates' ][ ] = 'CNDatabasePatcher::applyUpdates'; +$wgHooks[ 'SkinTemplateNavigation::SpecialPage' ][ ] = array( 'CentralNotice::addNavigationTabs' ); diff --git a/includes/Banner.php b/includes/Banner.php index 01d26c7..77b7736 100644 --- a/includes/Banner.php +++ b/includes/Banner.php @@ -702,4 +702,16 @@ $row = $dbr->selectRow( 'cn_templates', 'tmp_landing_pages', array( 'tmp_name' => $eBannerName ) ); return explode( ", ", $row->tmp_landing_pages ); } + + /** + * Validation function for banner names. Will return true iff the name fits + * the generic format of letters, numbers, and dashes. + * + * @param string $name The name to check + * + * @return bool True if valid + */ + static function isValidBannerName( $name ) { + return preg_match( '/^[A-Za-z0-9_]+$/', $name ); + } } diff --git a/includes/CN_BannerPager.php b/includes/CN_BannerPager.php new file mode 100644 index 0000000..f4e2863 --- /dev/null +++ b/includes/CN_BannerPager.php @@ -0,0 +1,314 @@ +<?php + +class CN_BannerPager extends IndexPager { + + /** @var bool True if the form is to be created with editable elements */ + protected $editable = false; + + /** @var string Space separated strings to filter banner titles on */ + protected $filter = ''; + + /** @var bool If true; the pager will page off of the master database instead of the slave */ + protected $useMasterDb = false; + + /** @var array HTMLFormFields to add to the results before every banner entry */ + protected $prependPrototypes = array(); + + /** @var array HTMLFormFields to add to the results after every banner entry */ + protected $appendPrototypes = array(); + + /** @var string 'Section' attribute to apply to the banner elements generated */ + protected $formSection = null; + + /** + * @param IContextSource $hostTitle + * @param string $formSection + * @param array $prependPrototypes + * @param array $appendPrototypes + * @param string $bannerFilter + * @param bool $editable + * @param bool $useMaster + */ + function __construct( $hostTitle, $formSection = null, $prependPrototypes = array(), + $appendPrototypes = array(), $bannerFilter = '', $editable = false, $useMaster = false + ) { + $this->editable = $editable; + $this->filter = $bannerFilter; + parent::__construct(); + + $this->useMasterDb = $useMaster; + $this->prependPrototypes = $prependPrototypes; + $this->appendPrototypes = $appendPrototypes; + $this->formSection = $formSection; + + // Override paging defaults + list( $this->mLimit, /* $offset */ ) = $this->mRequest->getLimitOffset( 20, '' ); + $this->mLimitsShown = array( 20, 50, 100 ); + $this->viewPage = $hostTitle; + $this->mDefaultDirection = true; + $this->mIsBackwards = false; + + // Use the master if required + if ( $useMaster ) { + $this->mDb = wfGetDB( DB_MASTER ); + } + } + + function getNavigationBar() { + if ( !$this->isNavigationBarShown() ) { + return none; + } + + if ( isset( $this->mNavigationBar ) ) { + return $this->mNavigationBar; + } + + $linkTexts = array( + 'prev' => $this->msg( 'pager-newer-n' )->numParams( $this->mLimit )->escaped(), + 'next' => $this->msg( 'pager-older-n' )->numParams( $this->mLimit )->escaped(), + 'first' => $this->msg( 'histlast' )->escaped(), + 'last' => $this->msg( 'histfirst' )->escaped() + ); + + $pagingLinks = $this->getPagingLinks( $linkTexts ); + $limitLinks = $this->getLimitLinks(); + $limits = $this->getLanguage()->pipeList( $limitLinks ); + + $firstLastLinks = $this->msg( 'parentheses' )->rawParams( + "{$pagingLinks['first']}" . + $this->msg( 'pipe-separator' )->escaped() . + "{$pagingLinks['last']}" + )->escaped(); + + $this->mNavigationBar = array( + 'class' => 'HTMLBannerPagerNavigation', + 'value' => $firstLastLinks . ' ' . $this->msg( 'viewprevnext' )->rawParams( + $pagingLinks['prev'], + $pagingLinks['next'], + $limits + )->escaped() + ); + + if ( $this->formSection ) { + $this->mNavigationBar['section'] = $this->formSection; + } + + return $this->mNavigationBar; + } + + /** + * Set the database query to retrieve all the banners in the database + * + * @return array of query settings + */ + function getQueryInfo() { + $dbr = wfGetDB( DB_SLAVE ); + + // When the filter comes in it is space delimited, so break that... + $likeArray = preg_split( '/\s/', $this->filter ); + + // ...and then insert all the wildcards between search terms + if ( empty( $likeArray ) ) { + $likeArray = $dbr->anyString(); + } else { + $anyStringToken = $dbr->anyString(); + $tempArray = array( $anyStringToken ); + foreach ( $likeArray as $likePart ) { + $tempArray[ ] = $likePart; + $tempArray[ ] = $anyStringToken; + } + $likeArray = $tempArray; + } + + return array( + 'tables' => array( 'templates' => 'cn_templates'), + 'fields' => array( 'templates.tmp_name', 'templates.tmp_id' ), + 'conds' => array( 'templates.tmp_name' . $dbr->buildLike( $likeArray ) ), + ); + } + + /** + * Sort the banner list by tmp_id (generally equals reverse chronological) + * + * @return string + */ + function getIndexField() { + return 'templates.tmp_id'; + } + + /** + * Generate the contents of the table pager; intended to be consumed by the HTMLForm + * + * @param $row object: database row + * + * @return array HTMLFormElement classes + */ + function formatRow( $row ) { + $retval = array(); + + $bannerId = $row->tmp_id; + $bannerName = $row->tmp_name; + + // Add the prepend prototypes + foreach( $this->prependPrototypes as $prototypeName => $prototypeValues ) { + $retval[ "{$prototypeName}-{$bannerId}" ] = $prototypeValues; + if ( array_key_exists( 'id', $prototypeValues ) ) { + $retval[ "{$prototypeName}-{$bannerId}" ][ 'id' ] .= "-$bannerId"; + } + } + + // Now do the banner + $retval["cn-banner-list-element-$bannerId"] = array( + 'class' => 'HTMLCentralNoticeBanner', + 'banner' => $bannerName + ); + if ( $this->formSection ) { + $retval["cn-banner-list-element-$bannerId"]['section'] = $this->formSection; + } + + // Append prototypes + foreach( $this->appendPrototypes as $prototypeName => $prototypeValues ) { + $retval[ $prototypeName . "-$bannerId" ] = $prototypeValues; + if ( array_key_exists( 'id', $prototypeValues ) ) { + $retval[ $prototypeName . "-$bannerId" ][ 'id' ] .= "-$bannerId"; + } + } + + // Set the disabled attribute + if ( !$this->editable ) { + foreach( $retval as $prototypeName => $prototypeValues ) { + $retval[ $prototypeName ][ 'disabled' ] = true; + } + } + + return $retval; + } + + /** + * Get the formatted result list. Calls getStartBody(), formatRow() and + * getEndBody(), concatenates the results and returns them. + * + * @return array + */ + public function getBody() { + if ( !$this->mQueryDone ) { + $this->doQuery(); + } + + if ( $this->mResult->numRows() ) { + # Do any special query batches before display + $this->doBatchLookups(); + } + + # Don't use any extra rows returned by the query + $numRows = min( $this->mResult->numRows(), $this->mLimit ); + + $retval = array(); + + if ( $numRows ) { + if ( $this->mIsBackwards ) { + for ( $i = $numRows - 1; $i >= 0; $i-- ) { + $this->mResult->seek( $i ); + $row = $this->mResult->fetchObject(); + $retval += $this->formatRow( $row ); + } + } else { + $this->mResult->seek( 0 ); + for ( $i = 0; $i < $numRows; $i++ ) { + $row = $this->mResult->fetchObject(); + $retval += $this->formatRow( $row ); + } + } + } else { + // TODO: empty value + } + return $retval; + } +} + +class HTMLBannerPagerNavigation extends HTMLFormField { + /** Empty - no validation can be done on a navigation element */ + function validate( $value, $alldata ) { return true; } + + public function getInputHTML( $value ) { + return $value; + } +} + +/** + * Produces a banner preview DIV that can be embedded in an HTMLForm. + * + * Expects the following options: + * - 'language' - ISO language code to render banner in + * - 'banner' - Canonical name of banner + */ +class HTMLCentralNoticeBanner extends HTMLFormField { + /** Empty - no validation can be done on a banner */ + function validate( $value, $alldata ) { return true; } + + /** Get a preview of the banner */ + public function getInputHTML( $value ) { + global $wgOut; + + $renderer = new SpecialBannerLoader(); + $renderer->language = array_key_exists( 'language', $this->mParams ) + ? $this->mParams['language'] + : $wgOut->getLanguage()->getCode(); + + $bannerName = $this->mParams['banner']; + try { + $preview = $renderer->getHtmlNotice( $bannerName ); + } catch ( SpecialBannerLoaderException $e ) { + $preview = $this->msg( 'centralnotice-nopreview' )->text(); + } + + return Xml::tags( + 'div', + array( + 'id' => Sanitizer::escapeId( "cn-banner-preview-$bannerName" ), + 'class' => 'cn-banner-preview-div', + ), + $preview + ); + } + + public function getTableRow( $value ) { + throw new MWException( "getTableRow() is not implemented for HTMLCentralNoticeBanner" ); + } + + public function getRaw( $value ) { + throw new MWException( "getRaw() is not implemented for HTMLCentralNoticeBanner" ); + } + + public function getDiv( $value ) { + $html = Xml::openElement( + 'div', + array( + 'id' => Sanitizer::escapeId( "cn-banner-list-element-{$this->mParams['banner']}" ), + 'class' => "cn-banner-list-element", + ) + ); + + // Make the label; this consists of a text link to the banner editor, and a series of status icons + $html .= Xml::openElement( 'div', array( 'class' => 'cn-banner-list-element-label' ) ); + $html .= Xml::tags( + 'div', + array( 'class' => 'cn-banner-list-element-label-text' ), + Linker::link( + SpecialPage::getTitleFor( 'CentralNoticeBanners', 'Edit' ), + htmlspecialchars( $this->mParams['banner'] ), + array(), + array( 'banner' => $this->mParams['banner'] ) + ) + ); + // TODO: Output status icons + $html .= Xml::tags( 'div', array( 'class' => 'cn-banner-list-element-label-icons' ), '' ); + $html .= Xml::closeElement( 'div' ); + + // Add the banner preview + $html .= $this->getInputHTML( null ); + + $html .= Xml::closeElement( 'div' ); + return $html; + } +} diff --git a/modules/ext.centralNotice.adminUi.bannerManager/bannermanager.css b/modules/ext.centralNotice.adminUi.bannerManager/bannermanager.css new file mode 100644 index 0000000..fb477db --- /dev/null +++ b/modules/ext.centralNotice.adminUi.bannerManager/bannermanager.css @@ -0,0 +1,71 @@ +/* === Banner Manager List Form === */ +/* --- General Form Elements --- */ +/* Restyle the fieldsets to make them less annoying */ +#cn-banner-manager fieldset { + border: none; + margin: 0; + padding: 0; +} +#cn-banner-manager legend { + display: none; +} + +/* --- Banner management header --- */ +/* General header */ +#cn-banner-manager #cn-formsection-header { + padding: 0.5em; + background-color: #F9F9F9; + border: 1px solid #2F6FAB; +} + +/* Style the search sub form */ +#cn-formsection-header-banner-search, +#mw-htmlform-banner-search .mw-htmlform-field-HTMLSubmitField { + float: right; +} +#mw-htmlform-banner-search .mw-htmlform-field-HTMLTextField { + float: left; +} +#mw-input-wpbannerNameLike { + width: 17em; +} + +/* Bulk management options */ +#cn-formsection-header-banner-bulk-manage { + float: left; +} +#cn-formsection-header-banner-bulk-manage div { + display: inline; +} + +/* One off actions */ +fieldset #cn-formsection-header-one-off { + padding-left: 1em; +} + +/* --- Banner preview list --- */ +#mw-htmlform-banner-list { + margin-left: 0.9em; +} +.cn-banner-list-element { + margin: 0 0 0.5em 0.5em; +} +.cn-banner-preview-div { + border: 1px solid #A7D7F9; + padding: 1em; + margin-top: 0.5em; +} +#mw-htmlform-banner-list .mw-htmlform-field-HTMLCheckField { + float: left; +} +#mw-htmlform-banner-list .cn-banner-list-element { + display: inline; +} + +/* --- Add banner dialog --- */ +#cn-formsection-addBanner { + display: none; +} +.ui-dialog-content legend { + display: none; +} \ No newline at end of file diff --git a/modules/ext.centralNotice.adminUi.bannerManager/bannermanager.js b/modules/ext.centralNotice.adminUi.bannerManager/bannermanager.js new file mode 100644 index 0000000..fdbe1e7 --- /dev/null +++ b/modules/ext.centralNotice.adminUi.bannerManager/bannermanager.js @@ -0,0 +1,35 @@ +/** + * CentralNotice Banner Management UI Functions + */ +( function ( $, mw ) { + mw.centralNotice.adminUi.bannerManagement = { + doAddBannerDialog: function() { + var buttons = {}, + okButtonText = mw.message('centralnotice-add-notice-button').text(), + cancelButtonText = mw.message('centralnotice-add-notice-cancel-button').text(), + dialogObj = $('<form></form>'); + + // Implement the functionality + buttons[ cancelButtonText ] = function() { $(this).dialog("close"); }; + buttons[ okButtonText ] = function() { + var formobj = $('#cn-banner-manager')[0]; + formobj.wpaction.value = 'create'; + formobj.wpnewBannerName.value = $(this)[0].wpnewBannerName.value; + formobj.submit(); + }; + + // Create the dialog by copying the textfield element into a new form + dialogObj[0].name = 'addBannerDialog'; + dialogObj.append( $( '#cn-formsection-addBanner' ).children( 'div' ).clone().show() ) + .dialog( { + title: 'Add a new banner', + modal: true, + buttons: buttons, + width: 375 + }); + + // Do not submit the form... that's up to the ok button + return false; + } + }; +} )( jQuery, mediaWiki ); diff --git a/modules/ext.centralNotice.interface/centralnotice.css b/modules/ext.centralNotice.adminUi/centralnotice.css similarity index 94% rename from modules/ext.centralNotice.interface/centralnotice.css rename to modules/ext.centralNotice.adminUi/centralnotice.css index 2675e89..c721e82 100644 --- a/modules/ext.centralNotice.interface/centralnotice.css +++ b/modules/ext.centralNotice.adminUi/centralnotice.css @@ -140,3 +140,12 @@ body.skin-monobook #preferences fieldset.prefsection { border-color:#AAAAAA; } + +/* For HTMLForm generated forms */ +form .error { + color: #000000; + margin: 0 0 0.5em 0; + padding: 0.5em; + background-color: #FFCCCC; + border: 2px solid #FF0000; +} diff --git a/modules/ext.centralNotice.interface/centralnotice.js b/modules/ext.centralNotice.adminUi/centralnotice.js similarity index 97% rename from modules/ext.centralNotice.interface/centralnotice.js rename to modules/ext.centralNotice.adminUi/centralnotice.js index ee4df0b..d40367f 100644 --- a/modules/ext.centralNotice.interface/centralnotice.js +++ b/modules/ext.centralNotice.adminUi/centralnotice.js @@ -1,3 +1,8 @@ +/** + * CentralNotice Administrative UI - Common Functions + */ +window.mw.centralNotice.adminUi = {}; + // Collapse and uncollapse detailed view for an individual log entry window.toggleLogDisplay = function ( logId ) { var thisCollapsed = document.getElementById( 'cn-collapsed-' + logId ); diff --git a/modules/ext.centralNotice.interface/down-arrow-ltr.png b/modules/ext.centralNotice.adminUi/down-arrow-ltr.png similarity index 100% rename from modules/ext.centralNotice.interface/down-arrow-ltr.png rename to modules/ext.centralNotice.adminUi/down-arrow-ltr.png Binary files differ diff --git a/modules/ext.centralNotice.interface/down-arrow-rtl.png b/modules/ext.centralNotice.adminUi/down-arrow-rtl.png similarity index 100% rename from modules/ext.centralNotice.interface/down-arrow-rtl.png rename to modules/ext.centralNotice.adminUi/down-arrow-rtl.png Binary files differ diff --git a/modules/ext.centralNotice.interface/up-arrow-ltr.png b/modules/ext.centralNotice.adminUi/up-arrow-ltr.png similarity index 100% rename from modules/ext.centralNotice.interface/up-arrow-ltr.png rename to modules/ext.centralNotice.adminUi/up-arrow-ltr.png Binary files differ diff --git a/modules/ext.centralNotice.interface/up-arrow-rtl.png b/modules/ext.centralNotice.adminUi/up-arrow-rtl.png similarity index 100% rename from modules/ext.centralNotice.interface/up-arrow-rtl.png rename to modules/ext.centralNotice.adminUi/up-arrow-rtl.png Binary files differ diff --git a/special/SpecialBannerAllocation.php b/special/SpecialBannerAllocation.php index 624fe7f..5cc6c79 100644 --- a/special/SpecialBannerAllocation.php +++ b/special/SpecialBannerAllocation.php @@ -84,7 +84,7 @@ // Output ResourceLoader module for styling and javascript functions $out->addModules( array( - 'ext.centralNotice.interface', + 'ext.centralNotice.adminUi', 'ext.centralNotice.bannerStats' ) ); diff --git a/special/SpecialCentralNotice.php b/special/SpecialCentralNotice.php index bb8d3e4..7466767 100644 --- a/special/SpecialCentralNotice.php +++ b/special/SpecialCentralNotice.php @@ -13,6 +13,8 @@ parent::__construct( 'CentralNotice' ); } + function __destruct() {} + /** * Handle different types of page requests */ @@ -25,7 +27,7 @@ $request = $this->getRequest(); // Output ResourceLoader module for styling and javascript functions - $out->addModules( 'ext.centralNotice.interface' ); + $out->addModules( 'ext.centralNotice.adminUi' ); // Check permissions $this->editable = $this->getUser()->isAllowed( 'centralnotice-admin' ); @@ -1386,7 +1388,7 @@ * * @return string Space delimited string */ - protected function sanitizeSearchTerms( $terms ) { + public static function sanitizeSearchTerms( $terms ) { $retval = ' '; // The space is important... it gets trimmed later foreach ( preg_split( '/\s/', $terms ) as $term ) { @@ -1400,6 +1402,85 @@ return trim( $retval ); } + /** + * Adds CentralNotice specific navigation tabs to the UI. + * Implementation of SkinTemplateNavigation::SpecialPage hook. + * + * @param Skin $skin Reference to the Skin object + * @param array $tabs Any current skin tabs + * + * @return boolean + */ + public static function addNavigationTabs( Skin $skin, array &$tabs ) { + $title = $skin->getTitle(); + list( $alias, $sub ) = SpecialPageFactory::resolveAlias( $title->getText() ); + + $centralNoticePages = array( + 'CentralNotice' => array( + 'type' => 'namespaces', + 'message' => 'centralnotice-notices', + ), + 'CentralNoticeBanners' => array( + 'type' => 'namespaces', + 'message' => 'centralnotice-templates', + ), + + 'BannerAllocation' => array( + 'type' => 'views', + 'message' => 'centralnotice-allocation', + ), + 'CentralNoticeLogs' => array( + 'type' => 'views', + 'message' => 'centralnotice-logs', + ), + ); + + if ( !array_key_exists( $alias, $centralNoticePages ) ) { + return true; + } + + // Clear the special page tab that's there already + $tabs['namespaces'] = array(); + + // Now add our own + foreach ( $centralNoticePages as $page => $keys ) { + $tabs[ $keys[ 'type' ] ][ $page ] = array( + 'text' => wfMessage( $keys[ 'message' ] ), + 'href' => SpecialPage::getTitleFor( $page )->getFullURL(), + 'class' => ( $alias === $page ) ? 'selected' : '', + ); + } + + return true; + } + + /** + * Loads a CentralNotice variable from session data. + * + * @param string $variable Name of the variable + * @param object $default Default value of the variable + * + * @return object Stored variable or default + */ + public static function getCNSessionVar( $variable, $default = null ) { + if ( array_key_exists( $variable, $_SESSION ) ) { + return $_SESSION[ "centralnotice-$variable" ]; + } else { + return $default; + } + } + + /** + * Sets a CentralNotice session variable. Note that this will fail silently if a + * session does not exist for the user. + * + * @param string $variable Name of the variable + * @param object $value Value for the variable + */ + public static function setCNSessionVar( $variable, $value ) { + $_SESSION[ "centralnotice-$variable" ] = $value; + } + public function listProjects( $projects ) { global $wgNoticeProjects; return $this->makeShortList( $wgNoticeProjects, $projects ); diff --git a/special/SpecialCentralNoticeBanners.php b/special/SpecialCentralNoticeBanners.php new file mode 100644 index 0000000..721f2d1 --- /dev/null +++ b/special/SpecialCentralNoticeBanners.php @@ -0,0 +1,239 @@ +<?php + +/** + * Special page for management of CentralNotice banners + */ +class SpecialCentralNoticeBanners extends CentralNotice { + + /** @var bool True if edits have been made this sessions -- hint to use the DB_MASTER */ + protected $inEditAction = false; + + /** @var string Filter to apply to the banner search when generating the list */ + protected $bannerFilterString = ''; + + function __construct() { + SpecialPage::__construct( 'CentralNoticeBanners' ); + $this->getOutput()->addModules( 'ext.centralNotice.adminUi.bannerManager' ); + + // Make sure we have a session + wfSetupSession(); + + // Load things that may have been serialized into the session + $this->bannerFilterString = $this->getCNSessionVar( 'bannerFilterString', '' ); + } + + function __destruct() { + $this->setCNSessionVar( 'bannerFilterString', $this->bannerFilterString ); + } + + public function isListed() { + return false; + } + + /** + * Handle all the different types of page requests determined by $action + * + * Valid actions are: + * Null - Display a list of banners + * Edit - Edits an existing banner + */ + public function execute( $action ) { + // Do all the common setup + $this->setHeaders(); + $this->getOutput()->addModules( 'ext.centralNotice.adminUi' ); + $this->editable = $this->getUser()->isAllowed( 'centralnotice-admin' ); + + switch ( strtolower( $action ) ) { + case null: + // Display the list of banners + $this->showBannerList(); + break; + + case 'create': + case 'edit': + default: + // Something went wrong; display error page + $this->getOutput()->addHTML( + Xml::element( 'div', array( 'class' => 'error' ), wfMessage( 'centralnotice-generic-error' ) ) + ); + break; + } + } + + /** + * Process the 'banner list' form and display a new one. + */ + protected function showBannerList() { + $out = $this->getOutput(); + $out->setPageTitle( 'Manage Banners' ); + + // Process the form that we sent out + $formDescriptor = $this->generateBannerListForm(); + $htmlForm = new CentralNoticeHtmlForm( $formDescriptor, $this->getContext() ); + $htmlForm->setSubmitCallback( array( $this, 'processBannerList' ) ); + $htmlForm->loadData(); + $formResult = $htmlForm->trySubmit(); + + // Re-generate the form in case they changed the filter string, archived something, + // deleted something, etc... + $formDescriptor = $this->generateBannerListForm( $this->bannerFilterString ); + $htmlForm = new CentralNoticeHtmlForm( $formDescriptor, $this->getContext() ); + + $htmlForm->setId('cn-banner-manager')-> + suppressDefaultSubmit()-> + setDisplayFormat( 'div' )-> + setSubmitTextMsg( 'centralnotice-filter-template-submit' )-> + setSubmitCallback( 'SpecialCentralNoticeBanners::processBannerList' )-> + prepareForm()-> + displayForm( $formResult ); + } + + /** + * Generates the HTMLForm entities for the 'banner list' form. + * + * @param string $filter Filter to use for the banner list + * + * @return array of HTMLForm entities + */ + protected function generateBannerListForm( $filter = '' ) { + // --- Create the banner search form --- // + $formDescriptor = array( + 'bannerNameLike' => array( + 'section' => 'header/banner-search', + 'class' => 'HTMLTextField', + 'placeholder' => wfMessage( 'centralnotice-filter-template-prompt' ), + 'filter-callback' => array( $this, 'sanitizeSearchTerms' ) + ), + 'filterSubmit' => array( + 'section' => 'header/banner-search', + 'class' => 'HTMLSubmitField', + 'default' => wfMessage( 'centralnotice-filter-template-submit' )->text(), + ) + ); + + // --- Create the management options --- // + $formDescriptor += array( + 'selectAllBanners' => array( + 'section' => 'header/banner-bulk-manage', + 'class' => 'HTMLCheckField', + 'disabled' => !$this->editable, + ), + 'archiveSelectedBanners' => array( + 'section' => 'header/banner-bulk-manage', + 'class' => 'HTMLSubmitField', + 'default' => 'Archive', + 'disabled' => !$this->editable, + ), + 'deleteSelectedBanners' => array( + 'section' => 'header/banner-bulk-manage', + 'class' => 'HTMLSubmitField', + 'default' => wfMessage( 'centralnotice-remove' )->text(), + 'disabled' => !$this->editable, + ), + 'addNewBanner' => array( + 'section' => 'header/one-off', + 'class' => 'HTMLButtonField', + 'default' => wfMessage( 'centralnotice-add-template' )->text(), + 'disabled' => !$this->editable, + 'onclick' => 'mw.centralNotice.adminUi.bannerManagement.doAddBannerDialog()', + ), + 'newBannerName' => array( + 'section' => 'addBanner', + 'class' => 'HTMLTextField', + 'disabled' => !$this->editable, + 'label' => wfMessage( 'centralnotice-banner-name' )->text(), + ), + 'action' => array( + 'type' => 'hidden', + ) + ); + + // --- Add all the banners via the fancy pager object --- + $pager = new CN_BannerPager( + $this->getTitle(), + 'banner-list', + array( + 'applyTo' => array( + 'section' => 'banner-list', + 'class' => 'HTMLCheckField', + ) + ), + array(), + $filter, + $this->editable + ); + $formDescriptor[ 'topPagerNav' ] = $pager->getNavigationBar(); + $formDescriptor += $pager->getBody(); + $formDescriptor[ 'bottomPagerNav' ] = $pager->getNavigationBar(); + + return $formDescriptor; + } + + /** + * Callback function from the showBannerList() form that actually processes the + * response data. + * + * @param $formData + * + * @return null|string|array + */ + public function processBannerList( $formData ) { + $this->bannerFilterString = $formData[ 'bannerNameLike' ]; + + if ( $formData[ 'action' ] && $this->editable ) { + switch ( strtolower( $formData[ 'action' ] ) ) { + case 'create': + // Attempt to create a new banner and redirect; we validate here because it's + // a hidden field and that doesn't work so well with the form + if ( !Banner::isValidBannerName( $formData[ 'newBannerName' ] ) ) { + return wfMessage( 'centralnotice-banner-name-error' ); + } + + if ( Banner::bannerExists( $formData[ 'newBannerName' ] ) ) { + return wfMessage( 'centralnotice-template-exists' )->text(); + } else { + $retval = Banner::addTemplate( + $formData[ 'newBannerName' ], + "<!-- Empty banner -->", + $this->getUser(), + false, + false + ); + + if ( $retval ) { + // Something failed; display error to user + return wfMessage( $retval )->text(); + } else { + $this->getOutput()->redirect( + SpecialPage::getTitleFor( 'CentralNoticeBanners', 'Edit' )-> + getFullURL( "banner={$formData[ 'newBannerName' ]}" ) + ); + } + } + break; + + case 'archive': + case 'remove': + break; + } + } elseif ( $formData[ 'action' ] ) { + // Oh noes! The l33t hakorz are here... + return wfMessage( 'centralnotice-generic-error' )->text(); + } + + return null; + } +} + +/** + * Class CentralNoticeHtmlForm + */ +class CentralNoticeHtmlForm extends HTMLForm { + /** + * Get the whole body of the form. + * @return string + */ + function getBody() { + return $this->displaySection( $this->mFieldTree, '', 'cn-formsection-' ); + } +} diff --git a/special/SpecialCentralNoticeLogs.php b/special/SpecialCentralNoticeLogs.php index 2531ac1..50cd181 100644 --- a/special/SpecialCentralNoticeLogs.php +++ b/special/SpecialCentralNoticeLogs.php @@ -27,7 +27,7 @@ $this->setHeaders(); // Output ResourceLoader module for styling and javascript functions - $out->addModules( 'ext.centralNotice.interface' ); + $out->addModules( 'ext.centralNotice.adminUi' ); // Initialize error variable $this->centralNoticeError = false; diff --git a/special/SpecialNoticeTemplate.php b/special/SpecialNoticeTemplate.php index b7eb73e..90333a7 100644 --- a/special/SpecialNoticeTemplate.php +++ b/special/SpecialNoticeTemplate.php @@ -24,7 +24,7 @@ $this->setHeaders(); // Output ResourceLoader module for styling and javascript functions - $out->addModules( 'ext.centralNotice.interface' ); + $out->addModules( 'ext.centralNotice.adminUi' ); // Check permissions $this->editable = $user->isAllowed( 'centralnotice-admin' ); -- To view, visit https://gerrit.wikimedia.org/r/57090 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I02b1f7f5f5dff7e95528b7c55105f7447227b329 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/CentralNotice Gerrit-Branch: master Gerrit-Owner: Mwalker <mwal...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits