Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 55
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 / 55
0.00% covered (danger)
0.00%
0 / 7
210
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 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 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\User\Options\UserOptionsLookup;
17use Parser;
18use ParserOptions;
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        $mode = $parser->getOptions()->getOption( 'math' );
81        $renderer = $this->rendererFactory->getRenderer( $content ?? '', $attributes, $mode );
82
83        $parser->getOutput()->addModuleStyles( [ 'ext.math.styles' ] );
84        if ( array_key_exists( "qid", $attributes ) ) {
85            $parser->getOutput()->addModules( [ 'ext.math.popup' ] );
86        }
87        if ( $mode == MathConfig::MODE_MATHML ) {
88            $marker = Parser::MARKER_PREFIX .
89                '-postMath-' . sprintf( '%08X', $this->mathTagCounter++ ) .
90                Parser::MARKER_SUFFIX;
91            $this->mathLazyRenderBatch[$marker] = [ $renderer, $parser ];
92            return $marker;
93        }
94        return [ $this->mathPostTagHook( $renderer, $parser ), 'markerType' => 'nowiki' ];
95    }
96
97    /**
98     * Callback function for the <ce> parser hook.
99     *
100     * @param ?string $content (the LaTeX input)
101     * @param array $attributes
102     * @param Parser $parser
103     * @return array|string
104     */
105    public function chemTagHook( ?string $content, array $attributes, Parser $parser ) {
106        $attributes['chem'] = true;
107        return $this->mathTagHook( '\ce{' . $content . '}', $attributes, $parser );
108    }
109
110    /**
111     * Callback function for the <math> parser hook.
112     *
113     * @param MathRenderer $renderer
114     * @param Parser $parser
115     * @return string
116     */
117    private function mathPostTagHook( MathRenderer $renderer, Parser $parser ) {
118        $checkResult = $renderer->checkTeX();
119
120        if ( $checkResult !== true ) {
121            $renderer->addTrackingCategories( $parser );
122            return $renderer->getLastError();
123        }
124
125        if ( $renderer->render() ) {
126            LoggerFactory::getInstance( 'Math' )->debug( "Rendering successful. Writing output" );
127            $renderedMath = $renderer->getHtmlOutput();
128            $renderer->addTrackingCategories( $parser );
129        } else {
130            LoggerFactory::getInstance( 'Math' )->warning(
131                "Rendering failed. Printing error message." );
132            // Set a short parser cache time (10 minutes) after encountering
133            // render issues, but not syntax issues.
134            $parser->getOutput()->updateCacheExpiry( 600 );
135            $renderer->addTrackingCategories( $parser );
136            return $renderer->getLastError();
137        }
138        $this->hookRunner->onMathFormulaPostRender(
139            $parser, $renderer, $renderedMath
140        ); // Enables indexing of math formula
141
142        // Writes cache if rendering was successful
143        $renderer->writeCache();
144
145        return $renderedMath;
146    }
147
148    /**
149     * @param Parser $parser
150     * @param string &$text
151     */
152    public function onParserAfterTidy( $parser, &$text ) {
153        global $wgMathoidCli;
154        $renderers = array_column( $this->mathLazyRenderBatch, 0 );
155        if ( $wgMathoidCli ) {
156            MathMathMLCli::batchEvaluate( $renderers );
157        } else {
158            MathMathML::batchEvaluate( $renderers );
159        }
160        foreach ( $this->mathLazyRenderBatch as $key => [ $renderer, $renderParser ] ) {
161            $value = $this->mathPostTagHook( $renderer, $renderParser );
162            $count = 0;
163            $text = str_replace( $key, $value, $text, $count );
164            if ( $count ) {
165                // This hook might be called multiple times. However once the tag is rendered the job is done.
166                unset( $this->mathLazyRenderBatch[ $key ] );
167            }
168        }
169    }
170
171    public function onParserOptionsRegister( &$defaults, &$inCacheKey, &$lazyLoad ) {
172        $defaults['math'] = $this->userOptionsLookup->getDefaultOption( 'math' );
173        $inCacheKey['math'] = true;
174        $lazyLoad['math'] = function ( ParserOptions $options ) {
175            return MathConfig::normalizeRenderingMode(
176                $this->userOptionsLookup->getOption( $options->getUserIdentity(), 'math' )
177            );
178        };
179    }
180}