From c3c56543c6febe8fd4c66dda5551abe965efff7f Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Thu, 1 Dec 2022 14:15:07 +0100 Subject: initial commit --- Code.php | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 Code.php (limited to 'Code.php') diff --git a/Code.php b/Code.php new file mode 100644 index 0000000..ca02b3c --- /dev/null +++ b/Code.php @@ -0,0 +1,234 @@ +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() ) { + $out = $result->getValue(); + $outputPage->addModuleStyles( [ 'ext.pygments' ] ); + } else { + $out = pre( $code, $tagAttrs ); + } + } else { + $out = pre( $code, $tagAttrs ); + } + + foreach ( $lang['linkifiers'] as $regex => $href ) { + $out = preg_replace_callback( $regex, fn( $match ) => + Xml::element( 'a', [ + 'href' => str_replace( '$1', $match[0], $href ), + 'tabindex' => -1 + ], $match[0] ), + $out ); + } + + 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. + $out .= Xml::openElement( 'a', [ 'href' => str_replace( '$code', $encodedCode, $url ) ] ); + $out .= wfMessage( "codeaction-label", $action )->parse(); + $out .= Xml::closeElement( 'a' ) . ' '; + } + + return $out; +} + +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' => [], + 'linkifiers' => [], + ]; + } + $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 ); + } +} -- cgit v1.2.3