AndyRussG has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/371125 )
Change subject: [WIP, pls. don't merge] Clone campaign feature ...................................................................... [WIP, pls. don't merge] Clone campaign feature Change-Id: If51004f3027d6d4e1964d4f1e04244fda883a375 --- M extension.json M i18n/en.json M i18n/qqq.json M includes/Campaign.php M resources/infrastructure/bannereditor.js M resources/infrastructure/campaignManager.js M special/SpecialCentralNotice.php 7 files changed, 301 insertions(+), 13 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/CentralNotice refs/changes/25/371125/1 diff --git a/extension.json b/extension.json index 6772ca7..4f975cd 100644 --- a/extension.json +++ b/extension.json @@ -169,7 +169,7 @@ "styles": "infrastructure/bannereditor.css", "messages": [ "centralnotice-clone", - "centralnotice-clone-notice", + "centralnotice-clone-banner-dialog", "centralnotice-clone-cancel", "centralnotice-archive-banner", "centralnotice-archive-banner-title", @@ -196,7 +196,9 @@ "jquery.ui.slider", "jquery.throttle-debounce", "mediawiki.template", - "mediawiki.template.mustache" + "mediawiki.template.mustache", + "mediawiki.Uri", + "mediawiki.user" ], "scripts": "resources/infrastructure/campaignManager.js", "styles": "resources/infrastructure/campaignManager.css", @@ -204,6 +206,11 @@ "campaignMixinParamControls.mustache": "templates/campaignMixinParamControls.mustache" }, "messages": [ + "centralnotice-clone-notice-dialog", + "centralnotice-clone-notice-name", + "centralnotice-clone-notice-button", + "centralnotice-clone-notice-cancel-button", + "centralnotice-change-summary-action-prompt", "centralnotice-notice-mixins-int-required", "centralnotice-notice-mixins-float-required", "centralnotice-banner-history-logger-rate", diff --git a/i18n/en.json b/i18n/en.json index ba086ce..69240fa 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -25,6 +25,11 @@ "centralnotice-translate-heading": "Translation for $1", "centralnotice-manage": "Manage campaigns", "centralnotice-manage-templates": "Manage banners", + "centralnotice-save-notice-button": "Save", + "centralnotice-clone-notice-button": "Clone", + "centralnotice-clone-notice-cancel-button": "Cancel", + "centralnotice-clone-notice-dialog": "Clone campaign", + "centralnotice-clone-notice-name": "New campaign name", "centralnotice-add": "Add", "centralnotice-add-notice": "Add a campaign", "centralnotice-add-notice-button": "Create", @@ -96,7 +101,7 @@ "centralnotice-message-not-set": "Message not set", "centralnotice-campaigns-using-banner": "Campaigns using this banner", "centralnotice-clone": "Clone", - "centralnotice-clone-notice": "Create a copy of the banner", + "centralnotice-clone-banner-dialog": "Create a copy of the banner", "centralnotice-clone-name": "Name:", "centralnotice-clone-cancel": "Cancel", "centralnotice-clone-banner": "Banner", diff --git a/i18n/qqq.json b/i18n/qqq.json index cd501ce..4b8eeeb 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -50,6 +50,11 @@ "centralnotice-manage": "Title of sub-page of [[meta:Special:CentralNotice|Manage Central Notice]] special page.", "centralnotice-manage-templates": "Title of sub-page of [[meta:Special:NoticeTemplate|Manage Banners]] special page.", "centralnotice-add": "Used in [[mw:Extension:CentralNotice]]\n{{Identical|Add}}", + "centralnotice-save-notice-button": "Text for button to save campaign settings", + "centralnotice-clone-notice-button": "Text for button to clone (create a duplicate of) a campaign. Appears both at the end of the campaign edit form, and in the modal dialog.", + "centralnotice-clone-notice-cancel-button": "Text for button to cancel cloning a campaign, in modal dialog.", + "centralnotice-clone-notice-dialog": "Clone campaign", + "centralnotice-clone-notice-name": "New campaign name", "centralnotice-add-notice": "Used as heading (<nowiki><h2></nowiki> tag)", "centralnotice-add-notice-button": "Used as label for the Submit button in the \"Create banner\" dialog.\n{{Identical|Create}}", "centralnotice-add-notice-cancel-button": "{{Identical|Cancel}}", @@ -120,8 +125,8 @@ "centralnotice-message-not-set": "Used if the native text of the banner message is not set, in [[Special:NoticeTemplate]].", "centralnotice-campaigns-using-banner": "On the banner edit page, title of subsection showing campaigns associated with the banner.", "centralnotice-clone": "Used as label for the Submit button in [[Special:NoticeTemplate]].\n\nPreceded by the \"Name\" input box with the label {{msg-mw|Centralnotice-clone-name}}.\n{{Identical|Clone}}", - "centralnotice-clone-notice": "Used as fieldset label in the form in [[Special:NoticeTemplate]].", - "centralnotice-clone-name": "Used as label for the \"Name\" input box in [[Special:NoticeTemplate]].\n\nPreceded by the fieldset label {{msg-mw|centralnotice-clone-notice}}.\n\nThe input box is followed by the Submit button. Its label is {{msg-mw|centralnotice-clone}}.\n{{Identical|Name}}", + "centralnotice-clone-banner-dialog": "Used as heading for modal dialog to clone a banner. (Renamed from centralnotice-clone-notice, and documentation updated, but same usage as before.)", + "centralnotice-clone-name": "Used as label for the \"Name\" input box for a banner name", "centralnotice-clone-cancel": "{{Identical|Cancel}}", "centralnotice-clone-banner": "Used as label for the banner in the form in [[Special:NoticeTemplate]].\n{{Identical|Banner}}", "centralnotice-preview-all-template-translations": "Link to display all approved (currently available to use) translations for a banner.", diff --git a/includes/Campaign.php b/includes/Campaign.php index 38f4f9e..17f9aed 100644 --- a/includes/Campaign.php +++ b/includes/Campaign.php @@ -926,6 +926,91 @@ } /** + * Create a complete copy of a campaign + * + * @param string $existingCampaignName + * @param string $newCampaignName + * @param User $user + * @param string $summary + * @return string|boolean + */ + static function cloneCampaign( + $existingCampaignName, + $newCampaignName, + $user, + $summary + ) { + // FIXME Validation!!! + $existingCampaignName = trim( $existingCampaignName ); + if ( !self::campaignExists( $existingCampaignName ) ) { + return 'centralnotice-notice-doesnt-exist'; + } + + $settings = self::getCampaignSettings( $existingCampaignName ); + + // FIXME Validation!!! + $newCampaignName = trim( $newCampaignName ); + if ( self::campaignExists( $newCampaignName ) ) { + return 'centralnotice-notice-exists'; + } + + // FIXME Error handling + + $newCampaign = self::addCampaign( + $newCampaignName, + $settings[ 'enabled' ], + $settings[ 'start' ], + $settings[ 'projects' ], + $settings[ 'languages' ], + $settings[ 'geo' ], + $settings[ 'countries' ], + $settings[ 'throttle' ], + $settings[ 'preferred' ], + $user, + $summary + ); + + Campaign::setBooleanCampaignSetting( + $newCampaignName, 'locked', $settings[ 'locked' ] + ); + + Campaign::setNumericCampaignSetting( + $newCampaignName, + 'buckets', + $settings[ 'buckets' ] + ); + + Campaign::updateNoticeDate( + $newCampaignName, + $settings[ 'start' ], + $settings[ 'end' ] + ); + + // TODO JSON error handling? Maybe not needed? (See also below.) + $banners = FormatJson::decode( $settings[ 'banners'] ); + foreach ( $banners as $bannerName => $bannerData ) { + Campaign::addTemplateTo( + $newCampaignName, + $bannerName, + $bannerData[ 'weight' ], + $bannerData[ 'bucket' ] + ); + } + + $mixins = FormatJson::decode( $settings[ 'mixins' ] ); + foreach ( $mixin as $mixinName => $mixinData ) { + Campaign::updateCampaignMixins( + $newCampaignName, + $mixinName, + true, + $mixinData + ); + } + + return true; + } + + /** * Remove a campaign from the database * * @param $campaignName string: Name of the campaign diff --git a/resources/infrastructure/bannereditor.js b/resources/infrastructure/bannereditor.js index 1199c07..bc08eb4 100644 --- a/resources/infrastructure/bannereditor.js +++ b/resources/infrastructure/bannereditor.js @@ -119,7 +119,7 @@ dialogObj[ 0 ].name = 'addBannerDialog'; dialogObj.append( $( '#cn-formsection-clone-banner' ).children( 'div' ).clone().show() ) .dialog( { - title: mw.message( 'centralnotice-clone-notice' ).text(), + title: mw.message( 'centralnotice-clone-banner-dialog' ).text(), modal: true, buttons: buttons, width: 'auto' diff --git a/resources/infrastructure/campaignManager.js b/resources/infrastructure/campaignManager.js index afd6787..e75baf2 100644 --- a/resources/infrastructure/campaignManager.js +++ b/resources/infrastructure/campaignManager.js @@ -30,6 +30,7 @@ $form, $submitBtn, MixinCustomUiController, MixinCustomWidget, ErrorStateTracker, + CloneCampaignDialog, cloneCampaignDialog, windowManager, mixinCustomUiControllerFactory = new OO.Factory(), errorStateTracker, eventBus, assignedBanners, BUCKET_LABELS = [ 'A', 'B', 'C', 'D' ]; // TODO Fix for configs with more buckets @@ -270,6 +271,133 @@ $submitBtn.prop( 'disabled', false ); } } ); + + // Class and handler for clone campaign button and dialog + CloneCampaignDialog = function() { + CloneCampaignDialog.super.call( this, {} ); + } + + OO.inheritClass( CloneCampaignDialog, OO.ui.ProcessDialog ); + CloneCampaignDialog.static.name = 'cloneCampaign'; + + CloneCampaignDialog.static.title = + mw.message( 'centralnotice-clone-notice-dialog' ).text(); + + CloneCampaignDialog.static.actions = [ + { + label: mw.message( 'centralnotice-clone-notice-cancel-button' ).text(), + flags: 'safe' + }, + { + label: mw.message( 'centralnotice-clone-notice-button' ).text(), + flags: 'primary', + action: 'clone' + } + ]; + + CloneCampaignDialog.prototype.initialize = function () { + var panel, fieldSetLayout; + + CloneCampaignDialog.super.prototype.initialize.apply( this, arguments ); + + // Text input widgets + this.newNameTextInput = new OO.ui.TextInputWidget(); // FIXME Validation + this.summaryTextInput = new OO.ui.TextInputWidget( { + placeholder: mw.message( 'centralnotice-change-summary-action-prompt' ).text() + } ); + + // Attach widgets via a FieldSetLayout + fieldSetLayout = new OO.ui.FieldsetLayout(); + fieldSetLayout.addItems( [ + new OO.ui.FieldLayout( + this.newNameTextInput, + { label: mw.message( 'centralnotice-clone-notice-name' ).text() } + ), + new OO.ui.FieldLayout( + this.summaryTextInput, + { label: 'Summary' } + ), + ] ); + + // Attach the FieldSetLayout to the dialog via a panel + panel = new OO.ui.PanelLayout( { padded: true, expanded: false } ); + panel.$element.append( fieldSetLayout.$element ); + this.$body.append( panel.$element ); + }; + + CloneCampaignDialog.prototype.getSetupProcess = function ( data ) { + data = data || {}; + return CloneCampaignDialog.super.prototype.getSetupProcess.call( this, data ) + .next( function () { + // Set up contents based on data + this.summaryTextInput.setValue( $( 'input[name=changeSummary]' ).val() ); + }, this ); + }; + + CloneCampaignDialog.prototype.getActionProcess = function ( action ) { + // Handle the 'clone' action + if ( action === 'clone' ) { + return new OO.ui.Process( function () { + this.close( { + newCampaignName: this.newNameTextInput.getValue(), + summary: this.summaryTextInput.getValue() + } ); + }, this ); + } + + // Fallback to parent handler. (Super handles escape keypress and cancel button.) + return CloneCampaignDialog.super.prototype.getActionProcess.call( this, action ); + }; + + function onClickCloneCampaign() { + // Ensure window manager and dialog instance + if ( !windowManager ) { + windowManager = new OO.ui.WindowManager(); + $( 'body' ).append( windowManager.$element ); + cloneCampaignDialog = new CloneCampaignDialog(); + windowManager.addWindows( [ cloneCampaignDialog ] ); + } + + windowManager.openWindow( cloneCampaignDialog ).closed.then( function ( data ) { + var $form = $( '<form />' ), + url = new mw.Uri(); + + // Cancel button or escape key pressed + if ( !data ) { + return; + } + + // It should be fine to leave other fields on the URL alone. The name of the + // campaign to be cloned should already be on the 'notice' parameter. + // (We'll add the new campaign name and user token as form fields.) + url.query.subaction = 'cloneCampaign'; + + $form.attr( 'action', url.toString() ) + .attr( 'method', 'POST' ) + .css( 'display', 'none'); + + $form.append( $( '<input />' ) + .attr( 'type', 'hidden' ) + .attr( 'name', 'newCampaignName' ) + .attr( 'value', data.newCampaignName ) + ); + + $form.append( $( '<input />' ) + .attr( 'type', 'hidden' ) + .attr( 'name', 'summary' ) + .attr( 'value', data.summary ) + ); + + $form.append( $( '<input />' ) + .attr( 'type', 'hidden' ) + .attr( 'name', 'authtoken' ) + .attr( 'value', mw.user.tokens.get( 'editToken' ) ) + ); + + $( 'body' ).append( $form ); + $form.submit(); + } ); + } function updateThrottle() { if ( $( '#throttle-enabled' ).prop( 'checked' ) ) { @@ -749,9 +877,12 @@ $( '#throttle-enabled' ).click( updateThrottle ); $( '#balanced' ).click( updateWeightColumn ); $( 'select#buckets' ).change( updateBuckets ); + $( '.bucketSelectorForAssignedBanners, .bannerRemoveCheckbox' ) .change( updateAssignedBanners ); + $( '#centralnotice-clone-notice' ).click( onClickCloneCampaign ); + $mixinCheckboxes.each( showOrHideCampaignMixinControls ); $mixinCheckboxes.change( showOrHideCampaignMixinControls ); } diff --git a/special/SpecialCentralNotice.php b/special/SpecialCentralNotice.php index 79b9039..4f186d7 100644 --- a/special/SpecialCentralNotice.php +++ b/special/SpecialCentralNotice.php @@ -58,7 +58,15 @@ return; } + // Handle clone campaign post. (If this wasn't posted, we'll just fall through and + // show the default list of notices.) + if ( $subaction === 'cloneCampaign' && $request->wasPosted() ) { + $this->handleCloneCampaignPost(); + return; + } + // Handle form submissions from "Manage campaigns" or "Add a campaign" interface + // (both shown on the same page). if ( $this->editable && $request->wasPosted() ) { if ( wfReadOnly() || CNDatabase::getDb( DB_MASTER )->isReadOnly() ) { throw new ReadOnlyError(); @@ -71,6 +79,8 @@ // list view. if ( $subaction === 'addCampaign' ) { $this->handleAddCampaignPost(); + } else if ( $subaction === 'cloneCampaign' ) { + $this->handleCloneCampaignPost(); } else { $this->handleNoticePostFromList(); } @@ -440,6 +450,43 @@ } } + protected function handleCloneCampaignPost() { + if ( !$this->editable ) { + throw new PermissionsError( 'centralnotice-admin' ); + } + + $request = $this->getRequest(); + + if ( !$this->getUser()->matchEditToken( $request->getVal( 'authtoken' ) ) ) { + throw new ErrorPageError( 'centralnotice', 'sessionfailure' ); + } + + // FIXME Validation + $newCampaignName = $request->getText( 'newCampaignName' ); + if ( $newCampaignName === '' ) { + throw new ErrorPageError( 'centralnotice', 'centralnotice-null-string' ); + } + + $existingCampaignName = $request->getVal( 'notice' ); + + // FIXME Validation + $summary = $request->getVal( 'summary' ); + + // This will return a message key for an error if $existingCampaignName is empty + $result = Campaign::cloneCampaign( + $existingCampaignName, + $newCampaignName, + $this->getUser(), + $summary + ); + + if ( is_string( $result ) ) { + throw new ErrorPageError( 'centralnotice', $result ); + } + + // TODO Redirect to new campaign + } + /** * Retrieve jquery.ui.datepicker date and homebrew time, * and return as a MW timestamp string. @@ -545,19 +592,27 @@ $htmlOut .= $output_templates; } } + + // Submit and clone buttons if ( $this->editable ) { $htmlOut .= Html::hidden( 'authtoken', $this->getUser()->getEditToken() ); $htmlOut .= $this->makeSummaryField(); - // Submit button - $htmlOut .= Xml::tags( 'div', - [ 'class' => 'cn-buttons' ], - Xml::submitButton( - $this->msg( 'centralnotice-modify' )->text(), - [ 'id' => 'noticeDetailSubmit' ] - ) + $htmlOut .= Xml::openElement('div', [ 'class' => 'cn-buttons' ] ); + + $htmlOut .= Xml::element( 'input', [ + 'type' => 'button', + 'id' => 'centralnotice-clone-notice', + 'value' => $this->msg( 'centralnotice-clone-notice-button' )->text() + ] ); + + $htmlOut .= Xml::submitButton( + $this->msg( 'centralnotice-save-notice-button' )->text(), + [ 'id' => 'noticeDetailSubmit' ] ); + + $htmlOut .= Xml::closeElement('div' ); } if ( $this->editable ) { -- To view, visit https://gerrit.wikimedia.org/r/371125 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: If51004f3027d6d4e1964d4f1e04244fda883a375 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/CentralNotice Gerrit-Branch: master Gerrit-Owner: AndyRussG <andrew.green...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits