jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/393072 )

Change subject: Added a oojs ui for managing translations
......................................................................


Added a oojs ui for managing translations

* added oojs ui window
* improved / fixed error handling
* added / fixed some i18n keys

Change-Id: I3da5e718989718d9f67164b2dfcd50ad25ffd441
---
M extension.json
M i18n/en.json
M i18n/qqq.json
A resources/ext.mlm.js
A src/Api/Tasks.php
M src/Helper.php
M src/Hooks/BeforePageDisplay.php
M src/MultiLanguageTranslation.php
M src/Specials/MultiLanguageManager.php
9 files changed, 826 insertions(+), 49 deletions(-)

Approvals:
  Robert Vogel: Looks good to me, approved
  Raimond Spekking: Looks good to me, but someone else must approve
  jenkins-bot: Verified



diff --git a/extension.json b/extension.json
index 9c6259b..d18530b 100644
--- a/extension.json
+++ b/extension.json
@@ -47,6 +47,9 @@
        "SpecialPages": {
                "MultiLanguageManager": 
"MultiLanguageManager\\Specials\\MultiLanguageManager"
        },
+       "APIModules": {
+               "mlm-tasks": "MultiLanguageManager\\Api\\Tasks"
+       },
        "ResourceFileModulePaths": {
                "localBasePath": "resources",
                "remoteExtPath": "MultiLanguageManager/resources"
@@ -56,6 +59,25 @@
                        "styles": [
                                "ext.mlm.less"
                        ]
+               },
+               "ext.mlm": {
+                       "scripts": [
+                               "ext.mlm.js"
+                       ],
+                       "dependencies": [
+                               "mediawiki.api",
+                               "oojs",
+                               "oojs-ui"
+                       ],
+                       "messages": [
+                               "mlm-input-label-sourcetitle",
+                               "mlm-input-label-translationtitles",
+                               "mlm-input-label-add",
+                               "mlm-input-label-save",
+                               "mlm-input-label-delete",
+                               "mlm-input-label-cancel",
+                               "mlm-contentaction-label"
+                       ]
                }
        },
        "config_prefix": "mg",
diff --git a/i18n/en.json b/i18n/en.json
index dc7cab7..26546bd 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -7,18 +7,23 @@
        "multilanguagemanager": "Multi language manager",
        "mlm-desc": "Mediawiki extension to manage multi language in one 
MediaWiki.",
        "mlm-error-title-invalid": "Invalid page name",
-       "mlm-error-title-notexists": "Page does not exist",
-       "mlm-error-title-istalkpage": "Talk pages can not be translated",
-       "mlm-error-title-nsnotallowed": "Invalid namespace for translation",
-       "mlm-error-title-isalreadytranslation": "Page is already a translation",
-       "mlm-error-title-isalreadysource": "Page is already a source",
-       "mlm-error-title-isnottranslation": "Page is not a translation",
+       "mlm-error-title-notexists": "Page \"$1\" does not exist",
+       "mlm-error-title-istalkpage": "\"$1\". Talk pages can not be 
translated",
+       "mlm-error-title-nsnotallowed": "\"$1\". Invalid namespace for 
translation",
+       "mlm-error-title-isalreadytranslation": "Page \"$1\" is already a 
translation",
+       "mlm-error-title-isalreadysource": "Page \"$1\" is already a source",
+       "mlm-error-title-isnottranslation": "Page \"$1\" is not a translation",
        "mlm-error-lang-invalid": "Invalid language",
-       "mlm-error-lang-notallowed": "Language is not allowed for translation",
-       "mlm-error-lang-alreadytraslated": "There is already a translation for 
language '$1'",
+       "mlm-error-lang-notallowed": "Language \"$1\" is not allowed for 
translation",
+       "mlm-error-lang-alreadytraslated": "There is already a translation for 
language \"$1\"",
        "mlm-input-label-sourcetitle": "Source page",
        "mlm-input-label-translationtitles": "Translation 
{{plural:$1|page|pages}}",
+       "mlm-input-label-add": "Add",
        "mlm-input-label-save": "Save",
        "mlm-input-label-delete": "Delete",
-       "mlm-contentaction-label": "Manage translations"
+       "mlm-input-label-cancel": "Cancel",
+       "mlm-contentaction-label": "Manage translations",
+       "apihelp-mlm-param-task": "The task that should be executed",
+       "apihelp-mlm-param-taskdata": "JSON string encoded object with 
arbitrary data for the task",
+       "apihelp-mlm-param-format": "The format of the result"
 }
\ No newline at end of file
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 771165b..8b8ddd7 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -9,18 +9,23 @@
        "multilanguagemanager": "Title of the special page",
        "mlm-desc": 
"{{desc|name=MultiLanguageManager|url=https://www.mediawiki.org/wiki/Extension:MultiLanguageManager}}";,
        "mlm-error-title-invalid": "Error message in case that a invalid title 
was provided",
-       "mlm-error-title-notexists": "Error message in case that a provided 
title does not exist",
-       "mlm-error-title-istalkpage": "Error message in case that a provided 
title is a talk page",
-       "mlm-error-title-nsnotallowed": "Error message in case that a provided 
title is in a namespace that has no translation enabled",
-       "mlm-error-title-isalreadytranslation": "Error message in case that a 
title already has a translation",
-       "mlm-error-title-isalreadysource": "Error message in case that a title 
is already declared to be the source of a translation",
-       "mlm-error-title-isnottranslation": "Error message in case that a title 
is no translation page",
-       "mlm-error-lang-invalid": "Error message in case that a invalis 
language code was provided",
-       "mlm-error-lang-notallowed": "Error message in case that a specific 
language code is not allowed by configuration",
+       "mlm-error-title-notexists": "Error message in case that a provided 
title does not exist\n*Paramter $1 - Full page name",
+       "mlm-error-title-istalkpage": "Error message in case that a provided 
title is a talk page\n*Paramter $1 - Full page name",
+       "mlm-error-title-nsnotallowed": "Error message in case that a provided 
title is in a namespace that has no translation enabled\n*Paramter $1 - Full 
page name",
+       "mlm-error-title-isalreadytranslation": "Error message in case that a 
title already has a translation\n*Paramter $1 - Full page name",
+       "mlm-error-title-isalreadysource": "Error message in case that a title 
is already declared to be the source of a translation\n*Paramter $1 - Full page 
name",
+       "mlm-error-title-isnottranslation": "Error message in case that a title 
is no translation page\n*Paramter $1 - Full page name",
+       "mlm-error-lang-invalid": "Error message in case that a invalid 
language code was provided",
+       "mlm-error-lang-notallowed": "Error message in case that a specific 
language code is not allowed by configuration\n*Paramter $1 - The language 
code",
        "mlm-error-lang-alreadytraslated": "Error message in case that there is 
already a translation for a specific language code\n*Paramter $1 - The language 
code",
        "mlm-input-label-sourcetitle": "Label for a textfield that allows 
specification of the source title",
        "mlm-input-label-translationtitles": "Label of a section that lists 
translations of an article.\n*Paramter $1 - Number of available translations",
+       "mlm-input-label-add": "Label for a add button\n{{Identical|Add}}",
        "mlm-input-label-save": "Label for a save button\n{{Identical|Save}}",
        "mlm-input-label-delete": "Label for a delete 
button\n{{Identical|Delete}}",
-       "mlm-contentaction-label": "Lable of a content action link that opens 
Special:MultiLanguageManager"
+       "mlm-input-label-cancel": "Label for a delete 
button\n{{Identical|Cancel}}",
+       "mlm-contentaction-label": "Label of a content action link that opens 
Special:MultiLanguageManager",
+       "apihelp-mlm-param-task": "{{doc-apihelp-param|mlm-tasks|task}}",
+       "apihelp-mlm-param-taskdata": 
"{{doc-apihelp-param|mlm-tasks|taskdata}}",
+       "apihelp-mlm-param-format": "{{doc-apihelp-param|mlm-tasks|format}}"
 }
diff --git a/resources/ext.mlm.js b/resources/ext.mlm.js
new file mode 100644
index 0000000..7dca9fb
--- /dev/null
+++ b/resources/ext.mlm.js
@@ -0,0 +1,348 @@
+
+( function( mw, $ ){
+       mw.mlm = mw.mlm || {};
+       $(document).on( 'click', '#ca-mlm', function( e ) {
+               if( !mw.mlm.dialog ) {
+                       return;
+               }
+               var windowManager = new OO.ui.WindowManager( {
+                       factory: mw.mlm.factory
+               } );
+               $( 'body' ).append( windowManager.$element );
+
+               windowManager.openWindow( 'mlm' );
+               e.stopPropagation();
+               return false;
+       });
+
+       mw.loader.using( 'oojs-ui', function() {
+
+               mw.mlm.factory = new OO.Factory();
+
+               mw.mlm.srcTitle = mw.config.get(
+                       'mlmSourceTitle',
+                       ''
+               );
+               mw.mlm.translations = mw.config.get(
+                       'mlmTranslations',
+                       {}
+               );
+               mw.mlm.languages = mw.config.get(
+                       'mlmLanguages',
+                       []
+               );
+               mw.mlm.languageFlags = mw.config.get(
+                       'mlmLanguageFlags',
+                       {}
+               );
+               var lang =  mw.config.get( 'wgContentLanguage' ).split('-');
+               mw.mlm.lang = lang[0];
+
+               mw.mlm.dialog = function( config ) {
+                       this.translations = {};
+                       mw.mlm.dialog.super.call( this, config );
+               };
+               OO.inheritClass( mw.mlm.dialog, OO.ui.ProcessDialog );
+               OO.initClass( mw.mlm.dialog );
+
+               // Specify a symbolic name (e.g., 'simple', in this example) 
using the static 'name' property.
+               mw.mlm.dialog.static.name = 'mlm';
+               mw.mlm.dialog.static.title = mw.message(
+                       'mlm-contentaction-label'
+               ).plain();
+               mw.mlm.dialog.static.actions = [{
+                       action: 'save',
+                       label: mw.message( 'mlm-input-label-save' ).plain(),
+                       flags: [ 'primary', 'constructive' ]
+               }, {
+                       action: 'cancel',
+                       label: mw.message( 'mlm-input-label-cancel' ).plain(),
+                       flags: 'safe'
+               }, {
+                       action: 'delete',
+                       label: mw.message( 'mlm-input-label-delete' ).plain(),
+                       flags: 'destructive'
+               }];
+
+               mw.mlm.dialog.prototype.initialize = function () {
+                       mw.mlm.dialog.super.prototype.initialize.call( this );
+
+                       this.panel = new OO.ui.PanelLayout( { padded: true, 
expanded: false } );
+                       this.content = new OO.ui.FieldsetLayout();
+                       this.errorSection = new OO.ui.Layout();
+                       this.errorSection.$element.css( 'color', 'red' );
+                       this.errorSection.$element.css( 'font-weight', 'bold' );
+                       this.errorSection.$element.css( 'text-align', 'center' 
);
+
+                       var options = [];
+                       for( var i = 0; i < mw.mlm.languages.length; i++ ) {
+                               if( mw.mlm.languages[i] === mw.mlm.lang ) {
+                                       continue;
+                               }
+                               options.push( {
+                                       data: mw.mlm.languages[i],
+                                       label: mw.mlm.languages[i]
+                               });
+                       }
+
+                       this.srcLang = new OO.ui.ButtonWidget( {disabled: true} 
);
+                       this.srcLang.$element.find('a').css(
+                               'background',
+                               'url(' + mw.mlm.languageFlags[mw.mlm.lang] + ')'
+                       );
+                       this.srcLang.$element.find('a').css(
+                               'background-size',
+                               '40px 30px'
+                       );
+                       this.srcLang.$element.find('a').css(
+                               'background-repeat',
+                               'no-repeat'
+                       );
+                       this.srcLang.$element.find('a').css(
+                               'width',
+                               '40px'
+                       );
+                       this.srcText = new OO.ui.TextInputWidget( {
+                               value: mw.mlm.srcTitle,
+                               required: true,
+                               label: mw.message( 
'mlm-input-label-sourcetitle' ).plain(),
+                               disabled: mw.mlm.srcTitle === '' ? false : true
+                       });
+
+                       this.srcSection = new OO.ui.HorizontalLayout( {
+                               items: [
+                                       this.srcLang,
+                                       this.srcText
+                               ]
+                       });
+                       this.srcSection.$element.css( 'display', 'flex' );
+
+                       this.translationsSection = new OO.ui.FieldsetLayout();
+                       for( var i = 0; i < mw.mlm.translations.length; i++ ) {
+                               this.updateTranslations( mw.mlm.translations[i] 
);
+                       }
+
+                       this.translationLang = new OO.ui.DropdownInputWidget( {
+                               value: '',
+                               options: options,
+                               label: 'lang'
+                       });
+                       this.translationText = new OO.ui.TextInputWidget( {
+                               value: mw.mlm.srcTitle === '' ? mw.config.get( 
'wgTitle' ) : '',
+                               label: mw.message(
+                                       'mlm-input-label-translationtitles',
+                                       1
+                               ).text()
+                       });
+                       this.translationAdd = new OO.ui.ButtonWidget( {
+                               label: mw.message( 'mlm-input-label-add' 
).plain(),
+                               flags: [ 'primary', 'constructive' ]
+                       });
+
+                       var me = this;
+                       this.translationAdd.on( 'click', 
me.onTranslationAdd.bind( this ) );
+
+                       this.addSection = new OO.ui.FieldsetLayout( {
+                               items: [
+                                       this.translationLang,
+                                       this.translationText,
+                                       this.translationAdd
+                               ]
+                       });
+
+                       this.content.addItems([
+                               this.errorSection,
+                               this.srcSection,
+                               this.addSection,
+                               this.translationsSection
+                       ]);
+
+                       this.panel.$element.append( this.content.$element );
+                       this.$body.append( this.panel.$element );
+               };
+
+               mw.mlm.dialog.prototype.save = function() {
+                       var api = new mw.Api();
+                       return api.postWithToken( 'csrf', {
+                               action: 'mlm-tasks',
+                               task: 'save',
+                               format: 'json',
+                               taskData: JSON.stringify( this.getData() )
+                       });
+               };
+
+               mw.mlm.dialog.prototype.delete = function() {
+                       var api = new mw.Api();
+                       return api.postWithToken( 'csrf', {
+                               action: 'mlm-tasks',
+                               task: 'delete',
+                               format: 'json',
+                               taskData: JSON.stringify( this.getData() )
+                       });
+               };
+
+               mw.mlm.dialog.prototype.getData = function() {
+                       var data = {};
+
+                       data.srcText = this.srcText.value;
+                       data.translations = {};
+                       for( var i in this.translations ) {
+                               var translation = this.translations[i];
+                               data.translations[i] = {
+                                       lang: i,
+                                       text: translation.input.value
+                               };
+                       }
+                       return data;
+               };
+
+               mw.mlm.dialog.prototype.getActionProcess = function ( action ) {
+                       return 
mw.mlm.dialog.super.prototype.getActionProcess.call( this, action )
+                       .next( function () {
+                               return 1000;
+                       }, this )
+                       .next( function () {
+                               var closing;
+                               if ( action === 'save' ) {
+                                       if ( this.broken ) {
+                                               this.broken = false;
+                                               return new OO.ui.Error( 'Server 
did not respond' );
+                                       }
+                                       var me = this;
+                                       return me.save().done( function( data ) 
{
+                                               //success is just emtyed out 
somewhere for no reason
+                                               if( data.message.length === 0 ) 
{
+                                                       closing = me.close( { 
action: action } );
+                                                       me.reloadPage();
+                                                       return closing;
+                                               }
+                                               me.showRequestErrors( 
data.message );
+                                       });
+                               } else if ( action === 'cancel' ) {
+                                       closing = this.close( { action: action 
} );
+                                       return closing;
+                               }
+                               else if ( action === 'delete' ) {
+                                       var me = this;
+                                       return this.delete().done( function( 
data ) {
+                                               //success is just emtyed out 
somewhere for no reason
+                                               if( data.message.length === 0 ) 
{
+                                                       closing = me.close( { 
action: action } );
+                                                       me.reloadPage();
+                                                       return closing;
+                                               }
+                                               me.showRequestErrors( 
data.message );
+                                       });
+                                       return closing;
+                               }
+
+                               return 
mw.mlm.dialog.super.prototype.getActionProcess.call(
+                                       this,
+                                       action
+                               );
+                       }, this );
+               };
+
+               mw.mlm.dialog.prototype.showRequestErrors = function( errors ) {
+                       var errors = errors || {};
+
+                       var error = '';
+                       for( var i in errors ) {
+                               error += errors[i] + "<br />";
+                       }
+
+                       this.errorSection.$element.html( error );
+               };
+
+               mw.mlm.dialog.prototype.reloadPage = function() {
+                       window.location = mw.util.getUrl(
+                               mw.config.get( 'wgTitle' )
+                       );
+               };
+
+               mw.mlm.dialog.prototype.onTranslationAdd = function(){
+                       this.updateTranslations( {
+                               'lang': this.translationLang.value,
+                               'text': this.translationText.value
+                       });
+               };
+
+               mw.mlm.dialog.prototype.onTranslationDelete = function( lang ){
+                       this.updateTranslations( {
+                               'lang': lang,
+                               'text': ''
+                       }, true);
+               };
+
+               mw.mlm.dialog.prototype.updateTranslations = function ( 
translation, removeOnly ) {
+                       removeOnly = removeOnly || false;
+                       if( mw.mlm.srcTitle === translation.text ) {
+                               return;
+                       }
+                       if( this.translations[translation.lang] ) {
+                               
this.translations[translation.lang].layout.$element.remove();
+                               delete this.translations[translation.lang];
+                       }
+                       if( removeOnly ) {
+                               return;
+                       }
+                       this.translations[translation.lang] = {
+                               'lang': new OO.ui.ButtonWidget( {
+                                       disabled: true,
+                                       title: translation.lang
+                               }),
+                               'input': new OO.ui.TextInputWidget( {
+                                       value: translation.text,
+                                       required: true
+                               }),
+                               'delete': new OO.ui.ButtonWidget( {
+                                       icon: 'trash',
+                                       flags: 'destructive',
+                                       title: mw.message( 
'mlm-input-label-delete' ).plain()
+                               })
+                       };
+
+                       
this.translations[translation.lang].lang.$element.find('a').css(
+                               'background',
+                               'url(' + mw.mlm.languageFlags[translation.lang] 
+ ')'
+                       );
+                       
this.translations[translation.lang].lang.$element.find('a').css(
+                               'background-size',
+                               '40px 30px'
+                       );
+                       
this.translations[translation.lang].lang.$element.find('a').css(
+                               'background-repeat',
+                               'no-repeat'
+                       );
+                       
this.translations[translation.lang].lang.$element.find('a').css(
+                               'width',
+                               '40px'
+                       );
+
+                       this.translations[translation.lang].layout = new 
OO.ui.HorizontalLayout( {
+                               items: [
+                                       
this.translations[translation.lang].lang,
+                                       
this.translations[translation.lang].input,
+                                       
this.translations[translation.lang].delete
+                               ]
+                       });
+                       this.translations[translation.lang].layout.$element.css(
+                               'display',
+                               'flex'
+                       );
+
+                       var me = this;
+                       this.translations[translation.lang].delete.on(
+                               'click',
+                               me.onTranslationDelete.bind( this ),
+                               [translation.lang]
+                       );
+
+                       this.translationsSection.addItems([
+                               this.translations[translation.lang].layout
+                       ]);
+               };
+
+               mw.mlm.factory.register( mw.mlm.dialog );
+       });
+})( mediaWiki, jQuery );
\ No newline at end of file
diff --git a/src/Api/Tasks.php b/src/Api/Tasks.php
new file mode 100644
index 0000000..b2a9c27
--- /dev/null
+++ b/src/Api/Tasks.php
@@ -0,0 +1,295 @@
+<?php
+
+namespace MultiLanguageManager\Api;
+
+use MultiLanguageManager\Helper;
+use MultiLanguageManager\Config;
+use MultiLanguageManager\MultiLanguageTranslation;
+
+class Tasks extends \ApiBase {
+       /**
+        * Returns an array of tasks and their required permissions
+        * array('taskname' => array('read', 'edit'))
+        * @return type
+        */
+       protected function getRequiredTaskPermissions() {
+               return [
+                       'save' => [
+                               'read',
+                               Helper::getConfig()->get( Config::PERMISSION )
+                       ],
+                       'delete' => [
+                               'read',
+                               Helper::getConfig()->get( Config::PERMISSION )
+                       ],
+               ];
+       }
+
+       protected function task_save( $taskData, $params ) {
+               $result = $this->makeStandardReturn();
+               $sysLang = Helper::getSystemLanguageCode();
+
+               //dont use errors param to prevent random unalterable client 
side code
+               $result->message = [];
+               if( empty( $taskData->srcText ) ) {
+                       $taskData->srcText = '';
+               }
+               $oSourceTitle = \Title::newFromText( $taskData->srcText );
+               $status = Helper::isValidTitle(
+                       $oSourceTitle
+               );
+               if( !$status->isOK() ) {
+                       $result->message[$sysLang] = $status->getHTML();
+               }
+
+               if( empty( $taskData->translations ) ) {
+                       $taskData->translations = [];
+               }
+               if( is_object( $taskData->translations ) ) {
+                       $taskData->translations = (array) 
$taskData->translations;
+               }
+               foreach( $taskData->translations as $translation ) {
+                       $status = Helper::isValidTitle(
+                               \Title::newFromText( $translation->text )
+                       );
+                       if( !$status->isOK() ) {
+                               $result->message[$translation->lang] = 
$status->getHTML();
+                       }
+               }
+               if( count( $result->message ) > 0 ) {
+                       return $result;
+               }
+
+               $mlmTranslation = MultiLanguageTranslation::newFromTitle(
+                       $oSourceTitle
+               );
+
+               if( !$mlmTranslation ) {
+                       //very unexpected!
+                       $result->message[$sysLang] = $this->msg(
+                               'mlm-error-title-invalid'
+                       )->plain();
+                       return $result;
+               }
+
+               if( !$mlmTranslation->isSourceTitle( $oSourceTitle ) ) {
+                       $status = $mlmTranslation->setSourceTitle( 
$oSourceTitle );
+                       if( !$status->isOK() ) {
+                               $result->message[$sysLang] = $status->getHTML();
+                               return $result;
+                       }
+               }
+
+               foreach( $mlmTranslation->getTranslations() as $translation ) {
+                       $status = $mlmTranslation->removeTranslation(
+                               \Title::newFromID( $translation->id )
+                       );
+                       if( !$status->isOK() ) {
+                               $result->message[$translation->lang] = 
$status->getHTML();
+                               return $result;
+                       }
+               }
+               foreach( $taskData->translations as $translation ) {
+                       $status = $mlmTranslation->addTranslation(
+                               \Title::newFromText( $translation->text ),
+                               $translation->lang
+                       );
+                       if( !$status->isOK() ) {
+                               $result->message[$translation->lang] = 
$status->getHTML();
+                       }
+               }
+               if( count( $result->message ) > 0 ) {
+                       return $result;
+               }
+
+               $status = $mlmTranslation->save();
+               if( !$status->isOK() ) {
+                       $result->message[$translation->lang] = 
$status->getHTML();
+                       return $result;
+               }
+
+               $result->success = true;
+               return $result;
+       }
+
+       protected function task_delete( $taskData, $params ) {
+               $result = $this->makeStandardReturn();
+               $sysLang = Helper::getSystemLanguageCode();
+
+               if( empty( $taskData->srcText ) ) {
+                       $taskData->srcText = '';
+               }
+               $oSourceTitle = \Title::newFromText( $taskData->srcText );
+               $status = Helper::isValidTitle(
+                       $oSourceTitle
+               );
+               if( !$status->isOK() ) {
+                       $result->message[$sysLang] = $status->getHTML();
+                       return $result;
+               }
+
+               $mlmTranslation = MultiLanguageTranslation::newFromTitle(
+                       $oSourceTitle
+               );
+
+               if( !$mlmTranslation ) {
+                       //very unexpected!
+                       $result->message[$sysLang] = $this->msg(
+                               'mlm-error-title-invalid'
+                       )->plain();
+                       return $result;
+               }
+
+               $status = $mlmTranslation->delete();
+               if( !$status->isOK() ) {
+                       $result->message[$sysLang] = $status->getHTML();
+                       return $result;
+               }
+               return $result;
+       }
+
+       public function execute() {
+               $params = $this->extractRequestParams();
+
+               $task = $params['task'];
+
+               $method= "task_$task";
+               $result = $this->makeStandardReturn();
+
+               if( !is_callable( array( $this, $method) ) ) {
+                       $result->errors['task'] = "Task '$task' not 
implemented!";
+               }
+               else {
+                       $res = $this->checkTaskPermission( $task );
+                       if( !$res ) {
+                               if ( is_callable( [ $this, 'dieWithError' ] ) ) 
{
+                                       $this->dieWithError(
+                                               
'apierror-permissiondenied-generic',
+                                               'permissiondenied'
+                                       );
+                               } else {
+                                       $this->dieUsageMsg( 'badaccess-groups' 
);
+                               }
+                       }
+                       if( wfReadOnly() ) {
+                               global $wgReadOnly;
+                               $result->message = wfMessage(
+                                       'bs-readonly',
+                                       $wgReadOnly
+                               )->plain();
+                       }
+                       else {
+                               $taskData = $this->getParameter( 'taskData' );
+                               if( empty( $result->errors ) && empty( 
$result->message ) ) {
+                                       try {
+                                               $result = $this->$method( 
$taskData , $params );
+                                       }
+                                       catch ( Exception $e ) {
+                                               $result->success = false;
+                                               $result->message = 
$e->getMessage();
+                                               $mCode = method_exists( $e, 
'getCodeString' )
+                                                       ? $e->getCodeString()
+                                                       : $e->getCode();
+                                               if( $e instanceof DBError ) {
+                                                       //TODO: error code for 
subtypes like DBQueryError or
+                                                       //DBReadOnlyError?
+                                                       $mCode = 'dberror';
+                                               }
+                                               $result->errors[$mCode] = 
$e->getMessage();
+                                               $result->errors[0]['code'] = 
'unknown error';
+                                       }
+                               }
+                       }
+               }
+
+               foreach( $result as $sFieldName => $mFieldValue ) {
+                       if( $mFieldValue === null ) {
+                               continue; //MW Api doesn't like NULL values
+                       }
+
+                       //Remove empty 'errors' array from respons as mw.Api in 
MW 1.30+
+                       //will interpret this field as indicator for a failed 
request
+                       if( $sFieldName === 'errors' && empty( $mFieldValue ) ) 
{
+                               continue;
+                       }
+                       $this->getResult()->addValue( null, $sFieldName, 
$mFieldValue );
+               }
+       }
+
+       protected function getParameterFromSettings( $paramName, 
$paramSettings, $parseLimit ) {
+               $value = parent::getParameterFromSettings(
+                       $paramName,
+                       $paramSettings,
+                       $parseLimit
+               );
+               //Unfortunately there is no way to register custom types for 
parameters
+               if( $paramName == 'taskData' ) {
+                       $value = \FormatJson::decode($value);
+                       if( empty($value) ) {
+                               return new stdClass();
+                       }
+               }
+               return $value;
+       }
+
+       protected function makeStandardReturn() {
+               return (object) [
+                       'errors' => [],
+                       'success' => false,
+                       'message' => '',
+                       'payload' => [],
+                       'payload_count' => 0
+               ];
+       }
+
+       /**
+        *
+        * @param string $task
+        * @return boolean null if requested task not in list
+        * true if allowed
+        * false if not found in permission table of current user
+        */
+       public function checkTaskPermission( $task ) {
+               $taskPermissions = $this->getRequiredTaskPermissions();
+
+               if( empty($taskPermissions[$task]) ) {
+                       return;
+               }
+               //lookup permission for given task
+               foreach( $taskPermissions[$task] as $sPermission ) {
+                       //check if user have needed permission
+                       if( $this->getUser()->isAllowed( $sPermission ) ) {
+                               continue;
+                       }
+                       //TODO: Reflect permission in error message
+                       return false;
+               }
+
+               return true;
+       }
+
+       /**
+        * Returns an array of allowed parameters
+        * @return array
+        */
+       protected function getAllowedParams() {
+               return [
+                       'task' => array(
+                               \ApiBase::PARAM_REQUIRED => true,
+                               \ApiBase::PARAM_TYPE => 'string',
+                               \ApiBase::PARAM_HELP_MSG => 
'apihelp-mlm-param-task',
+                       ),
+                       'taskData' => array(
+                               \ApiBase::PARAM_TYPE => 'string',
+                               \ApiBase::PARAM_REQUIRED => false,
+                               \ApiBase::PARAM_DFLT => '{}',
+                               \ApiBase::PARAM_HELP_MSG => 
'apihelp-mlm-param-taskdata',
+                       ),
+                       'format' => array(
+                               \ApiBase::PARAM_DFLT => 'json',
+                               \ApiBase::PARAM_TYPE => [ 'json', 'jsonfm' ],
+                               \ApiBase::PARAM_HELP_MSG => 
'apihelp-mlm-param-format',
+                       )
+               ];
+       }
+}
\ No newline at end of file
diff --git a/src/Helper.php b/src/Helper.php
index 3b76276..ca05836 100644
--- a/src/Helper.php
+++ b/src/Helper.php
@@ -21,19 +21,31 @@
                        return \Status::newFatal( 'mlm-error-title-invalid' );
                }
                if( !$oTitle->exists() ) {
-                       return \Status::newFatal( 'mlm-error-title-notexists' );
+                       return \Status::newFatal(
+                               'mlm-error-title-notexists',
+                               $oTitle->getFullText()
+                       );
                }
                if( $oTitle->isTalkPage() ) {
-                       return \Status::newFatal( 'mlm-error-title-istalkpage' 
);
+                       return \Status::newFatal(
+                               'mlm-error-title-istalkpage',
+                               $oTitle->getFullText()
+                       );
                }
                if( !$oTitle->getNamespace() < 0 ) {
-                       return \Status::newFatal( 
'mlm-error-title-nsnotallowed' );
+                       return \Status::newFatal(
+                               'mlm-error-title-nsnotallowed',
+                               $oTitle->getFullText()
+                       );
                }
                $aNonTranslatableNs = static::getConfig()->get(
                        Config::NON_TRANSLATABLE_NAMESPACES
                );
                if( in_array( $oTitle->getNamespace(), $aNonTranslatableNs ) ) {
-                       return \Status::newFatal( 
'mlm-error-title-nsnotallowed' );
+                       return \Status::newFatal(
+                               'mlm-error-title-nsnotallowed',
+                               $oTitle->getFullText()
+                       );
                }
                return \Status::newGood( $oTitle );
        }
@@ -63,7 +75,10 @@
                        static::getConfig()->get( Config::AVAILABLE_LANGUAGES )
                );
                if( !$bAvailableLang ) {
-                       return \Status::newFatal( 'mlm-error-lang-notallowed' );
+                       return \Status::newFatal(
+                               'mlm-error-lang-notallowed',
+                               $sLang
+                       );
                }
                return \Status::newGood( $sLang );
        }
diff --git a/src/Hooks/BeforePageDisplay.php b/src/Hooks/BeforePageDisplay.php
index 92f6547..1f78a04 100644
--- a/src/Hooks/BeforePageDisplay.php
+++ b/src/Hooks/BeforePageDisplay.php
@@ -2,6 +2,9 @@
 
 namespace MultiLanguageManager\Hooks;
 
+use MultiLanguageManager\Helper;
+use MultiLanguageManager\MultiLanguageTranslation as Translation;
+
 class BeforePageDisplay {
 
        /**
@@ -33,6 +36,57 @@
         */
        public function process() {
                $this->oOutputPage->addModuleStyles( 'ext.mlm.styles' );
+
+               if( !Helper::isValidTitle( $this->oSkin->getTitle() )->isOK() ) 
{
+                       return true;
+               }
+               $this->oOutputPage->addModules( 'ext.mlm' );
+
+               $availableLanguages = Helper::getAvailableLanguageCodes();
+               $this->oOutputPage->addJsConfigVars(
+                       'mlmLanguages',
+                       $availableLanguages
+               );
+
+               $sysLang = Helper::getSystemLanguageCode();
+               $langFlags = [
+                       $sysLang => Helper::getLangFlagUrl( $sysLang ),
+               ];
+               foreach( $availableLanguages as $lang ) {
+                       $langFlags[$lang] = Helper::getLangFlagUrl( $lang );
+               }
+               $this->oOutputPage->addJsConfigVars(
+                       'mlmLanguageFlags',
+                       $langFlags
+               );
+
+               $oTransations = Translation::newFromTitle( 
$this->oSkin->getTitle() );
+               if( !$oTransations || !$oTransations->getSourceTitle() 
instanceof \Title ) {
+                       return true;
+               }
+
+               $this->oOutputPage->addJsConfigVars(
+                       'mlmSourceTitle',
+                       $oTransations->getSourceTitle()->getFullText()
+               );
+
+               $translations = [];
+               foreach( $oTransations->getTranslations() as $translation ) {
+                       if( !$title = \Title::newFromID( $translation->id ) ) {
+                               continue;
+                       }
+                       $translations[] = [
+                               'text' => $title->getFullText(),
+                               'lang' => $translation->lang,
+                               'id' => $translation->id,
+                       ];
+               }
+
+               $this->oOutputPage->addJsConfigVars(
+                       'mlmTranslations',
+                       $translations
+               );
+
                return true;
        }
 }
\ No newline at end of file
diff --git a/src/MultiLanguageTranslation.php b/src/MultiLanguageTranslation.php
index 6762d7a..a69535e 100644
--- a/src/MultiLanguageTranslation.php
+++ b/src/MultiLanguageTranslation.php
@@ -186,10 +186,24 @@
                        return \Status::newFatal( 'mlm-error-title-invalid' );
                }
                if( $oTranslation->isSourceTitle( $oTitle ) ) {
-                       return \Status::newFatal( 
'mlm-error-title-isalreadysource' );
+                       return \Status::newFatal(
+                               'mlm-error-title-isalreadysource',
+                               $oTitle->getFullText()
+                       );
                }
-               if( $oTranslation->isTranslation( $oTitle ) ) {
-                       return \Status::newFatal( 
'mlm-error-title-isalreadytranslation' );
+               if( $this->isTranslation( $oTitle ) ) {
+                       return \Status::newFatal(
+                               'mlm-error-title-isalreadysource',
+                               $oTitle->getFullText()
+                       );
+               }
+               if( $oTranslation->getSourceTitle()
+                       && !$oTranslation->getSourceTitle()->equals( 
$this->getSourceTitle() )
+                       && $oTranslation->isTranslation( $oTitle ) ) {
+                       return \Status::newFatal(
+                               'mlm-error-title-isalreadysource',
+                               $oTitle->getFullText()
+                       );
                }
                if( $sLang == Helper::getSystemLanguageCode() ) {
                        return \Status::newFatal(
@@ -202,7 +216,8 @@
                );
                if( !in_array( $sLang, $aLangs ) ) {
                        return \Status::newFatal(
-                               wfMessage( "mlm-error-lang-alreadytraslated", 
$sLang )
+                               "mlm-error-lang-alreadytraslated",
+                               $sLang
                        );
                }
                $this->aTranslations[] = (object) [
@@ -223,7 +238,10 @@
                        return $oStatus;
                }
                if( !$this->isTranslation( $oTitle ) ) {
-                       return \Status::newFatal( 
'mlm-error-title-isnottranslation' );
+                       return \Status::newFatal(
+                               'mlm-error-title-isnottranslation',
+                               $oTitle->getFullText()
+                       );
                }
                foreach( $this->aTranslations as $iKey => $oTranslation ) {
                        if( (int) $oTitle->getArticleID() !== $oTranslation->id 
) {
@@ -245,7 +263,16 @@
                        return $oStatus;
                }
                if( $this->getSourceTitle() instanceof \Title ) {
-                       return \Status::newFatal( 
'mlm-error-title-isalreadysource' );
+                       if( $this->isSourceTitle( $oTitle ) ) {
+                               return \Status::newFatal(
+                                       'mlm-error-title-isalreadysource',
+                                       $oTitle->getFullText()
+                               );
+                       }
+                       return \Status::newFatal(
+                               'mlm-error-title-isalreadytranslation',
+                               $oTitle->getFullText()
+                       );
                }
                $oTranslation = static::newFromTitle( $oTitle, true );
                if( !$oTranslation ) {
@@ -253,10 +280,16 @@
                        return \Status::newFatal( 'mlm-error-title-invalid' );
                }
                if( $oTranslation->isSourceTitle( $oTitle ) ) {
-                       return \Status::newFatal( 
'mlm-error-title-isalreadysource' );
+                       return \Status::newFatal(
+                               'mlm-error-title-isalreadysource',
+                               $oTitle->getFullText()
+                       );
                }
                if( $oTranslation->isTranslation( $oTitle ) ) {
-                       return \Status::newFatal( 
'mlm-error-title-isalreadytranslation' );
+                       return \Status::newFatal(
+                               'mlm-error-title-isalreadytranslation',
+                               $oTitle->getFullText()
+                       );
                }
 
                $this->oSourceTitle = $oTitle;
diff --git a/src/Specials/MultiLanguageManager.php 
b/src/Specials/MultiLanguageManager.php
index 9aea9c3..38da73a 100644
--- a/src/Specials/MultiLanguageManager.php
+++ b/src/Specials/MultiLanguageManager.php
@@ -74,10 +74,10 @@
                        $this->getRequest()->getVal( 'mlm-sourcetitle', '' )
                );
 
-               $oStatus = Helper::isValidTitle( $oSourceTitle );
-               if( !$oStatus->isOK() ) {
+               $status = Helper::isValidTitle( $oSourceTitle );
+               if( !$status->isOK() ) {
                        $this->outputError(
-                               $oStatus->getHTML(),
+                               $status->getHTML(),
                                wfMessage( 'mlm-input-label-sourcetitle' 
)->plain()
                        );
                        return false;
@@ -97,10 +97,10 @@
                }
 
                if( !$oTranslation->isSourceTitle( $oSourceTitle ) ) {
-                       $oStatus = $oTranslation->setSourceTitle( $oSourceTitle 
);
-                       if( !$oStatus->isOK() ) {
+                       $status = $oTranslation->setSourceTitle( $oSourceTitle 
);
+                       if( !$status->isOK() ) {
                                $this->outputError(
-                                       $oStatus->getHTML(),
+                                       $status->getHTML(),
                                        wfMessage( 
'mlm-input-label-sourcetitle' )->plain()
                                );
                                return false;
@@ -108,9 +108,9 @@
                }
 
                if( $this->getRequest()->getVal( 'mlm-delete', false ) ) {
-                       $oStatus = $oTranslation->delete();
-                       if( !$oStatus->isOK() ) {
-                               $this->outputError( $oStatus->getHTML() );
+                       $status = $oTranslation->delete();
+                       if( !$status->isOK() ) {
+                               $this->outputError( $status->getHTML() );
                                return false;
                        }
                        return true;
@@ -124,22 +124,22 @@
                        $oNewTranslationTitle = \Title::newFromText(
                                $sNewTranslation
                        );
-                       $oStatus = $oTranslation->addTranslation(
+                       $status = $oTranslation->addTranslation(
                                $oNewTranslationTitle,
                                $this->getRequest()->getVal( 
'mlm-newtranslation-lang', '' )
                        );
-                       if( !$oStatus->isOK() ) {
+                       if( !$status->isOK() ) {
                                $this->outputError(
-                                       $oStatus->getHTML(),
+                                       $status->getHTML(),
                                        wfMessage( 
'mlm-input-label-translationtitles', 1)->text()
                                );
                                return false;
                        }
                }
 
-               $oStatus = $oTranslation->save();
-               if( !$oStatus->isOK() ) {
-                       $this->outputError( $oStatus->getHTML() );
+               $status = $oTranslation->save();
+               if( !$status->isOK() ) {
+                       $this->outputError( $status->getHTML() );
                }
                return true;
 
@@ -296,9 +296,9 @@
        protected function makeTitleContext( $subPage = '' ) {
                if( !empty( $subPage ) ) {
                        $oTitle = \Title::newFromText( $subPage );
-                       $oStatus = Helper::isValidTitle( $oTitle );
-                       if( !$oStatus->isOK() ) {
-                               $this->outputError( $oStatus->getHTML(), 
"'$subPage':" );
+                       $status = Helper::isValidTitle( $oTitle );
+                       if( !$status->isOK() ) {
+                               $this->outputError( $status->getHTML(), 
"'$subPage':" );
                                return false;
                        }
                        $this->oTitle = $oTitle;

-- 
To view, visit https://gerrit.wikimedia.org/r/393072
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I3da5e718989718d9f67164b2dfcd50ad25ffd441
Gerrit-PatchSet: 11
Gerrit-Project: mediawiki/extensions/MultiLanguageManager
Gerrit-Branch: master
Gerrit-Owner: Pwirth <[email protected]>
Gerrit-Reviewer: Pwirth <[email protected]>
Gerrit-Reviewer: Raimond Spekking <[email protected]>
Gerrit-Reviewer: Robert Vogel <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to