Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
60.92% covered (warning)
60.92%
106 / 174
41.86% covered (danger)
41.86%
18 / 43
CRAP
0.00% covered (danger)
0.00%
0 / 1
MathRenderer
60.92% covered (warning)
60.92%
106 / 174
41.86% covered (danger)
41.86%
18 / 43
451.51
0.00% covered (danger)
0.00%
0 / 1
 __construct
60.00% covered (warning)
60.00%
12 / 20
0.00% covered (danger)
0.00%
0 / 1
8.30
 getRenderer
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 render
n/a
0 / 0
n/a
0 / 0
0
 getHtmlOutput
n/a
0 / 0
n/a
0 / 0
0
 getError
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getInputHash
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 readFromCache
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
2.09
 dbInArray
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 initializeFromCache
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
5.03
 writeToCache
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 dbOutArray
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 setRestbaseInterface
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 hasWarnings
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 addTrackingCategories
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getChecker
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getAttributes
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 writeCache
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 getTex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMode
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 setTex
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getMathml
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 setMathml
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getParams
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setParams
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isChanged
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isPurge
41.67% covered (danger)
41.67%
5 / 12
0.00% covered (danger)
0.00%
0 / 1
13.15
 setPurge
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getLastError
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setMathStyle
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getMathStyle
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isTexSecure
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 checkTeX
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 isInDatabase
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getUserInputTex
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getID
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setID
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setSvg
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getSvg
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 getMathTableName
n/a
0 / 0
n/a
0 / 0
0
 getModeName
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 setInputType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInputType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doCheck
50.00% covered (danger)
50.00%
5 / 10
0.00% covered (danger)
0.00%
0 / 1
4.12
 debug
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCacheKey
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * MediaWiki math extension
4 *
5 * @copyright 2002-2012 Tomasz Wegrzanowski, Brion Vibber, Moritz Schubotz,
6 * and other MediaWiki contributors
7 * @license GPL-2.0-or-later
8 *
9 * @file
10 */
11
12namespace MediaWiki\Extension\Math;
13
14use MediaWiki\Extension\Math\InputCheck\BaseChecker;
15use MediaWiki\Logger\LoggerFactory;
16use MediaWiki\MediaWikiServices;
17use MediaWiki\Parser\Sanitizer;
18use Message;
19use Parser;
20use Psr\Log\LoggerInterface;
21use RequestContext;
22use StringUtils;
23use WANObjectCache;
24
25/**
26 * Abstract base class with static methods for rendering the <math> tags using
27 * different technologies. These static methods create a new instance of the
28 * extending classes and render the math tags based on the mode setting of the user.
29 * Furthermore, this class handles the caching of the rendered output.
30 *
31 * @author Tomasz Wegrzanowski
32 * @author Brion Vibber
33 * @author Moritz Schubotz
34 */
35abstract class MathRenderer {
36
37    // REPRESENTATIONS OF THE MATHEMATICAL CONTENT
38    /** @var ?string tex representation */
39    protected $tex = '';
40    /** @var string MathML content and presentation */
41    protected $mathml = '';
42    /** @var string SVG layout only (no semantics) */
43    protected $svg = '';
44    /** @var string the original user input string (which was used to calculate the inputhash) */
45    protected $userInputTex = '';
46    // FURTHER PROPERTIES OF THE MATHEMATICAL CONTENT
47    /** @var string ('inlineDisplaystyle'|'display'|'inline'|'linebreak') the rendering style */
48    protected $mathStyle = 'inlineDisplaystyle';
49    /** @var array with userdefined parameters passed to the extension (not used) */
50    protected $params = [];
51    /** @var string a userdefined identifier to link to the equation. */
52    protected $id = '';
53
54    // STATE OF THE CLASS INSTANCE
55    /** @var bool has variable tex been security-checked */
56    protected $texSecure = false;
57    /** @var bool has the mathematical content changed */
58    protected $changed = false;
59    /** @var bool is there a database entry for the mathematical content */
60    protected $storedInCache = null;
61    /** @var bool is there a request to purge the existing mathematical content */
62    protected $purge = false;
63    /** @var string with last occurred error */
64    protected $lastError = '';
65    /** @var string binary packed inputhash */
66    protected $inputHash = '';
67    /** @var string rendering mode */
68    protected $mode = MathConfig::MODE_MATHML;
69    /** @var string input type */
70    protected $inputType = 'tex';
71    /** @var MathRestbaseInterface used for checking */
72    protected $rbi;
73    /** @var array with rendering warnings */
74    protected $warnings;
75    /** @var LoggerInterface */
76    private $logger;
77
78    private WANObjectCache $cache;
79
80    /**
81     * Constructs a base MathRenderer
82     *
83     * @param string $tex (optional) LaTeX markup
84     * @param array $params (optional) HTML attributes
85     * @param WANObjectCache|null $cache (optional)
86     */
87    public function __construct( string $tex = '', $params = [], $cache = null ) {
88        $this->cache = $cache ?? MediaWikiServices::getInstance()->getMainWANObjectCache();
89        $this->params = $params;
90        if ( isset( $params['id'] ) ) {
91            $this->id = $params['id'];
92        }
93        if ( isset( $params['display'] ) ) {
94            $layoutMode = $params['display'];
95            if ( $layoutMode == 'block' ) {
96                $this->mathStyle = 'display';
97                $tex = '{\displaystyle ' . $tex . '}';
98                $this->inputType = 'tex';
99            } elseif ( $layoutMode == 'inline' ) {
100                $this->mathStyle = 'inline';
101                $this->inputType = 'inline-tex';
102                $tex = '{\textstyle ' . $tex . '}';
103            } elseif ( $layoutMode == 'linebreak' ) {
104                $this->mathStyle = 'linebreak';
105                $tex = '\[ ' . $tex . ' \]';
106            }
107        }
108        // TODO: Implement caching for attributes of the math tag
109        // Currently the key for the database entry relating to an equation
110        // is md5($tex) the new option to determine if the tex input
111        // is rendered in displaystyle or textstyle would require a database
112        // layout change to use a composite key e.g. (md5($tex),$mathStyle).
113        // As a workaround we use the prefix \displaystyle so that the key becomes
114        // md5((\{\\displaystyle|\{\\textstyle)?\s?$tex\}?)
115        // The new value of $tex string describes now how the rendering should look like.
116        // The variable MathRenderer::mathStyle determines if the rendered equation should
117        // be centered in a new line, or just in be displayed in the current line.
118        $this->userInputTex = $tex;
119        $this->tex = $tex;
120        $this->logger = LoggerFactory::getInstance( 'Math' );
121    }
122
123    /**
124     * Static factory method for getting a renderer based on mode
125     *
126     * @deprecated since 3.0.0. Use Math.RendererFactory service instead.
127     * @param string $tex LaTeX markup
128     * @param array $params HTML attributes
129     * @param string $mode indicating rendering mode
130     * @return self appropriate renderer for mode
131     */
132    public static function getRenderer( $tex, $params = [], $mode = MathConfig::MODE_MATHML ) {
133        return MediaWikiServices::getInstance()
134            ->get( 'Math.RendererFactory' )
135            ->getRenderer( $tex, $params, $mode );
136    }
137
138    /**
139     * Performs the rendering
140     *
141     * @return bool if rendering was successful.
142     */
143    abstract public function render();
144
145    /**
146     * @return string Html output that is embedded in the page
147     */
148    abstract public function getHtmlOutput();
149
150    /**
151     * texvc error messages
152     * TODO: update to MathML
153     * Returns an internationalized HTML error string
154     *
155     * @param string $msg message key for specific error
156     * @param string ...$parameters zero or more message
157     *  parameters for specific error
158     *
159     * @return string HTML error string
160     */
161    public function getError( $msg, ...$parameters ) {
162        $mf = wfMessage( 'math_failure' )->inContentLanguage()->escaped();
163        $errmsg = wfMessage( $msg, $parameters )->inContentLanguage()->escaped();
164        $source = htmlspecialchars( str_replace( "\n", ' ', $this->tex ) );
165        return "<strong class=\"error texerror\">$mf ($errmsg): $source</strong>\n";
166    }
167
168    /**
169     * Return hash of input
170     *
171     * @return string hash
172     */
173    public function getInputHash(): string {
174        if ( !$this->inputHash ) {
175            $this->inputHash = hash( 'md5', // xxh128 might be better when dropping php 7 support
176                $this->mode .
177                $this->userInputTex .
178                implode( $this->params )
179            );
180        }
181        return $this->inputHash;
182    }
183
184    /**
185     * Reads rendering data from database
186     *
187     * @return bool true if read successfully, false otherwise
188     */
189    public function readFromCache(): bool {
190        $rpage = $this->cache->get( $this->getCacheKey() );
191        if ( $rpage !== false ) {
192            $this->initializeFromCache( $rpage );
193            $this->storedInCache = true;
194            return true;
195        } else {
196            # Missing from the database and/or the render cache
197            $this->storedInCache = false;
198            return false;
199        }
200    }
201
202    /**
203     * @return array with the database column names
204     */
205    protected function dbInArray() {
206        $in = [ 'math_inputhash',
207            'math_mathml',
208            'math_inputtex',
209            'math_tex',
210            'math_svg'
211        ];
212        return $in;
213    }
214
215    /**
216     * Reads the values from the database but does not overwrite set values with empty values
217     * @param array $rpage (a database row)
218     */
219    public function initializeFromCache( $rpage ) {
220        $this->inputHash = $rpage['math_inputhash']; // MUST NOT BE NULL
221        if ( isset( $rpage['math_mathml'] ) ) {
222            $this->mathml = $rpage['math_mathml'];
223        }
224        if ( isset( $rpage['math_inputtex'] ) ) {
225            $this->userInputTex = $rpage['math_inputtex'];
226        }
227        if ( isset( $rpage['math_tex'] ) ) {
228            $this->tex = $rpage['math_tex'];
229        }
230        if ( isset( $rpage['math_svg'] ) ) {
231            $this->svg = $rpage['math_svg'];
232        }
233        $this->changed = false;
234    }
235
236    /**
237     * Writes rendering entry to cache.
238     *
239     * WARNING: Use writeCache() instead of this method to be sure that all
240     * renderer specific (such as squid caching) are taken into account.
241     * This function stores the values that are currently present in the class
242     * to the cache even if they are empty.
243     *
244     * This function can be seen as protected function.
245     */
246    public function writeToCache() {
247        $outArray = $this->dbOutArray();
248        $this->cache->set( $this->getCacheKey(), $outArray );
249    }
250
251    /**
252     * Gets an array that matches the variables of the class to the database columns
253     * @return array
254     */
255    protected function dbOutArray() {
256        $out = [
257            'math_inputhash' => $this->getInputHash(),
258            'math_mathml' => $this->mathml,
259            'math_inputtex' => $this->userInputTex,
260            'math_tex' => $this->tex,
261            'math_svg' => $this->svg,
262            'math_mode' => $this->mode
263        ];
264        return $out;
265    }
266
267    /**
268     * @param MathRestbaseInterface $param
269     */
270    public function setRestbaseInterface( $param ) {
271        $this->rbi = $param;
272        $this->rbi->setPurge( $this->isPurge() );
273    }
274
275    public function hasWarnings() {
276        if ( is_array( $this->warnings ) && count( $this->warnings ) ) {
277            return true;
278        }
279
280        return false;
281    }
282
283    /**
284     * Adds tracking categories to the parser
285     *
286     * @param Parser $parser
287     */
288    public function addTrackingCategories( $parser ) {
289        if ( !$this->checkTeX() ) {
290            $parser->addTrackingCategory( 'math-tracking-category-error' );
291        }
292        if ( $this->lastError ) {
293            // Add a tracking category specialized on render errors.
294            $parser->addTrackingCategory( 'math-tracking-category-render-error' );
295        }
296    }
297
298    protected function getChecker(): BaseChecker {
299        return Math::getCheckerFactory()
300            ->newDefaultChecker( $this->tex, $this->getInputType(), $this->rbi, $this->isPurge() );
301    }
302
303    /**
304     * Returns sanitized attributes
305     *
306     * @param string $tag element name
307     * @param array $defaults default attributes
308     * @param array $overrides attributes to override defaults
309     * @return array HTML attributes
310     */
311    protected function getAttributes( $tag, $defaults = [], $overrides = [] ) {
312        $attribs = Sanitizer::validateTagAttributes( $this->params, $tag );
313        $attribs = Sanitizer::mergeAttributes( $defaults, $attribs );
314        return Sanitizer::mergeAttributes( $attribs, $overrides );
315    }
316
317    /**
318     * Writes cache. Writes the database entry if values were changed
319     * @return bool
320     */
321    public function writeCache() {
322        $this->debug( 'Writing of cache requested' );
323        if ( $this->isChanged() ) {
324            $this->debug( 'Change detected. Perform writing' );
325            $this->writeToCache();
326            return true;
327        } else {
328            $this->debug( "Nothing was changed. Don't write to database" );
329            return false;
330        }
331    }
332
333    /**
334     * Gets TeX markup
335     *
336     * @return string TeX markup
337     */
338    public function getTex() {
339        return $this->tex;
340    }
341
342    /**
343     * Gets the rendering mode
344     *
345     * @return string
346     */
347    public function getMode() {
348        return $this->mode;
349    }
350
351    /**
352     * Sets the rendering mode
353     * @param string $newMode element of the array $wgMathValidModes
354     * @return bool
355     */
356    public function setMode( $newMode ) {
357        if ( Math::getMathConfig()->isValidRenderingMode( $newMode ) ) {
358            $this->mode = $newMode;
359            return true;
360        } else {
361            return false;
362        }
363    }
364
365    /**
366     * Sets the TeX code
367     *
368     * @param ?string $tex
369     */
370    public function setTex( ?string $tex ) {
371        if ( $this->tex != $tex ) {
372            $this->changed = true;
373            $this->tex = $tex;
374        }
375    }
376
377    /**
378     * Gets the MathML XML element
379     * @return string in UTF-8 encoding
380     */
381    public function getMathml() {
382        if ( !StringUtils::isUtf8( $this->mathml ) ) {
383            $this->setMathml( '' );
384        }
385        return $this->mathml;
386    }
387
388    /**
389     * @param string $mathml use UTF-8 encoding
390     */
391    public function setMathml( $mathml ) {
392        $this->changed = true;
393        $this->mathml = $mathml;
394    }
395
396    /**
397     * Get the attributes of the math tag
398     *
399     * @return array
400     */
401    public function getParams() {
402        return $this->params;
403    }
404
405    /**
406     * @param array $params
407     */
408    public function setParams( $params ) {
409        // $changed is not set to true here, because the attributes do not affect
410        // the rendering in the current implementation.
411        // If this behavior will change in the future $this->tex is no longer a
412        // primary key and the input hash cannot be calculate form $this->tex
413        // only. See the discussion 'Tag extensions in Block mode' on wikitech-l.
414        $this->params = $params;
415    }
416
417    /**
418     * Checks if the instance was modified i.e., because math was rendered
419     *
420     * @return bool true if something was changed false otherwise
421     */
422    public function isChanged() {
423        return $this->changed;
424    }
425
426    /**
427     * Checks if there is an explicit user request to rerender the math-tag.
428     * @return bool purge state
429     */
430    public function isPurge() {
431        if ( $this->purge ) {
432            return true;
433        }
434        $refererHeader = RequestContext::getMain()->getRequest()->getHeader( 'REFERER' );
435        if ( $refererHeader ) {
436            $url = parse_url( $refererHeader, PHP_URL_QUERY );
437            if ( !is_string( $url ) ) {
438                return false;
439            }
440            parse_str( $url, $refererParam );
441            if ( isset( $refererParam['action'] ) && $refererParam['action'] === 'purge' ) {
442                $this->logger->debug( 'Re-Rendering on user request' );
443                return true;
444            }
445        }
446        return false;
447    }
448
449    /**
450     * Sets purge. If set to true the render is forced to rerender and must not
451     * use a cached version.
452     * @param bool $purge
453     */
454    public function setPurge( $purge = true ) {
455        $this->changed = true;
456        $this->purge = $purge;
457    }
458
459    public function getLastError() {
460        return $this->lastError;
461    }
462
463    /**
464     * @param string $mathStyle ('inlineDisplaystyle'|'display'|'inline')
465     */
466    public function setMathStyle( $mathStyle = 'display' ) {
467        if ( $this->mathStyle !== $mathStyle ) {
468            $this->mathStyle = $mathStyle;
469            $this->changed = true;
470            $this->inputType = $mathStyle === 'inline' ? 'inline-tex' : 'tex';
471        }
472    }
473
474    /**
475     * Returns the value of the DisplayStyle attribute
476     * @return string ('inlineDisplaystyle'|'display'|'inline'|'linebreak') the DisplayStyle
477     */
478    public function getMathStyle() {
479        return $this->mathStyle;
480    }
481
482    /**
483     * Get if the input tex was marked as secure
484     * @return bool
485     */
486    public function isTexSecure() {
487        return $this->texSecure;
488    }
489
490    /**
491     * @return bool
492     */
493    public function checkTeX() {
494        if ( $this->texSecure ) {
495            // equation was already checked
496            return true;
497        }
498        $texCheckDisabled = MediaWikiServices::getInstance()
499            ->get( 'Math.Config' )
500            ->texCheckDisabled();
501        if ( $texCheckDisabled === MathConfig::ALWAYS ) {
502            // checking is disabled
503            $this->debug( 'Skip TeX check ' );
504            return true;
505        } else {
506            if ( $texCheckDisabled === MathConfig::NEW && $this->mode != MathConfig::MODE_SOURCE ) {
507                if ( $this->readFromCache() ) {
508                    $this->debug( 'Skip TeX check' );
509                    $this->texSecure = true;
510                    return true;
511                }
512            }
513            $this->debug( 'Perform TeX check' );
514            return $this->doCheck();
515        }
516    }
517
518    public function isInDatabase() {
519        if ( $this->storedInCache === null ) {
520            $this->readFromCache();
521        }
522        return $this->storedInCache;
523    }
524
525    /**
526     * @return string TeX the original tex string specified by the user
527     */
528    public function getUserInputTex() {
529        return $this->userInputTex;
530    }
531
532    /**
533     * @return string user defined ID
534     */
535    public function getID() {
536        return $this->id;
537    }
538
539    /**
540     * @param string $id user defined ID
541     */
542    public function setID( $id ) {
543        // Changes in the ID affect the container for the math element on the current page
544        // only. Therefore an id change does not affect the $this->changed variable, which
545        // indicates if database relevant fields have been changed.
546        $this->id = $id;
547    }
548
549    /**
550     * @param string $svg
551     */
552    public function setSvg( $svg ) {
553        $this->changed = true;
554        $this->svg = trim( $svg );
555    }
556
557    /**
558     * Gets the SVG image
559     *
560     * @param string $render if set to 'render' (default) and no SVG image exists, the function
561     *                       tries to generate it on the fly.
562     *                       Otherwise, if set to 'cached', and there is no SVG in the database
563     *                       cache, an empty string is returned.
564     *
565     * @return string XML-Document of the rendered SVG
566     */
567    public function getSvg( $render = 'render' ) {
568        // Spaces will prevent the image from being displayed correctly in the browser
569        if ( !$this->svg && $this->rbi ) {
570            $this->svg = $this->rbi->getSvg();
571        }
572        return trim( $this->svg );
573    }
574
575    /**
576     * @return string
577     */
578    abstract protected function getMathTableName();
579
580    protected function getModeName(): Message {
581        return MediaWikiServices::getInstance()
582            ->get( 'Math.Config' )
583            ->getRenderingModeName( $this->getMode() );
584    }
585
586    public function setInputType( string $inputType ) {
587        $this->inputType = $inputType;
588    }
589
590    public function getInputType(): string {
591        return $this->inputType;
592    }
593
594    protected function doCheck(): bool {
595        $checker = $this->getChecker();
596
597        if ( $checker->isValid() ) {
598            $this->setTex( $checker->getValidTex() );
599            $this->texSecure = true;
600            return true;
601        }
602
603        $checkerError = $checker->getError();
604        $this->lastError = $checkerError === null ?
605            $this->getError( 'math_unknown_error' ) :
606            $this->getError( $checkerError->getKey(), ...$checkerError->getParams() );
607        return false;
608    }
609
610    protected function debug( $msg ) {
611        $this->logger->debug( "$msg for \"{tex}\".", [ 'tex' => $this->userInputTex ] );
612    }
613
614    private function getCacheKey() {
615            return $this->cache->makeGlobalKey(
616                self::class,
617                $this->getInputHash()
618            );
619    }
620}