setHook( $lang['tag'], fn ( $input, $args ) => renderCode( $input, $lang, $parser->getOutput(), $args ) ); } } public static function onContentHandlerDefaultModelFor( Title $title, &$model ) { if ( getLangByPageName( $title->getDBkey() ) ) { $model = CodeContent::MODEL; return false; } else { return true; } } } function getLangByPageName( string $dbKey ) { global $wgCode_languages; $suffix = '.' . pathinfo( $dbKey, PATHINFO_EXTENSION ); return current( array_filter( $wgCode_languages, fn( $lang ) => $lang['suffix'] == $suffix ) ); } function pre( $code, $attr ) { return Xml::element( 'pre', Sanitizer::validateTagAttributes( $attr, 'pre' ), $code ); } /** * Render the given code and return the HTML. * * @param string $code The code to be rendered. * @param ? $lang The language configuration. * @param OutputPage|ParserOutput|null $outputPage * @param ?array $tagAttrs Associative array of tag attributes or null in case of a code page. * @return string The rendered HTML. */ function renderCode( string $code, $lang, $outputPage, ?array $tagAttrs = null ) { if ( ExtensionRegistry::getInstance()->isLoaded( 'SyntaxHighlight' ) ) { $result = \SyntaxHighlight::highlight( $code, $lang['pygmentsLexer'], $tagAttrs ?? [ 'line' => true ] ); if ( $result->isOk() ) { $codeBlock = $result->getValue(); $outputPage->addModuleStyles( [ 'ext.pygments' ] ); } else { $codeBlock = pre( $code, $tagAttrs ); } } else { $codeBlock = pre( $code, $tagAttrs ); } $actions = ''; foreach ( $lang['actions'] as $action => $url ) { if ( str_contains( $url, '$url' ) ) { // FUTURE: it would be nice to still display such actions for code pages continue; } $encodedCode = rawurlencode( trim( $code ) ); // The wfMessage allows admins to create MediaWiki:codeaction-label with a template call // if they want to enable their users to customize or localize the code action labels. $actions .= Xml::openElement( 'a', [ 'href' => str_replace( '$code', $encodedCode, $url ) ] ); $actions .= wfMessage( "codeaction-label", $action )->parse(); $actions .= Xml::closeElement( 'a' ) . ' '; } $additionalOutput = ''; $scribuntoModule = $lang['scribuntoModule'] ?? null; if ( $scribuntoModule && ExtensionRegistry::getInstance()->isLoaded( 'Scribunto' ) ) { $callFunction = static function ( Parser $parser, string $function, array $extraArgs = [] ) use ( $scribuntoModule, $code, $lang, $tagAttrs ) { $args = [ $scribuntoModule, $function, $code, $lang['tag'], $tagAttrs == null ]; $args = array_merge( $args, $extraArgs ); if ( $tagAttrs ) { $args = array_merge( $args, $tagAttrs ); } $frame = $parser->getPreprocessor()->newFrame(); $res = $parser->callParserFunction( $frame, '#invoke', $args ); if ( !$res['found'] ) { throw new FatalError( "unexpected condition in Code extension: the Scribunto module is loaded but the #invoke parser function wasn't found" ); } return Sanitizer::removeSomeTags( $res['text'], [ 'extraTags' => [ 'a' ], 'extra' ] ); }; if ( $tagAttrs ) { $parser = MediaWikiServices::getInstance()->getParser(); } else { $parser = MediaWikiServices::getInstance()->getParserFactory()->create(); $parser->startExternalParse( null, ParserOptions::newFromAnon(), Parser::OT_HTML ); } $codeBlock = $callFunction( $parser, 'formatCode', [ $codeBlock ] ); $additionalOutput = $callFunction( $parser, 'additionalOutput' ); } return $codeBlock . $actions . $additionalOutput; } class ContentHandler extends TextContentHandler { public function __construct( $modelId = CodeContent::MODEL ) { parent::__construct( $modelId, [ CONTENT_FORMAT_TEXT ] ); } public function fillParserOutput( Content $content, ContentParseParams $params, ParserOutput &$output ) { $lang = getLangByPageName( $params->getPage()->getDBkey() ); $out = ''; if ( !$lang ) { global $wgCode_languages; $supportedSuffixes = implode( ', ', array_map( fn( $lang ) => $lang['suffix'], $wgCode_languages ) ); MediaWikiServices::getInstance()->getTrackingCategories() ->addTrackingCategory( $output, 'codepage-error-category', $params->getPage() ); $out = Html::errorBox( wfMessage( 'codepage-unknown-suffix', 'code', "[$supportedSuffixes]" ) ); $lang = [ 'pygmentsLexer' => 'text', 'actions' => [], ]; } $out .= renderCode( $content->getText(), $lang, $output ); $output->setText( $out ); } protected function getContentClass() { return CodeContent::class; } } class CodeContent extends TextContent { // Has to match the ContentHandlers entry in extension.json. public const MODEL = 'code'; public function __construct( $text, $model_id = self::MODEL ) { parent::__construct( $text, $model_id ); } } class SpecialCodeAction extends SpecialPage { public function __construct() { parent::__construct( 'CodeAction' ); } public function execute( $par ) { $namespacesWithCodePages = $this->getConfig()->get( 'Code_namespacesWithCodePages' ); $languages = $this->getConfig()->get( 'Code_languages' ); if ( empty( $namespacesWithCodePages ) ) { $exampleConfig = htmlspecialchars( file_get_contents( __DIR__ . '/examples/namespacesWithCodePages.php' ) ); throw new ErrorPageError( 'codeactions-unavailable-title', 'codeactions-unavailable-no-namespaces', "
$exampleConfig
" ); } $exampleLang = current( array_filter( $languages, fn( $lang ) => count( $lang['actions'] ) > 0 ) ); if ( !$exampleLang ) { $exampleConfig = htmlspecialchars( file_get_contents( __DIR__ . '/examples/languages.php' ) ); throw new ErrorPageError( 'codeactions-unavailable-title', 'codeactions-unavailable-no-lang-with-actions', "
$exampleConfig
" ); } $contLang = MediaWikiServices::getInstance()->getContentLanguage(); $output = $this->getOutput(); $args = explode( '/', $par, 2 ); if ( count( $args ) != 2 ) { $formatter = MediaWikiServices::getInstance()->getTitleFormatter(); $title = $formatter->formatTitle( array_keys( $namespacesWithCodePages )[0], 'Example' . $exampleLang['suffix'] ); $action = htmlspecialchars( array_keys( $exampleLang['actions'] )[0] ); $output->setPageTitle( $this->msg( 'notargettitle' ) ); $output->addWikiMsg( 'codeaction-howto', $this->getPageTitle(), $action, $title ); $output->addWikiTextAsInterface( Xml::element( 'h2', null, $this->msg( 'codeactions-available' ) ) ); $output->addHTML( Xml::buildTable( array_map( fn ( $lang ) => [ $lang['suffix'], implode( ', ', array_keys( $lang['actions'] ) ) ], $languages ), [ 'class' => 'wikitable' ], [ $this->msg( 'code-pagesuffix' ), $this->msg( 'codeactions-available' ) ] ) ); return; } [ $action, $pagename ] = $args; $title = Title::newFromText( $pagename ); if ( !$title ) { throw new ErrorPageError( 'badtitle', 'title-invalid' ); } if ( !array_key_exists( $title->getNamespace(), $namespacesWithCodePages ) ) { $nsName = $this->getContext()->getLanguage()->getFormattedNsText( $title->getNamespace() ); throw new ErrorPageError( 'codeaction-unsupported-namespace-title', 'codeaction-unsupported-namespace-text', $nsName ); } $lang = getLangByPageName( $title->getDBkey() ); if ( !$lang ) { $supportedSuffixes = implode( ', ', array_map( fn( $lang ) => $lang['suffix'], $languages ) ); throw new ErrorPageError( 'codeaction-unknown-suffix-title', 'codeaction-unknown-suffix-text', [ $pagename, "[$supportedSuffixes]" ] ); } $url = $lang['actions'][$action] ?? null; if ( !$url ) { $supportedActions = implode( ', ', array_keys( $lang['actions'] ) ); throw new ErrorPageError( 'codeaction-unknown-action-title', 'codeaction-unknown-action-text', [ $action, "[$supportedActions]" ] ); } if ( str_contains( $url, '$code' ) ) { $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); $errors = $permissionManager->getPermissionErrors( 'read', $this->getUser(), $title ); if ( !empty( $errors ) ) { $output->showPermissionsErrorPage( $errors, 'read' ); return; } $content = WikiPage::factory( $title )->getContent(); if ( $content == null ) { throw new ErrorPageError( 'codeaction-page-not-found-title', 'codeaction-page-not-found-text', $title ); } $content = ContentHandler::getContentText( $content ); $codePrefix = $lang['codePrefix'] ?? null; if ( $codePrefix ) { $content = str_replace( '$url', $title->getFullURL(), $codePrefix ) . "\n\n" . $content; } $url = str_replace( '$code', rawurlencode( $content ), $url ); } else { $url = str_replace( '$url', rawurlencode( $title->getFullURL() ), $url ); } $output->redirect( $url ); } }