Page MenuHomeWickedGov Phorge

SpecialCentralNoticeBanners.php
No OneTemporary

Size
33 KB
Referenced Files
None
Subscribers
None

SpecialCentralNoticeBanners.php

<?php
use MediaWiki\Deferred\DeferredUpdates;
use MediaWiki\Html\Html;
use MediaWiki\HTMLForm\Field\HTMLButtonField;
use MediaWiki\HTMLForm\Field\HTMLCheckField;
use MediaWiki\HTMLForm\Field\HTMLInfoField;
use MediaWiki\HTMLForm\Field\HTMLSelectLimitField;
use MediaWiki\HTMLForm\Field\HTMLSubmitField;
use MediaWiki\HTMLForm\Field\HTMLTextField;
use MediaWiki\MediaWikiServices;
use MediaWiki\SpecialPage\SpecialPage;
use MediaWiki\Xml\Xml;
/**
* Special page for management of CentralNotice banners
*/
class SpecialCentralNoticeBanners extends CentralNotice {
/** @var string Name of the banner we're currently editing */
private $bannerName = '';
/** @var Banner|null Banner object we're currently editing */
private $banner = null;
/** @var string Filter to apply to the banner search when generating the list */
private $bannerFilterString = '';
/** @var string Language code to render preview materials in */
private $bannerLanguagePreview;
/** @var bool If true, form execution must stop and the page will be redirected */
private $bannerFormRedirectRequired = false;
/** @var array|null Names of the banners that are marked as templates */
private $templateBannerNames = null;
public function __construct() {
SpecialPage::__construct( 'CentralNoticeBanners' );
}
public function doesWrites() {
return true;
}
/**
* Handle all the different types of page requests determined by the first subpage
* level after the special page title. If needed, the second subpage level is the
* banner name.
*
* Valid actions are:
* (none) - Display a list of banners
* edit - Edits an existing banner
*
* TODO: Use the "?action=" convention rather than parsing the URL subpath.
*
* @param string|null $subPage
*/
public function execute( $subPage ) {
// Do all the common setup
$this->setHeaders();
$this->editable = $this->getUser()->isAllowed( 'centralnotice-admin' );
// Make sure we have a session
$this->getRequest()->getSession()->persist();
// Load things that may have been serialized into the session
$this->bannerLanguagePreview = $this->getCNSessionVar(
'bannerLanguagePreview',
$this->getLanguage()->getCode()
);
// User settable text for some custom message, like usage instructions
$this->getOutput()->setPageTitleMsg( $this->msg( 'noticetemplate' ) );
// Allow users to add a custom nav bar (T138284)
$navBar = $this->msg( 'centralnotice-navbar' )->inContentLanguage();
if ( !$navBar->isDisabled() ) {
$this->getOutput()->addHTML( $navBar->parseAsBlock() );
}
$this->getOutput()->addWikiMsg( 'centralnotice-summary' );
// Now figure out what to display
// TODO Use only params instead of subpage to indicate action
$parts = explode( '/', $subPage );
$action = $parts[0] ?: 'list';
$this->bannerName = $parts[1] ?? '';
switch ( strtolower( $action ) ) {
case 'list':
// Display the list of banners
$this->showBannerList();
break;
case 'edit':
if ( $this->bannerName ) {
$this->ensureBanner( $this->bannerName );
$this->showBannerEditor();
} else {
throw new ErrorPageError( 'noticetemplate', 'centralnotice-banner-name-error' );
}
break;
default:
// Something went wrong; display error page
throw new ErrorPageError( 'noticetemplate', 'centralnotice-generic-error' );
}
}
/**
* Ensure that $this->banner is assigned.
*
* @param string $bannerName
* @throws ErrorPageError
*/
private function ensureBanner( $bannerName ) {
if ( !Banner::isValidBannerName( $bannerName ) ) {
throw new ErrorPageError( 'noticetemplate', 'centralnotice-generic-error' );
}
if ( $this->banner ) {
return;
}
$this->banner = Banner::fromName( $this->bannerName );
if ( !$this->banner->exists() ) {
throw new ErrorPageError( 'centralnotice-banner-not-found-title',
'centralnotice-banner-not-found-contents' );
}
}
/**
* Process the 'banner list' form and display a new one.
*/
private function showBannerList() {
$out = $this->getOutput();
$out->setPageTitleMsg( $this->msg( 'centralnotice-manage-templates' ) );
$out->addModules( 'ext.centralNotice.adminUi.bannerManager' );
// Process the form that we sent out
$formDescriptor = $this->generateBannerListForm( $this->bannerFilterString );
$htmlForm = new CentralNoticeHtmlForm( $formDescriptor, $this->getContext() );
$htmlForm->setSubmitCallback( [ $this, 'processBannerList' ] )
->prepareForm();
$formResult = $htmlForm->trySubmit();
if ( $this->bannerFormRedirectRequired ) {
return;
}
// 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' )
->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
*/
private function generateBannerListForm( $filter = '' ) {
// --- Create the banner search form --- //
// Note: filter is normally set via JS, not form submission. But we
// leave the info in the submitted form, in any case.
$formDescriptor = [
'bannerNameFilter' => [
'section' => 'header/banner-search',
'class' => HTMLTextField::class,
'placeholder-message' => 'centralnotice-filter-template-prompt',
'filter-callback' => [ $this, 'sanitizeSearchTerms' ],
'default' => $filter,
],
'filterApply' => [
'section' => 'header/banner-search',
'class' => HTMLButtonField::class,
'default' => $this->msg( 'centralnotice-filter-template-submit' )->text(),
]
];
// --- Create the management options --- //
$formDescriptor += [
'selectAllBanners' => [
'section' => 'header/banner-bulk-manage',
'class' => HTMLCheckField::class,
'disabled' => !$this->editable,
],
/* TODO: Actually enable this feature
'archiveSelectedBanners' => array(
'section' => 'header/banner-bulk-manage',
'class' => HTMLButtonField::class,
'default' => 'Archive',
'disabled' => !$this->editable,
),
*/
'deleteSelectedBanners' => [
'section' => 'header/banner-bulk-manage',
'class' => HTMLButtonField::class,
'default' => $this->msg( 'centralnotice-remove' )->text(),
'disabled' => !$this->editable,
],
'addNewBanner' => [
'section' => 'header/one-off',
'class' => HTMLButtonField::class,
'default' => $this->msg( 'centralnotice-add-template' )->text(),
'disabled' => !$this->editable,
],
'newBannerName' => [
'section' => 'addBanner',
'class' => HTMLTextField::class,
'disabled' => !$this->editable,
'label' => $this->msg( 'centralnotice-banner-name' )->text(),
],
'createFromTemplateCheckbox' => [
'section' => 'addBanner',
'class' => HTMLCheckField::class,
'label' => $this->msg( 'centralnotice-create-from-template-checkbox-label' )->text(),
'disabled' => !$this->editable || !$this->getTemplateBannerDropdownItems(),
],
'newBannerTemplate' => [
'section' => 'addBanner',
'class' => HTMLSelectLimitField::class,
'cssclass' => 'banner-template-dropdown-hidden',
'disabled' => !$this->editable || !$this->getTemplateBannerDropdownItems(),
'options' => $this->getTemplateBannerDropdownItems()
],
'newBannerEditSummary' => [
'section' => 'addBanner',
'class' => HTMLTextField::class,
'label-message' => 'centralnotice-change-summary-label',
'placeholder-message' => 'centralnotice-change-summary-action-prompt',
'disabled' => !$this->editable,
'filter-callback' => [ $this, 'truncateSummaryField' ]
],
'removeBannerEditSummary' => [
'section' => 'removeBanner',
'class' => HTMLTextField::class,
'label-message' => 'centralnotice-change-summary-label',
'placeholder-message' => 'centralnotice-change-summary-action-prompt',
'disabled' => !$this->editable,
'filter-callback' => [ $this, 'truncateSummaryField' ]
],
'action' => [
'type' => 'hidden',
]
];
// --- Add all the banners via the fancy pager object ---
$pager = new CNBannerPager(
$this,
'banner-list',
[
'applyTo' => [
'section' => 'banner-list',
'class' => HTMLCheckField::class,
'cssclass' => 'cn-bannerlist-check-applyto',
]
],
[],
$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 array $formData
*
* @return null|string|array
*/
public function processBannerList( $formData ) {
$this->setFilterFromUrl();
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 $this->msg( 'centralnotice-banner-name-error' )->parse();
} else {
$this->bannerName = $formData[ 'newBannerName' ];
}
if ( Banner::fromName( $this->bannerName )->exists() ) {
return $this->msg( 'centralnotice-template-exists' )->parse();
} else {
if ( !empty( $formData['newBannerTemplate'] ) ) {
try {
$bannerTemplate = Banner::fromName( $formData['newBannerTemplate'] );
// This will do data load for the banner, confirming it actually exists in the DB
// without calling Banner::exists()
if ( !$bannerTemplate || !$bannerTemplate->isTemplate() ) {
throw new BannerDataException(
"Attempted to create a banner based on invalid template"
);
}
} catch ( BannerDataException $exception ) {
wfDebugLog( 'CentralNotice', $exception->getMessage() );
// We do not want to show the actual exception to the user here,
// since the message does not actually refer to the template being created,
// but to the template it is being created from
return $this->msg( 'centralnotice-banner-template-error' )->plain();
}
$retval = Banner::addFromBannerTemplate(
$this->bannerName,
$this->getUser(),
$bannerTemplate,
$formData['newBannerEditSummary']
);
} else {
$retval = Banner::addBanner(
$this->bannerName,
"<!-- Empty banner -->",
$this->getUser(),
false,
false,
// Default values of a zillion parameters...
[], [], null,
$formData['newBannerEditSummary']
);
}
if ( $retval ) {
// Something failed; display error to user
return $this->msg( $retval )->parse();
} else {
$this->getOutput()->redirect(
SpecialPage::getTitleFor(
'CentralNoticeBanners', "edit/{$this->bannerName}"
)->getFullURL()
);
$this->bannerFormRedirectRequired = true;
}
}
break;
case 'archive':
return 'Archiving not yet implemented!';
case 'remove':
$summary = $formData['removeBannerEditSummary'];
$failed = [];
foreach ( $formData as $element => $value ) {
$parts = explode( '-', $element, 2 );
if ( ( $parts[0] === 'applyTo' ) && ( $value === true ) ) {
try {
Banner::removeBanner(
$parts[1], $this->getUser(), $summary );
} catch ( Exception $ex ) {
$failed[] = $parts[1];
}
}
}
if ( $failed ) {
return 'some banners were not deleted';
} else {
// Go back to the special banners page, including the
// same filter that was already set in the URL.
$this->getOutput()->redirect(
SpecialPage::getTitleFor( 'CentralNoticeBanners' )
->getFullURL( $this->getFilterUrlParamAsArray() ) );
$this->bannerFormRedirectRequired = true;
}
break;
}
} elseif ( $formData[ 'action' ] ) {
// Oh noes! The l33t hakorz are here...
return $this->msg( 'centralnotice-generic-error' )->parse();
}
return null;
}
/**
* Use a URL parameter to set the filter string for the banner list.
*/
private function setFilterFromUrl() {
// This is the normal param on visible URLs.
$filterParam = $this->getRequest()->getVal( 'filter', null );
// If the form was posted the filter parameter'll have a different name.
if ( $filterParam === null ) {
$filterParam =
$this->getRequest()->getVal( 'wpbannerNameFilter', null );
}
// Clean, clean...
if ( $filterParam !== null ) {
$this->bannerFilterString = $this->sanitizeSearchTerms( $filterParam );
}
}
/**
* Return an array for use in constructing a URL query part with or without
* a filter parameter, as required.
*
* @return array
*/
public function getFilterUrlParamAsArray() {
return $this->bannerFilterString ?
[ 'filter' => $this->bannerFilterString ] : [];
}
/**
* Returns array of navigation links to banner preview URL and
* edit link to the banner's wikipage if the user is allowed.
*
* FIXME Some of this code is repeated in BannerRenderer, but probably
* should be elsewhere.
*
* @return array
*/
private function getBannerPreviewEditLinks() {
$linkRenderer = $this->getLinkRenderer();
$links = [
$linkRenderer->makeKnownLink(
SpecialPage::getTitleFor( 'Randompage' ),
$this->msg( 'centralnotice-live-preview' )->text(),
[ 'class' => 'cn-banner-list-element-label-text' ],
[
'banner' => $this->bannerName,
'uselang' => $this->bannerLanguagePreview,
'force' => '1',
]
)
];
$bannerTitle = $this->banner->getTitle();
// $bannerTitle can be null sometimes
if ( $bannerTitle && $this->getUser()->isAllowed( 'editinterface' ) ) {
$links[] = $linkRenderer->makeLink(
$bannerTitle,
$this->msg( 'centralnotice-banner-edit-onwiki' )->text(),
[ 'class' => 'cn-banner-list-element-label-text' ],
[ 'action' => 'edit' ]
);
}
if ( $bannerTitle ) {
$links[] = $linkRenderer->makeLink(
$bannerTitle,
$this->msg( 'centralnotice-banner-history' )->text(),
[ 'class' => 'cn-banner-list-element-label-text' ],
[ 'action' => 'history' ]
);
}
return $links;
}
/**
* Display the banner editor and process edits
*/
private function showBannerEditor() {
global $wgUseCdn;
$out = $this->getOutput();
$out->addModules( 'ext.centralNotice.adminUi.bannerEditor' );
$this->addHelpLink(
'//meta.wikimedia.org/wiki/Special:MyLanguage/Help:CentralNotice',
true
);
$out->setPageTitle( $this->bannerName );
$out->setSubtitle(
$this->getLanguage()->pipeList( $this->getBannerPreviewEditLinks() )
);
// Generate the form
$formDescriptor = $this->generateBannerEditForm();
// Now begin form processing
$htmlForm = new CentralNoticeHtmlForm(
$formDescriptor, $this->getContext(), 'centralnotice' );
$htmlForm->setSubmitCallback( [ $this, 'processEditBanner' ] )
->prepareForm();
$formResult = $htmlForm->tryAuthorizedSubmit();
if ( $this->bannerFormRedirectRequired ) {
return;
}
// Recreate the form because something could have changed
$formDescriptor = $this->generateBannerEditForm();
$htmlForm = new CentralNoticeHtmlForm(
$formDescriptor, $this->getContext(), 'centralnotice' );
$htmlForm->setSubmitCallback( [ $this, 'processEditBanner' ] )
->setId( 'cn-banner-editor' );
// Push the form back to the user
$htmlForm->suppressDefaultSubmit()
->setId( 'cn-banner-editor' )
->setDisplayFormat( 'div' )
->prepareForm()
->displayForm( $formResult );
// Send banner name into page for access from JS
$out->addHTML( Html::element( 'span',
[
'id' => 'centralnotice-data-container',
'data-banner-name' => $this->bannerName
]
) );
if ( !$this->banner->isTemplate() ) {
// Controls to purge banner loader URLs from CDN caches for a given language.
if ( $wgUseCdn ) {
$out->addHTML( $this->generateCdnPurgeSection() );
}
$out->addHTML( Html::element( 'h2',
[ 'class' => 'cn-special-section' ],
$this->msg( 'centralnotice-campaigns-using-banner' )->text() ) );
$pager = new CNCampaignPager( $this, false, $this->banner->getId() );
$out->addModules( 'ext.centralNotice.adminUi.campaignPager' );
$out->addHTML( $pager->getBodyOutput()->getText() );
$out->addHTML( $pager->getNavigationBar() );
}
}
private function generateBannerEditForm() {
global $wgCentralNoticeBannerMixins, $wgNoticeUseTranslateExtension, $wgLanguageCode;
$languages = MediaWikiServices::getInstance()->getLanguageNameUtils()
->getLanguageNames( $this->getLanguage()->getCode() );
array_walk(
$languages,
static function ( &$val, $index ) {
$val = "$index - $val";
}
);
$languages = array_flip( $languages );
$bannerSettings = $this->banner->getBannerSettings( $this->bannerName, true );
$formDescriptor = [];
/* --- Use banner as template --- */
$campaignNames = $this->banner->getCampaignNames();
$formDescriptor['banner-is-template'] = [
'section' => 'banner-template',
'type' => 'check',
'disabled' => !$this->editable || $campaignNames,
'label-message' => 'centralnotice-banner-is-template',
'default' => $this->banner->isTemplate()
];
if ( $campaignNames ) {
$formDescriptor[ 'banner-assigned-to-campaign' ] = [
'section' => 'banner-template',
'class' => HTMLInfoField::class,
'label-message' => 'centralnotice-messages-banner-in-campaign',
'default' => implode( ', ', $campaignNames )
];
}
/* --- Banner Settings --- */
$formDescriptor['banner-class'] = [
'section' => 'settings',
'type' => 'selectorother',
'disabled' => !$this->editable,
'label-message' => 'centralnotice-banner-class',
'help-message' => 'centralnotice-banner-class-desc',
'options' => Banner::getAllUsedCategories(),
'size' => 30,
'maxlength' => 255,
'default' => $this->banner->getCategory(),
];
$selected = [];
if ( $bannerSettings[ 'anon' ] === 1 ) {
$selected[] = 'anonymous';
}
if ( $bannerSettings[ 'account' ] === 1 ) {
$selected[] = 'registered';
}
$formDescriptor[ 'display-to' ] = [
'section' => 'settings',
'type' => 'multiselect',
'disabled' => !$this->editable,
'label-message' => 'centralnotice-banner-display',
'options' => [
$this->msg( 'centralnotice-banner-logged-in' )->escaped() => 'registered',
$this->msg( 'centralnotice-banner-anonymous' )->escaped() => 'anonymous'
],
'default' => $selected,
'cssclass' => 'separate-form-element',
];
$assignedDevices = array_values(
CNDeviceTarget::getDevicesAssociatedWithBanner( $this->banner->getId() )
);
$availableDevices = [];
foreach ( CNDeviceTarget::getAvailableDevices() as $k => $value ) {
$header = htmlspecialchars( $value[ 'header' ] );
$label = $this->getOutput()->parseInlineAsInterface( $value[ 'label' ] );
$availableDevices[ "($header) $label" ] = $header;
}
$formDescriptor[ 'device-classes' ] = [
'section' => 'settings',
'type' => 'multiselect',
'disabled' => !$this->editable,
'label-message' => 'centralnotice-banner-display-on',
'options' => $availableDevices,
'default' => $assignedDevices,
'cssclass' => 'separate-form-element',
];
// TODO Remove. See T225831.
$mixinNames = array_keys( $wgCentralNoticeBannerMixins );
$availableMixins = array_combine( $mixinNames, $mixinNames );
$selectedMixins = array_keys( $this->banner->getMixins() );
$formDescriptor['mixins'] = [
'section' => 'settings',
'type' => 'multiselect',
'disabled' => !$this->editable,
'label-message' => 'centralnotice-banner-mixins',
'help-message' => 'centralnotice-banner-mixins-help',
'cssclass' => 'separate-form-element',
'options' => $availableMixins,
'default' => $selectedMixins,
];
/* --- Translatable Messages Section --- */
$messages = $this->banner->getMessageFieldsFromCache();
$linkRenderer = $this->getLinkRenderer();
if ( $messages ) {
// Only show this part of the form if messages exist
$formDescriptor[ 'translate-language' ] = [
'section' => 'banner-messages',
'class' => LanguageSelectHeaderElement::class,
'label-message' => 'centralnotice-language',
'options' => $languages,
'default' => $this->bannerLanguagePreview,
'cssclass' => 'separate-form-element',
];
$messageReadOnly = false;
if ( $wgNoticeUseTranslateExtension &&
( $this->bannerLanguagePreview !== $wgLanguageCode )
) {
$messageReadOnly = true;
}
foreach ( $messages as $messageName => $count ) {
if ( $wgNoticeUseTranslateExtension ) {
// Create per message link to the translate extension
$title = SpecialPage::getTitleFor( 'Translate' );
$label = Html::rawElement( 'td', [],
$linkRenderer->makeLink(
$title,
$messageName,
[],
[
'group' => BannerMessageGroup::getTranslateGroupName(
$this->banner->getName()
),
'task' => 'view'
]
)
);
} else {
$label = htmlspecialchars( $messageName );
}
$formDescriptor[ "message-$messageName" ] = [
'section' => 'banner-messages',
'class' => HTMLCentralNoticeBannerMessage::class,
'label-raw' => $label,
'banner' => $this->bannerName,
'message' => $messageName,
'language' => $this->bannerLanguagePreview,
'cssclass' => 'separate-form-element',
];
if ( !$this->editable || $messageReadOnly ) {
$formDescriptor[ "message-$messageName" ][ 'readonly' ] = true;
}
}
if ( $wgNoticeUseTranslateExtension ) {
$formDescriptor[ 'priority-langs' ] = [
'section' => 'banner-messages',
'class' => HTMLLargeMultiSelectField::class,
'disabled' => !$this->editable,
'label-message' => 'centralnotice-prioritylangs',
'options' => $languages,
'default' => $bannerSettings[ 'prioritylangs' ],
'help-message' => 'centralnotice-prioritylangs-explain',
'cssclass' => 'separate-form-element cn-multiselect',
];
}
if ( $wgNoticeUseTranslateExtension && BannerMessageGroup::isUsingGroupReview() ) {
$readyStateLangs = BannerMessageGroup::getLanguagesInState(
$this->bannerName,
'ready'
);
if ( $readyStateLangs ) {
$formDescriptor[ 'pending-languages' ] = [
'section' => 'banner-messages',
'class' => HTMLInfoField::class,
'disabled' => !$this->editable,
'label-message' => 'centralnotice-messages-pending-approval',
'default' => implode( ', ', $readyStateLangs ),
'cssclass' => 'separate-form-element',
];
}
}
}
/* -- The banner editor -- */
$formDescriptor[ 'banner-magic-words' ] = [
'section' => 'edit-template',
'class' => HTMLInfoField::class,
'default' => Html::rawElement(
'div',
[ 'class' => 'separate-form-element' ],
$this->msg( 'centralnotice-edit-template-summary' )->parse() ),
'rawrow' => true,
];
$renderer = new BannerRenderer( $this->getContext(), $this->banner );
$magicWords = $renderer->getMagicWords();
foreach ( $magicWords as &$word ) {
$word = wfEscapeWikiText( '{{{' . $word . '}}}' );
}
$formDescriptor[ 'banner-mixin-words' ] = [
'section' => 'edit-template',
'type' => 'info',
'default' => $this->msg(
'centralnotice-edit-template-magicwords',
$this->getLanguage()->listToText( $magicWords )
)->parse(),
'rawrow' => true,
];
$buttons = [];
// TODO: Fix this gawdawful method of inserting the close button
$buttons[] =
'<a href="#" onclick="mw.centralNotice.adminUi.bannerEditor.insertButton(\'close\');' .
'return false;">' . $this->msg( 'centralnotice-close-button' )->escaped() . '</a>';
$formDescriptor[ 'banner-insert-button' ] = [
'section' => 'edit-template',
'class' => HTMLInfoField::class,
'rawrow' => true,
'default' => Html::rawElement(
'div',
[ 'class' => 'banner-editing-top-hint separate-form-element' ],
$this->msg( 'centralnotice-insert' )->
rawParams( $this->getLanguage()->commaList( $buttons ) )->
escaped() ),
];
$formDescriptor[ 'banner-body' ] = [
'section' => 'edit-template',
'type' => 'textarea',
'readonly' => !$this->editable,
'hidelabel' => true,
'placeholder' => '<!-- blank banner -->',
'default' => $this->banner->getBodyContent(),
'cssclass' => 'separate-form-element'
];
$links = [];
foreach ( $this->banner->getIncludedTemplates() as $titleObj ) {
$links[] = $linkRenderer->makeLink( $titleObj );
}
if ( $links ) {
$formDescriptor[ 'links' ] = [
'section' => 'edit-template',
'type' => 'info',
'label-message' => 'centralnotice-templates-included',
'default' => implode( '<br />', $links ),
'raw' => true
];
}
/* --- Form bottom options --- */
$formDescriptor[ 'summary' ] = [
'section' => 'form-actions',
'class' => HTMLTextField::class,
'label-message' => 'centralnotice-change-summary-label',
'placeholder' => $this->msg( 'centralnotice-change-summary-prompt' ),
'disabled' => !$this->editable,
'filter-callback' => [ $this, 'truncateSummaryField' ]
];
$formDescriptor[ 'save-button' ] = [
'section' => 'form-actions',
'class' => HTMLSubmitField::class,
'default' => $this->msg( 'centralnotice-save-banner' )->text(),
'disabled' => !$this->editable,
'cssclass' => 'cn-formbutton',
'hidelabel' => true,
];
$formDescriptor[ 'clone-button' ] = [
'section' => 'form-actions',
'class' => HTMLButtonField::class,
'default' => $this->msg( 'centralnotice-clone' )->text(),
'disabled' => !$this->editable,
'cssclass' => 'cn-formbutton',
'hidelabel' => true,
];
/* TODO: Add this back in when we can actually support it
$formDescriptor[ 'archive-button' ] = array(
'section' => 'form-actions',
'class' => HTMLButtonField::class,
'default' => $this->msg( 'centralnotice-archive-banner' )->text(),
'disabled' => !$this->editable,
'cssclass' => 'cn-formbutton',
'hidelabel' => true,
);
*/
$formDescriptor[ 'delete-button' ] = [
'section' => 'form-actions',
'class' => HTMLButtonField::class,
'default' => $this->msg( 'centralnotice-delete-banner' )->text(),
'disabled' => !$this->editable,
'cssclass' => 'cn-formbutton',
'hidelabel' => true,
];
/* --- Hidden fields and such --- */
$formDescriptor[ 'cloneName' ] = [
'section' => 'clone-banner',
'type' => 'text',
'disabled' => !$this->editable,
'label-message' => 'centralnotice-clone-name',
];
$formDescriptor[ 'cloneEditSummary' ] = [
'section' => 'clone-banner',
'class' => HTMLTextField::class,
'label-message' => 'centralnotice-change-summary-label',
'placeholder' => $this->msg( 'centralnotice-change-summary-action-prompt' ),
'disabled' => !$this->editable,
'filter-callback' => [ $this, 'truncateSummaryField' ]
];
$formDescriptor[ 'deleteEditSummary' ] = [
'section' => 'delete-banner',
'class' => HTMLTextField::class,
'label-message' => 'centralnotice-change-summary-label',
'placeholder-message' => 'centralnotice-change-summary-action-prompt',
'disabled' => !$this->editable,
'filter-callback' => [ $this, 'truncateSummaryField' ]
];
$formDescriptor[ 'action' ] = [
'section' => 'form-actions',
'type' => 'hidden',
// The default is to save for historical reasons. TODO: review.
'default' => 'save',
];
return $formDescriptor;
}
/**
* Generate a string with the HTML for controls to request a front-end (CDN) cache
* purge of banner content for a language.
*
* @return string
*/
private function generateCdnPurgeSection() {
$purgeControls = Html::element( 'h2',
[ 'class' => 'cn-special-section' ],
$this->msg( 'centralnotice-banner-cdn-controls' )->text() );
$purgeControls .= Html::openElement( 'fieldset', [ 'class' => 'prefsection' ] );
$purgeControls .= Html::openElement( 'label' );
$purgeControls .=
$this->msg( 'centralnotice-banner-cdn-label' )->escaped() . ' ';
$disabledAttr = $this->editable ? [] : [ 'disabled' => true ];
$purgeControls .= Html::openElement( 'select',
$disabledAttr + [ 'id' => 'cn-cdn-cache-language' ] );
// Retrieve the list of languages in user's language
// FIXME Similar code in SpecialBannerAllocation::execute(), maybe switch
// to language selector?
$languages = MediaWikiServices::getInstance()->getLanguageNameUtils()
->getLanguageNames( $this->getLanguage()->getCode() );
ksort( $languages );
foreach ( $languages as $code => $name ) {
$purgeControls .= Xml::option(
$this->msg( 'centralnotice-language-listing', $code, $name )->text(),
$code );
}
$purgeControls .= Html::closeElement( 'select' );
$purgeControls .= Html::closeElement( 'label' );
$purgeControls .= ' ' . Html::element( 'button',
$disabledAttr + [ 'id' => 'cn-cdn-cache-purge' ],
$this->msg( 'centralnotice-banner-cdn-button' )->text()
);
$purgeControls .= Html::element(
'div',
[ 'class' => 'htmlform-help' ],
$this->msg( 'centralnotice-banner-cdn-help' )->text()
);
$purgeControls .= Html::closeElement( 'fieldset' );
return $purgeControls;
}
public function processEditBanner( $formData ) {
// First things first! Figure out what the heck we're actually doing!
switch ( $formData[ 'action' ] ) {
case 'update-lang':
$newLanguage = $formData[ 'translate-language' ];
$this->setCNSessionVar( 'bannerLanguagePreview', $newLanguage );
$this->bannerLanguagePreview = $newLanguage;
break;
case 'delete':
if ( !$this->editable ) {
return null;
}
if ( !Banner::isValidBannerName( $this->bannerName ) ) {
throw new ErrorPageError( 'noticetemplate', 'centralnotice-banner-name-error' );
}
try {
Banner::removeBanner(
$this->bannerName, $this->getUser(),
$formData[ 'deleteEditSummary' ] );
$this->getOutput()->redirect( $this->getPageTitle( '' )->getCanonicalURL() );
$this->bannerFormRedirectRequired = true;
} catch ( Exception $ex ) {
return htmlspecialchars( $ex->getMessage() ) . " <br /> " .
$this->msg( 'centralnotice-template-still-bound', $this->bannerName )->parse();
}
break;
case 'archive':
if ( !$this->editable ) {
return null;
}
return 'Archiving currently does not work';
case 'clone':
if ( !$this->editable ) {
return null;
}
$newBannerName = $formData[ 'cloneName' ];
if ( !Banner::isValidBannerName( $newBannerName ) ) {
throw new ErrorPageError( 'noticetemplate', 'centralnotice-banner-name-error' );
}
$this->ensureBanner( $this->bannerName );
$this->banner->cloneBanner(
$newBannerName, $this->getUser(),
$formData[ 'cloneEditSummary' ]
);
$this->getOutput()->redirect(
$this->getPageTitle( "Edit/$newBannerName" )->getCanonicalURL()
);
$this->bannerFormRedirectRequired = true;
break;
case 'save':
if ( !$this->editable ) {
return null;
}
$ret = $this->processSaveBannerAction( $formData );
// Clear the edit summary field in the request so the form
// doesn't re-display the same value. Note: this is a hack :(
$this->getRequest()->setVal( 'wpsummary', '' );
return $ret;
default:
// Nothing was requested, so do nothing
break;
}
}
private function processSaveBannerAction( $formData ) {
global $wgNoticeUseTranslateExtension, $wgLanguageCode;
$this->ensureBanner( $this->bannerName );
$summary = $formData['summary'];
/* --- Update the translations --- */
// But only if we aren't using translate or if the preview language is the content language
if ( !$wgNoticeUseTranslateExtension || $this->bannerLanguagePreview === $wgLanguageCode ) {
foreach ( $formData as $key => $value ) {
if ( str_starts_with( $key, 'message-' ) ) {
$messageName = substr( $key, strlen( 'message-' ) );
$bannerMessage = $this->banner->getMessageField( $messageName );
$bannerMessage->update(
$value, $this->bannerLanguagePreview, $this->getUser(),
$summary );
}
}
}
/* --- Banner settings --- */
if ( array_key_exists( 'priority-langs', $formData ) ) {
$prioLang = $formData[ 'priority-langs' ];
if ( !is_array( $prioLang ) ) {
$prioLang = [ $prioLang ];
}
} else {
$prioLang = [];
}
$this->banner->setAllocation(
in_array( 'anonymous', $formData[ 'display-to' ] ),
in_array( 'registered', $formData[ 'display-to' ] )
);
$this->banner->setCategory( $formData[ 'banner-class' ] );
$this->banner->setDevices( $formData[ 'device-classes' ] );
$this->banner->setPriorityLanguages( $prioLang );
$this->banner->setBodyContent( $formData[ 'banner-body' ] );
$this->banner->setMixins( $formData['mixins'] );
$this->banner->setIsTemplate( (bool)$formData['banner-is-template'] );
$this->banner->save( $this->getUser(), $summary );
// Deferred update to purge CDN caches for banner content (for user's lang)
DeferredUpdates::addUpdate(
new CdnCacheUpdateBannerLoader( $this->getLanguage()->getCode(), $this->banner ),
DeferredUpdates::POSTSEND
);
return null;
}
/**
* Returns all template banner names for dropdown
*
* @return array
*/
private function getTemplateBannerDropdownItems() {
if ( $this->templateBannerNames === null ) {
$dbr = CNDatabase::getDb();
$this->templateBannerNames = [];
$res = $dbr->newSelectQueryBuilder()
->select( [ 'tmp_name' ] )
->from( 'cn_templates' )
->where( [ 'tmp_is_template' => true ] )
->caller( __METHOD__ )
->fetchResultSet();
foreach ( $res as $row ) {
// name of the banner as a key for HTMLSelectField
$this->templateBannerNames[$row->tmp_name] = $row->tmp_name;
}
}
return $this->templateBannerNames;
}
}

File Metadata

Mime Type
text/x-php
Expires
Aug 19 2026, 18:57 (6 w, 4 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
49/74/46adfb61f55715496ce06873b13f
Default Alt Text
SpecialCentralNoticeBanners.php (33 KB)

Event Timeline