diff --git a/UsingDataHooks.php b/UsingDataHooks.php deleted file mode 100644 index 1606e1f..0000000 --- a/UsingDataHooks.php +++ /dev/null @@ -1,289 +0,0 @@ -setFunctionHook( 'using', [ self::$instance, 'usingParserFunction' ], SFH_OBJECT_ARGS ); - $parser->setFunctionHook( 'usingarg', [ self::$instance, 'usingArgParserFunction' ], SFH_OBJECT_ARGS ); - $parser->setFunctionHook( 'data', [ self::$instance, 'dataParserFunction' ], SFH_OBJECT_ARGS ); - $parser->setFunctionHook( 'ancestorname', [ __CLASS__, 'ancestorNameFunction' ], SFH_OBJECT_ARGS | SFH_NO_HASH ); - $parser->setHook( 'using', [ self::$instance, 'usingTag' ] ); - - return true; - } - - /** - * Tells MediaWiki that one or more magic word IDs should be treated as variables. - * - * @return void - */ - public static function onGetMagicVariableIDs( &$magicWords ) { - $magicWords[] = 'parentname'; - $magicWords[] = 'selfname'; - } - - /* Returns a UsingData frame for a given page - */ - private function getDataFrame( $sourcePage, $title, &$parser, $frame ) { - global $wgHooks; - if ( !isset( $this->dataFrames[$sourcePage] ) ) { - $this->dataFrames[$sourcePage] = new UsingDataPPFrameDOM( $frame, $sourcePage ); - if ( $sourcePage != '' - && ( $sourcePage != $parser->getTitle()->getPrefixedText() ) - || $parser->getOptions()->getIsSectionPreview() ) { - [ $text, $fTitle ] = $parser->fetchTemplateAndTitle( $title ); - if ( is_object( $fTitle ) && $fTitle->getPrefixedText() != $sourcePage ) { - $this->dataFrames[$fTitle->getPrefixedText()] = $this->dataFrames[$sourcePage]; - } - if ( is_string( $text ) && $text != '' ) { - $this->searchingForData = true; - $clearStateHooks = $wgHooks['ParserClearState']; - // Other extensions tend to assume the hook is only called by wgParser and reset internal state - $wgHooks['ParserClearState'] = []; - $subParser = clone $parser; - $subParser->preprocess( $text, $fTitle, clone $parser->getOptions() ); - // We might've blocked access to templates while preprocessing; should not be cached - $subParser->clearState(); - $subParser->getOutput()->setText( $parser->getOutput()->getText() ); - $wgHooks['ParserClearState'] = empty( $wgHooks['ParserClearState'] ) - ? $clearStateHooks - : array_merge( $clearStateHooks, $wgHooks['ParserClearState'] ); - $parser->mPPNodeCount += $subParser->mPPNodeCount; - $this->searchingForData = false; - } - } - } - return $this->dataFrames[$sourcePage]; - } - - /* Returns the page title of the $depth ancestor of $frame; empty string if invalid */ - private static function ancestorNameHandler( $frame, $depth ) { - while ( $depth-- && $frame != null ) { - $frame = $frame->parent ?? null; - } - return is_object( $frame ) && isset( $frame->title ) && is_object( $frame->title ) - ? wfEscapeWikiText( $frame->title->getPrefixedText() ) : ''; - } - - /* Handles {{ANCESTORNAME:depth}} */ - public static function ancestorNameFunction( &$parser, $frame, $args ) { - $arg = $frame->expand( $args[0] ); - return [ self::ancestorNameHandler( $frame, max( 0, is_numeric( $arg ) ? intval( $arg ) : 1 ) ), 'noparse' => true ]; - } - - /* Handles {{PARENTNAME}}, {{SELFNAME}}, {{ANCESTORNAME}} */ - public static function ancestorNameVar( &$parser, &$varCache, &$index, &$ret, &$frame ) { - if ( $index == 'parentname' ) { - $ret = self::ancestorNameHandler( $frame, 1 ); - } - if ( $index == 'selfname' ) { - $ret = self::ancestorNameHandler( $frame, 0 ); - } - return true; - } - - /* Parses common elements of #using syntax. - */ - private function usingParse( &$parser, $frame, $args ) { - if ( $this->searchingForData ) { - return ''; - } - - $titleArg = trim( $frame->expand( $args[0] ) ); - if ( strpos( $titleArg, '%' ) !== false ) { - $titleArg = str_replace( [ '<', '>' ], [ '<', '>' ], urldecode( $titleArg ) ); - } - $title = \Title::newFromText( $titleArg, NS_MAIN ); - $sourcePage = is_object( $title ) ? $title->getPrefixedText() : ''; - $sourceHash = is_object( $title ) ? $title->getFragment() : ''; - $namedArgs = []; - - $one = null; - $two = null; - foreach ( $args as $key => $val ) { - if ( $key === 0 ) { - continue; - } - $bits = $val->splitArg(); - // It looks like indexes are now integers, it is a change from legacy implementation - if ( $bits['index'] === 1 ) { - $one = $frame->expand( $bits['value'] ); - } elseif ( $bits['index'] === 2 ) { - $two = $bits['value']; - } elseif ( $bits['index'] === '' ) { - $namedArgs[trim( $frame->expand( $bits['name'] ) )] = $bits['value']; - } - } - return [ $this->getDataFrame( $sourcePage, $title, $parser, $frame ), $sourceHash, $namedArgs, $one, $two ]; - } - - /* {{#using:Page#Hash|Template|Default|...}} parses Template using #data from Page's Hash fragment; or Default - * if no data from Page can be found. Named arguments override those in the #data tag. - */ - public function usingParserFunction( &$parser, $frame, $args ) { - $parse = $this->usingParse( $parser, $frame, $args ); - if ( !is_array( $parse ) ) { - return ''; - } - - [ $dframe, $fragment, $namedArgs, $templateTitle, $defaultValue ] = $parse; - if ( !$dframe->hasFragment( $fragment ) && $defaultValue !== null ) { - return $frame->expand( $defaultValue ); - } - [ $dom, $title ] = $this->fetchTemplate( $parser, $templateTitle ); - return $dframe->expandUsing( $frame, $title, $dom, $namedArgs, $fragment ); - } - - /* {{#usingarg:Page#Hash|Arg|Default}} returns the value of Arg data field on Page's Hash fragment, Default if undefined. - */ - public function usingArgParserFunction( &$parser, $frame, $args ) { - $parse = $this->usingParse( $parser, $frame, $args ); - if ( !is_array( $parse ) ) { - return ''; - } - - [ $dframe, $fragment, $namedArgs, $argName, $defaultValue ] = $parse; - $ret = $dframe->getArgumentForParser( - $parser, - UsingDataPPFrameDOM::normalizeFragment( $fragment ), - $argName, - $defaultValue === null ? '' : false - ); - return $ret !== false ? $ret : $frame->expand( $defaultValue ); - } - - /* ... - * expands ... using the data from Page's Hash fragment; Default if undefined. - * This tag relies on $parser->replaceVariables($text, $frame), which may prove fragile across MW versions. - * Should it break, $parser->recursiveTagParse($text, $frame), in combination with either modifying the markerType, or using - * insertStripItem directly, is a viable short-term alternative -- but one that call certain hooks prematurely, - * potentially causing other extensions to misbehave slightly. - */ - public function usingTag( $text, array $args, Parser $parser, PPFrame $frame ) { - if ( $this->searchingForData ) { - return ['', 'markerType' => 'none']; - } - - $source = isset( $args['page'] ) ? $parser->replaceVariables( $args['page'], $frame ) : ''; - unset( $args['page'] ); - if ( strpos( $source, '%' ) !== false ) { - $source = str_replace( [ '<', '>' ], [ '<', '>' ], urldecode( $source ) ); - } - $title = \Title::newFromText( $source, NS_MAIN ); - if ( is_object( $title ) ) { - $dframe = $this->getDataFrame( $title->getPrefixedText(), $title, $parser, $frame ); - if ( is_object( $dframe ) && $dframe->hasFragment( $title->getFragment() ) ) { - $ovr = []; - unset( $args['default'] ); - foreach ( $args as $key => $val ) { - $ovr[$key] = $parser->replaceVariables( $val, $frame ); - } - return [ - $dframe->expandUsing( $frame, $frame->title, $text, $ovr, $title->getFragment(), true ), - 'markerType' => 'none' - ]; - } - } - - return [ - isset( $args['default'] ) ? $parser->replaceVariables( $args['default'], $frame ) : '', - 'markerType' => 'none' - ]; - } - - /* {{#data:Template#Hash|...}} specifies data-transcludable arguments for the page; may not be transcluded. */ - public function dataParserFunction( Parser &$parser, PPFrame $frame, $args ) { - $templateTitle = trim( $frame->expand( $args[0] ) ); - $hostPage = $frame->title->getPrefixedText(); - unset( $args[0] ); - $fragment = ''; - - if ( strpos( $templateTitle, '%' ) !== false ) { - $templateTitle = str_replace( [ '<', '>' ], [ '<', '>' ], urldecode( $templateTitle ) ); - } - - $templateTitleObj = \Title::newFromText( $templateTitle, NS_TEMPLATE ); - if ( is_object( $templateTitleObj ) ) { - $fragment = $templateTitleObj->getFragment(); - } elseif ( $templateTitle != '' && $templateTitle[0] == '#' ) { - $fragment = substr( $templateTitle, 1 ); - } - - if ( $frame->depth == 0 || $this->searchingForData ) { - if ( !isset( $this->dataFrames[$hostPage] ) ) { - $this->dataFrames[$hostPage] = new UsingDataPPFrameDOM( $frame, $hostPage ); - } - $df =& $this->dataFrames[$hostPage]; - $df->addArgs( $frame, $args, $fragment ); - if ( $this->searchingForData ) { - return ''; - } - } - if ( !is_object( $templateTitleObj ) ) { - return ''; - } - - [ $dom, $tTitle ] = $this->fetchTemplate( $parser, $templateTitleObj ); - foreach ( $args as $k => $v ) { - // Line below breaks #data processing, but it exists in old implementation - // $args[$k] = $v->node; - } - $cframe = $frame->newChild( $args, $tTitle ); - $nargs =& $cframe->namedArgs; - $nargs['data-found'] = $frame->depth == 0 ? '3' : '2'; - $nargs['data-source'] = $hostPage; - $nargs['data-sourcee'] = wfEscapeWikiText( $hostPage ); - $nargs['data-fragment'] = $fragment; - $nargs['data-source-fragment'] = $hostPage . ( empty( $fragment ) ? '' : ( '#' . $fragment ) ); - return $cframe->expand( $dom ); - } - - /* Returns template text for transclusion. - */ - private function fetchTemplate( $parser, $template ) { - global $wgNonincludableNamespaces; - - if ( $template == '' || ( !is_string( $template ) && !is_object( $template ) ) ) { - return ''; - } - - $title = is_object( $template ) ? $template : \Title::newFromText( $template, NS_TEMPLATE ); - if ( !is_object( $title ) - || $title->getNamespace() == NS_SPECIAL - || ( $wgNonincludableNamespaces && in_array( $title->getNamespace(), $wgNonincludableNamespaces ) ) ) { - return is_object( $title ) - ? [ '[[:' . $title->getPrefixedText() . ']]', $title ] - : [ '[[:' . $template . ']]', null ]; - } - [ $dom, $title ] = $parser->getTemplateDom( $title ); - return [ $dom ? $dom : ( '[[:' . $title->getPrefixedText() . ']]' ), $title ]; - } - - public static function onBeforeParserFetchTemplateRevisionRecord( ?LinkTarget $contextTitle, LinkTarget $title, - bool &$skip, ?RevisionRecord &$revRecord ): bool { - if ( !self::$instance->searchingForData ) { - return true; - } - if ( self::$phTitle === null ) { - self::$phTitle = \Title::newFromText( 'UsingDataPlaceholderTitle', NS_MEDIAWIKI ); - } - - $title = self::$phTitle; - $skip = true; - - return false; - } -} diff --git a/extension.json b/extension.json index f694182..64a74aa 100644 --- a/extension.json +++ b/extension.json @@ -1,12 +1,12 @@ { "name": "UsingData", - "version": "2.0.0-hydra", + "version": "2.1.0", "author": "foxlit", "descriptionmsg": "usingdata-description", "license-name": "GPL-2.0-or-later", "type": "parserhook", "requires": { - "MediaWiki": ">= 1.29.0" + "MediaWiki": ">= 1.39.0" }, "MessagesDirs": { "UsingData": [ @@ -16,15 +16,20 @@ "ExtensionMessagesFiles": { "UsingDataMagic": "UsingData.i18n.magic.php" }, - "AutoloadClasses": { - "UsingDataHooks": "UsingDataHooks.php", - "UsingDataPPFrameDOM": "src/UsingDataPPFrameDOM.php" + "AutoloadNamespaces": { + "UsingData\\": "src/" + }, + "HookHandlers": { + "UsingDataHookHandler": { + "class": "UsingData\\UsingDataHooks", + "services": [ "MainConfig" ] + } }, "Hooks": { - "BeforeParserFetchTemplateRevisionRecord": "UsingDataHooks::onBeforeParserFetchTemplateRevisionRecord", - "GetMagicVariableIDs": "UsingDataHooks::onGetMagicVariableIDs", - "ParserFirstCallInit": "UsingDataHooks::onParserFirstCallInit", - "ParserGetVariableValueSwitch": "UsingDataHooks::ancestorNameVar" + "BeforeParserFetchTemplateRevisionRecord": "UsingDataHookHandler", + "GetMagicVariableIDs": "UsingDataHookHandler", + "ParserFirstCallInit": "UsingDataHookHandler", + "ParserGetVariableValueSwitch": "UsingDataHookHandler" }, "manifest_version": 1 } diff --git a/src/UsingDataHooks.php b/src/UsingDataHooks.php new file mode 100644 index 0000000..e5eefff --- /dev/null +++ b/src/UsingDataHooks.php @@ -0,0 +1,298 @@ +setFunctionHook( 'using', [ $this, 'usingParserFunction' ], SFH_OBJECT_ARGS ); + $parser->setFunctionHook( 'usingarg', [ $this, 'usingArgParserFunction' ], SFH_OBJECT_ARGS ); + $parser->setFunctionHook( 'data', [ $this, 'dataParserFunction' ], SFH_OBJECT_ARGS ); + $parser->setFunctionHook( 'ancestorname', [ $this, 'ancestorNameFunction' ], SFH_OBJECT_ARGS | SFH_NO_HASH ); + $parser->setHook( 'using', [ $this, 'usingTag' ] ); + } + + /** @inheritDoc */ + public function onGetMagicVariableIDs( &$variableIDs ) { + $variableIDs[] = 'parentname'; + $variableIDs[] = 'selfname'; + } + + /** Returns a UsingData frame for a given page */ + private function getDataFrame( $sourcePage, ?Title $title, Parser &$parser, PPFrame $frame ): UsingDataPPFrameDOM { + if ( isset( $this->dataFrames[$sourcePage] ) ) { + return $this->dataFrames[$sourcePage]; + } + + $this->dataFrames[$sourcePage] = new UsingDataPPFrameDOM( $frame, $sourcePage ); + if ( + ( + $sourcePage === '' || + Title::castFromPageReference( $parser->getPage() )->getPrefixedText() + ) && !$parser->getOptions()->getIsSectionPreview() ) { + return $this->dataFrames[$sourcePage]; + } + + [ $text, $fTitle ] = $parser->fetchTemplateAndTitle( $title ); + if ( is_object( $fTitle ) && $fTitle->getPrefixedText() != $sourcePage ) { + $this->dataFrames[$fTitle->getPrefixedText()] = $this->dataFrames[$sourcePage]; + } + + global $wgHooks; + if ( is_string( $text ) && $text != '' ) { + $this->searchingForData = true; + $clearStateHooks = $wgHooks['ParserClearState']; + // Other extensions tend to assume the hook is only called by wgParser and reset internal state + $wgHooks['ParserClearState'] = []; + $subParser = clone $parser; + $subParser->preprocess( $text, $fTitle, clone $parser->getOptions() ); + // We might've blocked access to templates while preprocessing; should not be cached + $subParser->clearState(); + $subParser->getOutput()->setText( $parser->getOutput()->getText() ); + $wgHooks['ParserClearState'] = empty( $wgHooks['ParserClearState'] ) + ? $clearStateHooks + : array_merge( $clearStateHooks, $wgHooks['ParserClearState'] ); + $parser->mPPNodeCount += $subParser->mPPNodeCount; + $this->searchingForData = false; + } + return $this->dataFrames[$sourcePage]; + } + + /** Returns the page title of the $depth ancestor of $frame; empty string if invalid */ + private function ancestorNameHandler( PPFrame $frame, int $depth ): string { + while ( $depth-- && $frame !== null ) { + $frame = $frame->parent ?? null; + } + return is_object( $frame?->title ) ? wfEscapeWikiText( $frame->title->getPrefixedText() ) : ''; + } + + /** Handles {{ANCESTORNAME:depth}} */ + public function ancestorNameFunction( Parser $parser, PPFrame $frame, array $args ) { + $arg = $frame->expand( $args[0] ); + $depth = max( 0, is_numeric( $arg ) ? (int)$arg : 1 ); + return [ $this->ancestorNameHandler( $frame, $depth ), 'noparse' => true ]; + } + + /** + * @inheritDoc + * Handles {{PARENTNAME}}, {{SELFNAME}}, {{ANCESTORNAME}} + */ + public function onParserGetVariableValueSwitch( $parser, &$variableCache, $magicWordId, &$ret, $frame ) { + if ( $magicWordId === 'parentname' ) { + $ret = $this->ancestorNameHandler( $frame, 1 ); + } + if ( $magicWordId === 'selfname' ) { + $ret = $this->ancestorNameHandler( $frame, 0 ); + } + } + + /** Parses common elements of #using syntax. */ + private function usingParse( Parser &$parser, PPFrame $frame, array $args ) { + $title = Title::newFromText( $this->sanitizeTitleName( $frame->expand( $args[0] ) ) ); + $sourcePage = $title?->getPrefixedText() ?? ''; + $sourceHash = $title?->getFragment() ?? ''; + $namedArgs = []; + + $one = null; + $two = null; + foreach ( $args as $key => $val ) { + if ( $key === 0 ) { + continue; + } + $bits = $val->splitArg(); + // It looks like indexes are now integers, it is a change from legacy implementation + if ( $bits['index'] === 1 ) { + $one = $frame->expand( $bits['value'] ); + } elseif ( $bits['index'] === 2 ) { + $two = $bits['value']; + } elseif ( $bits['index'] === '' ) { + $namedArgs[trim( $frame->expand( $bits['name'] ) )] = $bits['value']; + } + } + return [ $this->getDataFrame( $sourcePage, $title, $parser, $frame ), $sourceHash, $namedArgs, $one, $two ]; + } + + /** + * {{#using:Page#Hash|Template|Default|...}} parses Template using #data from Page's Hash fragment; or Default + * if no data from Page can be found. Named arguments override those in the #data tag. + */ + public function usingParserFunction( Parser $parser, PPFrame $frame, array $args ) { + if ( $this->searchingForData ) { + return ''; + } + + /** @var UsingDataPPFrameDOM $dframe */ + [ $dframe, $fragment, $namedArgs, $templateTitle, $defaultValue ] = $this->usingParse( $parser, $frame, $args ); + + if ( !$dframe->hasFragment( $fragment ) && $defaultValue !== null ) { + return $frame->expand( $defaultValue ); + } + [ $dom, $title ] = $this->fetchTemplate( $parser, $templateTitle ); + return $dframe->expandUsing( $frame, $title, $dom, $namedArgs, $fragment ); + } + + /** + * {{#usingarg:Page#Hash|Arg|Default}} + * returns the value of Arg data field on Page's Hash fragment, Default if undefined. + */ + public function usingArgParserFunction( Parser &$parser, PPFrame $frame, array $args ) { + if ( $this->searchingForData ) { + return ''; + } + /** @var UsingDataPPFrameDOM $dframe */ + [ $dframe, $fragment, $namedArgs, $argName, $defaultValue ] = $this->usingParse( $parser, $frame, $args ); + + $ret = $dframe->getArgumentForParser( + $parser, + UsingDataPPFrameDOM::normalizeFragment( $fragment ), + $argName, + $defaultValue === null ? '' : false + ); + return $ret !== false ? $ret : $frame->expand( $defaultValue ); + } + + /** + * ... + * expands ... using the data from Page's Hash fragment; Default if undefined. + * This tag relies on $parser->replaceVariables($text, $frame), which may prove fragile across MW versions. + * Should it break, $parser->recursiveTagParse($text, $frame), + * in combination with either modifying the markerType, or using + * insertStripItem directly, is a viable short-term alternative -- but one that call certain hooks prematurely, + * potentially causing other extensions to misbehave slightly. + */ + public function usingTag( $text, array $args, Parser $parser, PPFrame $frame ): array { + if ( $this->searchingForData ) { + return [ '', 'markerType' => 'none' ]; + } + + $source = isset( $args['page'] ) ? $parser->replaceVariables( $args['page'], $frame ) : ''; + unset( $args['page'] ); + $title = Title::newFromText( $this->sanitizeTitleName( $source ) ); + if ( $title ) { + $dframe = $this->getDataFrame( $title->getPrefixedText(), $title, $parser, $frame ); + if ( $dframe->hasFragment( $title->getFragment() ) ) { + $ovr = []; + unset( $args['default'] ); + foreach ( $args as $key => $val ) { + $ovr[$key] = $parser->replaceVariables( $val, $frame ); + } + return [ + $dframe->expandUsing( $frame, $frame->title, $text, $ovr, $title->getFragment(), true ), + 'markerType' => 'none' + ]; + } + } + + return [ + isset( $args['default'] ) ? $parser->replaceVariables( $args['default'], $frame ) : '', + 'markerType' => 'none' + ]; + } + + /** {{#data:Template#Hash|...}} specifies data-transcludable arguments for the page; may not be transcluded. */ + public function dataParserFunction( Parser &$parser, PPFrame $frame, $args ): string { + $templateTitle = $this->sanitizeTitleName( $frame->expand( $args[0] ) ); + $hostPage = $frame->title->getPrefixedText(); + unset( $args[0] ); + $fragment = ''; + + $templateTitleObj = Title::newFromText( $templateTitle, NS_TEMPLATE ); + if ( $templateTitleObj ) { + $fragment = $templateTitleObj->getFragment(); + } elseif ( str_starts_with( $templateTitle, '#' ) ) { + $fragment = substr( $templateTitle, 1 ); + } + + if ( $frame->depth == 0 || $this->searchingForData ) { + if ( !isset( $this->dataFrames[$hostPage] ) ) { + $this->dataFrames[$hostPage] = new UsingDataPPFrameDOM( $frame, $hostPage ); + } + $this->dataFrames[$hostPage]->addArgs( $frame, $args, $fragment ); + if ( $this->searchingForData ) { + return ''; + } + } + + if ( !$templateTitleObj ) { + return ''; + } + + [ $dom, $tTitle ] = $this->fetchTemplate( $parser, $templateTitleObj ); + $cframe = $frame->newChild( $args, $tTitle ); + $cframe->namedArgs['data-found'] = $frame->depth == 0 ? '3' : '2'; + $cframe->namedArgs['data-source'] = $hostPage; + $cframe->namedArgs['data-sourcee'] = wfEscapeWikiText( $hostPage ); + $cframe->namedArgs['data-fragment'] = $fragment; + $cframe->namedArgs['data-source-fragment'] = $hostPage . ( empty( $fragment ) ? '' : ( '#' . $fragment ) ); + return $cframe->expand( $dom ); + } + + /** Returns template text for transclusion. */ + private function fetchTemplate( Parser $parser, $template ): array { + if ( $template == '' || ( !is_string( $template ) && !is_object( $template ) ) ) { + return [ '', null ]; + } + + $title = is_object( $template ) ? $template : Title::newFromText( $template, NS_TEMPLATE ); + if ( !$title || + $title->getNamespace() === NS_SPECIAL || + ( + $this->config->has( 'NonincludableNamespaces' ) && + in_array( $title->getNamespace(), $this->config->get( 'NonincludableNamespaces' ) ) + ) + ) { + return $title ? [ '[[:' . $title->getPrefixedText() . ']]', $title ] : [ '[[:' . $template . ']]', null ]; + } + + [ $dom, $title ] = $parser->getTemplateDom( $title ); + return [ $dom ?: ( '[[:' . $title->getPrefixedText() . ']]' ), $title ]; + } + + /** @inheritDoc */ + public function onBeforeParserFetchTemplateRevisionRecord( + ?LinkTarget $contextTitle, + LinkTarget $title, + bool &$skip, + ?RevisionRecord &$revRecord + ) { + if ( !$this->searchingForData ) { + return true; + } + + $title = Title::newFromText( 'UsingDataPlaceholderTitle', NS_MEDIAWIKI ); + $skip = true; + + return false; + } + + private function sanitizeTitleName( string $title ): string { + $trimmedTitle = trim( $title ); + if ( str_contains( $trimmedTitle, '%' ) ) { + return str_replace( [ '<', '>' ], [ '<', '>' ], urldecode( $trimmedTitle ) ); + } + return $trimmedTitle; + } +} diff --git a/src/UsingDataPPFrameDOM.php b/src/UsingDataPPFrameDOM.php index 0b1bdd3..f5626c5 100644 --- a/src/UsingDataPPFrameDOM.php +++ b/src/UsingDataPPFrameDOM.php @@ -1,5 +1,11 @@ expandedArgs = []; } - if ( is_string( $text ) && $useRTP ) { - $ret = $this->expansionForParser->replaceVariables( $text, $this ); - } else { - $ret = $this->expand( $text === null ? '' : $text ); - } + $ret = is_string( $text ) && $useRTP ? + $this->expansionForParser->replaceVariables( $text, $this ) : + $this->expand( $text === null ? '' : $text ); $this->overrideArgs = $oldArgs; $this->expansionFragment = $oldFragment; @@ -106,7 +110,7 @@ public function getArgumentForParser( $parser, $normalizedFragment, $arg, $defau $text = $aar[1][$arg]; unset( $aar[1][$arg] ); $text = $aar[0]->expand( $text ); - if ( strpos( $text, Parser::MARKER_PREFIX ) !== false ) { + if ( str_contains( $text, Parser::MARKER_PREFIX ) ) { $text = $aar[0]->parser->serialiseHalfParsedText( ' ' . $text ); // MW bug 26731 } $this->serializedArgs[$arg] = $text; @@ -138,7 +142,7 @@ public function getArgument( $index ) { case 'data-fragment': return $this->expansionFragment; case 'data-source-fragment': - return $this->sourcePage . ( empty( $this->expansionFragment ) ? '' : ( '#' . $this->expansionFragment ) ); + return $this->sourcePage . ( empty( $this->expansionFragment ) ? '' : "#$this->expansionFragment" ); default: if ( is_array( $this->overrideArgs ) && isset( $this->overrideArgs[$index] ) ) { if ( is_object( $this->overrideArgs[$index] ) ) { @@ -146,7 +150,7 @@ public function getArgument( $index ) { } return $this->overrideArgs[$index]; } - $p = $this->expansionForParser === null ? $this->parent->parser : $this->expansionForParser; + $p = $this->expansionForParser ?? $this->parent->parser; return $this->getArgumentForParser( $p, $this->expansionFragmentN, $index, false ); } }