Anomie has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/295257

Change subject: Add API support
......................................................................

Add API support

This doesn't add an API module of its own, instead it integrates into
the existing ApiParse and ApiExpandTemplates modules.

This also refactors the logic for actually doing the sandboxing into a
separate class.

Bug: T65523
Change-Id: I77a9aa5ac30cd954b380e1bfd3e60a353d26b32f
Depends-On: I72d5cf8e0b86e4250af1459219dc3b42d7adbbb8
---
M SpecialTemplateSandbox.php
M TemplateSandbox.hooks.php
A TemplateSandboxLogic.php
M extension.json
M i18n/en.json
M i18n/qqq.json
6 files changed, 284 insertions(+), 121 deletions(-)


  git pull 
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/TemplateSandbox 
refs/changes/57/295257/1

diff --git a/SpecialTemplateSandbox.php b/SpecialTemplateSandbox.php
index 20c0b4e..06ac2c3 100644
--- a/SpecialTemplateSandbox.php
+++ b/SpecialTemplateSandbox.php
@@ -1,7 +1,6 @@
 <?php
 class SpecialTemplateSandbox extends SpecialPage {
        private $prefixes = array();
-       private $oldCurrentRevisionCallback = null;
 
        /**
         * @var null|Title
@@ -19,37 +18,6 @@
 
        protected function getGroupName() {
                return 'wiki';
-       }
-
-       /**
-        * @return ScopedCallback to clean up
-        */
-       private function fakePageExists() {
-               global $wgHooks;
-               $prefixes = $this->prefixes;
-               $inHook = false;
-               $wgHooks['TitleExists']['TemplateSandbox'] =
-                       function ( $title, &$exists ) use ( $prefixes, &$inHook 
) {
-                               if ( $exists || $inHook ) {
-                                       return;
-                               }
-                               $inHook = true;
-                               foreach ( $prefixes as $prefix ) {
-                                       $newtitle = Title::newFromText(
-                                               $prefix . '/' . 
$title->getFullText() );
-                                       if ( $newtitle instanceof Title && 
$newtitle->exists() ) {
-                                               $exists = true;
-                                               break;
-                                       }
-                               }
-                               $inHook = false;
-                       };
-               LinkCache::singleton()->clear();
-               return new ScopedCallback( function () {
-                       global $wgHooks;
-                       unset( $wgHooks['TitleExists']['TemplateSandbox'] );
-                       LinkCache::singleton()->clear();
-               } );
        }
 
        function execute( $par ) {
@@ -232,29 +200,11 @@
                $popts = $page->makeParserOptions( $this->getContext() );
                $popts->setIsPreview( true );
                $popts->setIsSectionPreview( false );
-               $fakePageExistsScopedCallback = $this->fakePageExists();
-               $this->oldCurrentRevisionCallback = 
$popts->setCurrentRevisionCallback(
-                       array( $this, 'currentRevisionCallback' ) );
+               $logic = new TemplateSandboxLogic( $this->prefixes, null, null 
);
+               $reset = $logic->setupForParse( $popts );
                $this->title = $title;
                $this->output = $content->getParserOutput( $title, 
$rev->getId(), $popts );
 
                return Status::newGood();
-       }
-
-       /**
-        * @param Title $title
-        * @param Parser|bool $parser
-        * @return Revision
-        */
-       function currentRevisionCallback( $title, $parser = false ) {
-               $found = false;
-               foreach ( $this->prefixes as $prefix ) {
-                       $newtitle = Title::newFromText( $prefix . '/' . 
$title->getFullText() );
-                       if ( $newtitle instanceof Title && $newtitle->exists() 
) {
-                               $title = $newtitle;
-                               break;
-                       }
-               }
-               return call_user_func( $this->oldCurrentRevisionCallback, 
$title, $parser );
        }
 }
diff --git a/TemplateSandbox.hooks.php b/TemplateSandbox.hooks.php
index 4fb8873..1f73288 100644
--- a/TemplateSandbox.hooks.php
+++ b/TemplateSandbox.hooks.php
@@ -1,17 +1,5 @@
 <?php
 class TemplateSandboxHooks {
-       private static $template = null;
-
-       /**
-        * @var Content
-        */
-       private static $content = null;
-
-       /**
-        * @var callback
-        */
-       private static $oldCurrentRevisionCallback = null;
-
        /**
         * Hook for EditPage::importFormData to parse our new form fields, and 
if
         * necessary put $editpage into "preview" mode.
@@ -50,26 +38,6 @@
                return "<div id='mw-$msg'>\n"
                        . wfMessage( $msg )->parseAsBlock()
                        . "\n</div>";
-       }
-
-       /**
-        * @param Title $templatetitle
-        * @return ScopedCallback to clean up
-        */
-       private static function fakePageExists( $templatetitle ) {
-               global $wgHooks;
-               $wgHooks['TitleExists']['TemplateSandbox'] =
-                       function ( $title, &$exists ) use ( $templatetitle ) {
-                               if ( $templatetitle->equals( $title ) ) {
-                                       $exists = true;
-                               }
-                       };
-               LinkCache::singleton()->clearBadLink( 
$templatetitle->getPrefixedDBkey() );
-               return new ScopedCallback( function () use ( $templatetitle ) {
-                       global $wgHooks;
-                       unset( $wgHooks['TitleExists']['TemplateSandbox'] );
-                       LinkCache::singleton()->clearLink( $templatetitle );
-               } );
        }
 
        /**
@@ -123,7 +91,6 @@
                $parserOutput = null;
 
                try {
-                       TemplateSandboxHooks::$template = $templatetitle;
                        if ( $editpage->sectiontitle !== '' ) {
                                $sectionTitle = $editpage->sectiontitle;
                        } else {
@@ -131,14 +98,13 @@
                        }
 
                        if ( $editpage->getArticle()->exists() ) {
-                               TemplateSandboxHooks::$content = 
$editpage->getArticle()->replaceSectionContent(
+                               $content = 
$editpage->getArticle()->replaceSectionContent(
                                        $editpage->section, $content, 
$sectionTitle, $editpage->edittime
                                );
                        } else {
                                if ( $editpage->section === 'new' ) {
                                        $content = $content->addSectionHeader( 
$sectionTitle );
                                }
-                               TemplateSandboxHooks::$content = $content;
                        }
 
                        // Apply PST to the to-be-saved text
@@ -148,7 +114,7 @@
                        $popts->setEditSection( false );
                        $popts->setIsPreview( true );
                        $popts->setIsSectionPreview( false );
-                       TemplateSandboxHooks::$content = 
TemplateSandboxHooks::$content->preSaveTransform(
+                       $content = $content->preSaveTransform(
                                $templatetitle, $wgUser, $popts
                        );
 
@@ -161,13 +127,11 @@
                        $popts->setEditSection( false );
                        $popts->setIsPreview( true );
                        $popts->setIsSectionPreview( false );
-                       TemplateSandboxHooks::$oldCurrentRevisionCallback = 
$popts->setCurrentRevisionCallback(
-                               'TemplateSandboxHooks::currentRevisionCallback'
-                       );
-                       $fakePageExistsScopedCallback = self::fakePageExists( 
$templatetitle );
+                       $logic = new TemplateSandboxLogic( [], $templatetitle, 
$content );
+                       $reset = $logic->setupForParse( $popts );
                        $popts->enableLimitReport();
 
-                       $rev = TemplateSandboxHooks::currentRevisionCallback( 
$title );
+                       $rev = call_user_func_array( 
$popts->getCurrentRevisionCallback(), [ $title ] );
                        $content = $rev->getContent( Revision::FOR_THIS_USER, 
$wgUser );
                        $parserOutput = $content->getParserOutput( $title, 
$rev->getId(), $popts );
 
@@ -206,31 +170,6 @@
                $out = $previewhead . $out . $editpage->previewTextAfterContent;
 
                return false;
-       }
-
-       /**
-        * @param Title $title
-        * @param Parser|bool $parser
-        * @return Revision
-        */
-       static function currentRevisionCallback( $title, $parser = false ) {
-               if ( $title->equals( TemplateSandboxHooks::$template ) ) {
-                       global $wgUser;
-                       return new Revision( array(
-                               'page' => $title->getArticleID(),
-                               'user_text' => $wgUser->getName(),
-                               'user' => $wgUser->getId(),
-                               'parent_id' => $title->getLatestRevId(),
-                               'title' => $title,
-                               'content' => TemplateSandboxHooks::$content
-                       ) );
-               } else {
-                       return call_user_func(
-                               
TemplateSandboxHooks::$oldCurrentRevisionCallback,
-                               $title,
-                               $parser
-                       );
-               }
        }
 
        /**
@@ -331,4 +270,160 @@
 
                return true;
        }
+
+       /**
+        * Determine if this API module is appropriate for us to mess with.
+        * @param ApiBase $module
+        * @return bool
+        */
+       private static function isUsableApiModule( $module ) {
+               return $module instanceof ApiParse || $module instanceof 
ApiExpandTemplates;
+       }
+
+       /**
+        * Hook for APIGetAllowedParams to add our API parameters to the 
relevant
+        * modules.
+        *
+        * @param ApiBase $module
+        * @param array $params
+        * @param int $flags
+        * @return bool
+        */
+       public static function onAPIGetAllowedParams( $module, &$params, $flags 
) {
+               if ( !self::isUsableApiModule( $module ) ) {
+                       return true;
+               }
+
+               $params += array(
+                       'templatesandboxprefix' => array(
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_ISMULTI => true,
+                               ApiBase::PARAM_HELP_MSG => 
'templatesandbox-apihelp-prefix',
+                       ),
+                       'templatesandboxtitle' => array(
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_HELP_MSG => 
'templatesandbox-apihelp-title',
+                       ),
+                       'templatesandboxtext' => array(
+                               ApiBase::PARAM_TYPE => 'text',
+                               ApiBase::PARAM_HELP_MSG => 
'templatesandbox-apihelp-text',
+                       ),
+                       'templatesandboxcontentmodel' => array(
+                               ApiBase::PARAM_TYPE => 
ContentHandler::getContentModels(),
+                               ApiBase::PARAM_HELP_MSG => 
'templatesandbox-apihelp-contentmodel',
+                       ),
+                       'templatesandboxcontentformat' => array(
+                               ApiBase::PARAM_TYPE => 
ContentHandler::getAllContentFormats(),
+                               ApiBase::PARAM_HELP_MSG => 
'templatesandbox-apihelp-contentformat',
+                       ),
+               );
+               return true;
+       }
+
+       /**
+        * Hook for ApiMakeParserOptions to set things up for TemplateSandbox
+        * parsing when necessary.
+        *
+        * @param ParserOptions $options
+        * @param Title $title
+        * @param array $params
+        * @param ApiBase $module
+        * @param null &$reset Set to a ScopedCallback used to reset any hooks 
set.
+        * @param bool &$suppressCache
+        * @return bool
+        */
+       public static function onApiMakeParserOptions(
+               $options, $title, $params, $module, &$reset, &$suppressCache
+       ) {
+               // Shouldn't happen, but...
+               if ( !self::isUsableApiModule( $module ) ) {
+                       return true;
+               }
+
+               $params += [
+                       'templatesandboxprefix' => [],
+                       'templatesandboxtitle' => null,
+                       'templatesandboxtext' => null,
+                       'templatesandboxcontentmodel' => null,
+                       'templatesandboxcontentformat' => null,
+               ];
+               $params = [
+                       'prefix' => $params['templatesandboxprefix'],
+                       'title' => $params['templatesandboxtitle'],
+                       'text' => $params['templatesandboxtext'],
+                       'contentmodel' => 
$params['templatesandboxcontentmodel'],
+                       'contentformat' => 
$params['templatesandboxcontentformat'],
+               ];
+
+               if ( ( $params['title'] === null ) !== ( $params['text'] === 
null ) ) {
+                       $p = $module->getModulePrefix();
+                       $module->dieUsage(
+                               "The parameters {$p}templatesandboxtitle and 
{$p}templatesandboxtext must " .
+                                       'both be specified or both be omitted',
+                               'invalidparammix'
+                       );
+               }
+
+               $prefixes = [];
+               foreach ( $params['prefix'] as $prefix ) {
+                       $prefixTitle = Title::newFromText( rtrim( $prefix, '/' 
) );
+                       if ( !$prefixTitle instanceof Title || 
$prefixTitle->getFragment() !== '' ||
+                               $prefixTitle->isExternal()
+                       ) {
+                               $p = $module->getModulePrefix();
+                               $this->dieUsage(
+                                       "Invalid {$p}templatesandboxprefix: 
$prefix", "bad_{$p}templatesandboxprefix"
+                               );
+                       }
+                       $prefixes[] = $prefixTitle->getFullText();
+               }
+
+               if ( $params['title'] !== null ) {
+                       $page = $module->getTitleOrPageId( $params );
+                       if ( $params['contentmodel'] == '' ) {
+                               $contentHandler = $page->getContentHandler();
+                       } else {
+                               $contentHandler = 
ContentHandler::getForModelID( $params['contentmodel'] );
+                       }
+
+                       $name = $page->getTitle()->getPrefixedDBkey();
+                       $model = $contentHandler->getModelID();
+
+                       if ( $contentHandler->supportsDirectApiEditing() === 
false ) {
+                               $this->dieUsage(
+                                       "Direct editing via API is not 
supported for content model $model used by $name",
+                                       'no-direct-editing'
+                               );
+                       }
+
+                       $format = $params['contentformat'] ?: 
$contentHandler->getDefaultFormat() ;
+                       if ( !$contentHandler->isSupportedFormat( $format ) ) {
+                               $this->dieUsage( "The requested format $format 
is not supported for content model " .
+                                       " $model used by $name", 'badformat' );
+                       }
+
+                       $templatetitle = $page->getTitle();
+                       $content = $contentHandler->makeContent( 
$params['text'], $page->getTitle(), $model, $format );
+
+                       // Apply PST to templatesandboxtext
+                       $popts = $page->makeParserOptions( $module );
+                       $popts->setEditSection( false );
+                       $popts->setIsPreview( true );
+                       $popts->setIsSectionPreview( false );
+                       $user = RequestContext::getMain()->getUser();
+                       $content = $content->preSaveTransform( $templatetitle, 
$user, $popts );
+               } else {
+                       $templatetitle = null;
+                       $content = null;
+               }
+
+               if ( $prefixes || $templatetitle ) {
+                       $logic = new TemplateSandboxLogic( $prefixes, 
$templatetitle, $content );
+                       $reset = $logic->setupForParse( $options );
+                       $suppressCache = true;
+               }
+
+               return true;
+       }
+
 }
diff --git a/TemplateSandboxLogic.php b/TemplateSandboxLogic.php
new file mode 100644
index 0000000..b7917ca
--- /dev/null
+++ b/TemplateSandboxLogic.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Business logic class for TemplateSandbox
+ */
+class TemplateSandboxLogic {
+       private static $counter = 0;
+
+       /** Prefixes to search for sandbox templates */
+       private $prefixes = [];
+
+       /** Title to replace with $content*/
+       private $title = null;
+
+       /** Content to replace $title */
+       private $content = null;
+
+       /**
+        * @param string[] $prefixes Title prefixes to search for sandbox 
templates
+        * @param Title|null $title Title to replace with 'content'
+        * @param Content|null $content Content to use to replace 'title'
+        */
+       public function __construct( $prefixes, $title, $content ) {
+               if ( ( $title === null ) !== ( $content === null ) ) {
+                       throw new InvalidArgumentException( '$title and 
$content must both be given or both be null' );
+               }
+
+               $this->prefixes = $prefixes;
+               $this->title = $title;
+               $this->content = $content;
+
+       }
+
+       /**
+        * Set up a ParserOptions for TemplateSandbox operation.
+        * @param ParserOptions $popt
+        * @return ScopedCallback to uninstall
+        */
+       public function setupForParse( ParserOptions $popt ) {
+               global $wgHooks;
+
+               $id = 'TemplateSandbox.' . ++self::$counter;
+
+               $inHook = false;
+               $wgHooks['TitleExists'][$id] = function ( $title, &$exists ) 
use ( &$inHook ) {
+                       if ( $exists || $inHook ) {
+                               return;
+                       }
+                       $inHook = true;
+                       $titleText = $title->getFullText();
+                       try {
+                               if ( $this->title && $this->title->equals( 
$title ) ) {
+                                       $exists = true;
+                                       return;
+                               }
+
+                               foreach ( $this->prefixes as $prefix ) {
+                                       $newtitle = Title::newFromText( $prefix 
. '/' . $titleText );
+                                       if ( $newtitle instanceof Title && 
$newtitle->exists() ) {
+                                               $exists = true;
+                                               return;
+                                       }
+                               }
+                       } finally {
+                               $inHook = false;
+                       }
+               };
+
+               $oldCurrentRevisionCallback = $popt->setCurrentRevisionCallback(
+                       function ( $title, $parser = false ) use ( 
&$oldCurrentRevisionCallback ) {
+                               if ( $this->title && $this->title->equals( 
$title ) ) {
+                                       $user = 
RequestContext::getMain()->getUser();
+                                       return new Revision( [
+                                               'page' => 
$title->getArticleID(),
+                                               'user_text' => $user->getName(),
+                                               'user' => $user->getId(),
+                                               'parent_id' => 
$title->getLatestRevId(),
+                                               'title' => $title,
+                                               'content' => $this->content
+                                       ] );
+                               }
+
+                               foreach ( $this->prefixes as $prefix ) {
+                                       $newtitle = Title::newFromText( $prefix 
. '/' . $title->getFullText() );
+                                       if ( $newtitle instanceof Title && 
$newtitle->exists() ) {
+                                               $title = $newtitle;
+                                               break;
+                                       }
+                               }
+                               return call_user_func( 
$oldCurrentRevisionCallback, $title, $parser );
+                       }
+               );
+
+               LinkCache::singleton()->clear();
+
+               return new ScopedCallback( function () use ( $id ) {
+                       global $wgHooks;
+                       unset( $wgHooks['TitleExists'][$id] );
+                       LinkCache::singleton()->clear();
+               } );
+       }
+}
diff --git a/extension.json b/extension.json
index f7dc0ef..3b1253a 100644
--- a/extension.json
+++ b/extension.json
@@ -19,7 +19,8 @@
        },
        "AutoloadClasses": {
                "TemplateSandboxHooks": "TemplateSandbox.hooks.php",
-               "SpecialTemplateSandbox": "SpecialTemplateSandbox.php"
+               "SpecialTemplateSandbox": "SpecialTemplateSandbox.php",
+               "TemplateSandboxLogic": "TemplateSandboxLogic.php"
        },
        "ResourceModules": {
                "ext.TemplateSandbox": {
@@ -40,6 +41,12 @@
                ],
                "AlternateEditPreview": [
                        "TemplateSandboxHooks::templateSandboxPreview"
+               ],
+               "APIGetAllowedParams": [
+                       "TemplateSandboxHooks::onAPIGetAllowedParams"
+               ],
+               "ApiMakeParserOptions": [
+                       "TemplateSandboxHooks::onApiMakeParserOptions"
                ]
        },
        "config": {
diff --git a/i18n/en.json b/i18n/en.json
index 7b72507..5f107e9 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -34,5 +34,10 @@
        "templatesandbox-editform-need-title": "To preview another page with 
this template, a page title must be specified.",
        "templatesandbox-editform-invalid-template": "The name of the template 
you specified is invalid.",
        "templatesandbox-editform-invalid-title": "The title you specified for 
previewing is invalid.",
-       "templatesandbox-editform-title-not-exists": "The title you specified 
for previewing does not exist."
+       "templatesandbox-editform-title-not-exists": "The title you specified 
for previewing does not exist.",
+       "templatesandbox-apihelp-prefix": "Template sandbox prefix, as with 
[[Special:TemplateSandbox]].",
+       "templatesandbox-apihelp-title": "Parse the page using 
<var>$1templatesandboxtext</var> in place of the contents of the page named 
here.",
+       "templatesandbox-apihelp-text": "Parse the page using this page content 
in place of the page named by <var>$1templatesandboxtitle</var>.",
+       "templatesandbox-apihelp-contentmodel": "Content model of 
<var>$1templatesandboxtext</var>.",
+       "templatesandbox-apihelp-contentformat": "Content format of 
<var>$1templatesandboxtext</var>."
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 9838cd5..19e5b3d 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -38,5 +38,10 @@
        "templatesandbox-editform-need-title": "Error message displayed when no 
page title is given for the editpage form.\n\nSee also:\n* 
{{msg-mw|Templatesandbox-editform-need-template}}",
        "templatesandbox-editform-invalid-template": "Error message displayed 
when the template name specified for the editpage form is invalid.",
        "templatesandbox-editform-invalid-title": "Error message displayed when 
the title specified for the editpage form is invalid.",
-       "templatesandbox-editform-title-not-exists": "Error message displayed 
when the title specified for the editpage form does not exist."
+       "templatesandbox-editform-title-not-exists": "Error message displayed 
when the title specified for the editpage form does not exist.",
+       "templatesandbox-apihelp-prefix": "{{doc-apihelp-param|description=the 
\"templatesandboxprefix\" parameter for the \"parse\" and \"expandtemplates\" 
modules}}",
+       "templatesandbox-apihelp-title": "{{doc-apihelp-param|description=the 
\"templatesandboxtitle\" parameter for the \"parse\" and \"expandtemplates\" 
modules}}",
+       "templatesandbox-apihelp-text": "{{doc-apihelp-param|description=the 
\"templatesandboxtext\" parameter for the \"parse\" and \"expandtemplates\" 
modules}}",
+       "templatesandbox-apihelp-contentmodel": 
"{{doc-apihelp-param|description=the \"templatesandboxcontentmodel\" parameter 
for the \"parse\" and \"expandtemplates\" modules}}",
+       "templatesandbox-apihelp-contentformat": 
"{{doc-apihelp-param|description=the \"templatesandboxcontentformat\" parameter 
for the \"parse\" and \"expandtemplates\" modules}}"
 }

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I77a9aa5ac30cd954b380e1bfd3e60a353d26b32f
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/TemplateSandbox
Gerrit-Branch: master
Gerrit-Owner: Anomie <bjor...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to