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

Reply via email to