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