Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ParserHooksHandler
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 7
272
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 onParserFirstCallInit
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 mathTagHook
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 chemTagHook
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 mathPostTagHook
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 onParserAfterTidy
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 onParserOptionsRegister
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Math\HookHandlers;
4
5use MediaWiki\Extension\Math\Hooks\HookRunner;
6use MediaWiki\Extension\Math\MathConfig;
7use MediaWiki\Extension\Math\MathMathML;
8use MediaWiki\Extension\Math\MathMathMLCli;
9use MediaWiki\Extension\Math\MathRenderer;
10use MediaWiki\Extension\Math\Render\RendererFactory;
11use MediaWiki\Hook\ParserAfterTidyHook;
12use MediaWiki\Hook\ParserFirstCallInitHook;
13use MediaWiki\Hook\ParserOptionsRegisterHook;
14use MediaWiki\HookContainer\HookContainer;
15use MediaWiki\Logger\LoggerFactory;
16use MediaWiki\Parser\Parser;
17use MediaWiki\Parser\ParserOptions;
18use MediaWiki\User\Options\UserOptionsLookup;
19
20/**
21 * Hook handler for Parser hooks
22 */
23class ParserHooksHandler implements
24    ParserFirstCallInitHook,
25    ParserAfterTidyHook,
26    ParserOptionsRegisterHook
27{
28
29    /** @var int */
30    private $mathTagCounter = 1;
31
32    /** @var array[] renders delayed to be done as a batch [ MathRenderer, Parser ] */
33    private $mathLazyRenderBatch = [];
34
35    /** @var RendererFactory */
36    private $rendererFactory;
37
38    /** @var UserOptionsLookup */
39    private $userOptionsLookup;
40
41    /** @var HookRunner */
42    private $hookRunner;
43
44    /**
45     * @param RendererFactory $rendererFactory
46     * @param UserOptionsLookup $userOptionsLookup
47     * @param HookContainer $hookContainer
48     */
49    public function __construct(
50        RendererFactory $rendererFactory,
51        UserOptionsLookup $userOptionsLookup,
52        HookContainer $hookContainer
53    ) {
54        $this->rendererFactory = $rendererFactory;
55        $this->userOptionsLookup = $userOptionsLookup;
56        $this->hookRunner = new HookRunner( $hookContainer );
57    }
58
59    /**
60     * Register the <math> tag with the Parser.
61     *
62     * @param Parser $parser
63     */
64    public function onParserFirstCallInit( $parser ) {
65        $parser->setHook( 'math', [ $this, 'mathTagHook' ] );
66        // @deprecated the ce tag is deprecated in favour of chem cf. T153606
67        $parser->setHook( 'ce', [ $this, 'chemTagHook' ] );
68        $parser->setHook( 'chem', [ $this, 'chemTagHook' ] );
69    }
70
71    /**
72     * Callback function for the <math> parser hook.
73     *
74     * @param ?string $content (the LaTeX input)
75     * @param array $attributes
76     * @param Parser $parser
77     * @return array|string
78     */
79    public function mathTagHook( ?string $content, array $attributes, Parser $parser ) {
80        global $wgMathSvgRenderer;
81        $mode = $parser->getOptions()->getOption( 'math' );
82        if ( $mode === MathConfig::MODE_NATIVE_JAX ) {
83            $parser->getOutput()->addModules( [ 'ext.math.mathjax' ] );
84            $mode = MathConfig::MODE_NATIVE_MML;
85        }
86        $renderer = $this->rendererFactory->getRenderer( $content ?? '', $attributes, $mode );
87
88        $parser->getOutput()->addModuleStyles( [ 'ext.math.styles' ] );
89        if ( array_key_exists( "qid", $attributes ) ) {
90            $parser->getOutput()->addModules( [ 'ext.math.popup' ] );
91        }
92        if ( $wgMathSvgRenderer === 'restbase' && $mode == MathConfig::MODE_MATHML ) {
93            $marker = Parser::MARKER_PREFIX .
94                '-postMath-' . sprintf( '%08X', $this->mathTagCounter++ ) .
95                Parser::MARKER_SUFFIX;
96            $this->mathLazyRenderBatch[$marker] = [ $renderer, $parser ];
97            return $marker;
98        }
99        return [ $this->mathPostTagHook( $renderer, $parser ), 'markerType' => 'nowiki' ];
100    }
101
102    /**
103     * Callback function for the <ce> parser hook.
104     *
105     * @param ?string $content (the LaTeX input)
106     * @param array $attributes
107     * @param Parser $parser
108     * @return array|string
109     */
110    public function chemTagHook( ?string $content, array $attributes, Parser $parser ) {
111        $attributes['chem'] = true;
112        return $this->mathTagHook( '\ce{' . $content . '}', $attributes, $parser );
113    }
114
115    /**
116     * Callback function for the <math> parser hook.
117     *
118     * @param MathRenderer $renderer
119     * @param Parser $parser
120     * @return string
121     */
122    private function mathPostTagHook( MathRenderer $renderer, Parser $parser ) {
123        $checkResult = $renderer->checkTeX();
124
125        if ( $checkResult !== true ) {
126            $renderer->addTrackingCategories( $parser );
127            return $renderer->getLastError();
128        }
129
130        if ( $renderer->render() ) {
131            LoggerFactory::getInstance( 'Math' )->debug( "Rendering successful. Writing output" );
132            $renderedMath = $renderer->getHtmlOutput();
133            $renderer->addTrackingCategories( $parser );
134        } else {
135            LoggerFactory::getInstance( 'Math' )->warning(
136                "Rendering failed. Printing error message." );
137            // Set a short parser cache time (10 minutes) after encountering
138            // render issues, but not syntax issues.
139            $parser->getOutput()->updateCacheExpiry( 600 );
140            $renderer->addTrackingCategories( $parser );
141            return $renderer->getLastError();
142        }
143        $this->hookRunner->onMathFormulaPostRender(
144            $parser, $renderer, $renderedMath
145        ); // Enables indexing of math formula
146
147        // Writes cache if rendering was successful
148        $renderer->writeCache();
149
150        return $renderedMath;
151    }
152
153    /**
154     * @param Parser $parser
155     * @param string &$text
156     */
157    public function onParserAfterTidy( $parser, &$text ) {
158        global $wgMathoidCli;
159        $renderers = array_column( $this->mathLazyRenderBatch, 0 );
160        if ( $wgMathoidCli ) {
161            MathMathMLCli::batchEvaluate( $renderers );
162        } else {
163            MathMathML::batchEvaluate( $renderers );
164        }
165        foreach ( $this->mathLazyRenderBatch as $key => [ $renderer, $renderParser ] ) {
166            $value = $this->mathPostTagHook( $renderer, $renderParser );
167            $count = 0;
168            $text = str_replace( $key, $value, $text, $count );
169            if ( $count ) {
170                // This hook might be called multiple times. However once the tag is rendered the job is done.
171                unset( $this->mathLazyRenderBatch[ $key ] );
172            }
173        }
174    }
175
176    public function onParserOptionsRegister( &$defaults, &$inCacheKey, &$lazyLoad ) {
177        $defaults['math'] = $this->userOptionsLookup->getDefaultOption( 'math' );
178        $inCacheKey['math'] = true;
179        $lazyLoad['math'] = function ( ParserOptions $options ) {
180            return MathConfig::normalizeRenderingMode(
181                $this->userOptionsLookup->getOption( $options->getUserIdentity(), 'math' )
182            );
183        };
184    }
185}