Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.56% covered (success)
94.56%
417 / 441
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Preprocessor_Hash
94.77% covered (success)
94.77%
417 / 440
37.50% covered (danger)
37.50%
3 / 8
130.34
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 newFrame
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newCustomFrame
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 newPartNodeArray
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 getConfigurationHash
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
 preprocessToObj
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
5
 buildDomTreeArrayFromText
98.45% covered (success)
98.45%
382 / 388
0.00% covered (danger)
0.00%
0 / 1
112
 addLiteral
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * Preprocessor using PHP arrays
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 * @ingroup Parser
8 */
9
10namespace MediaWiki\Parser;
11
12use Wikimedia\ObjectCache\WANObjectCache;
13
14/**
15 * Differences from DOM schema:
16 *   * attribute nodes are children
17 *   * "<h>" nodes that aren't at the top are replaced with <possible-h>
18 *
19 * Nodes are stored in a recursive array data structure. A node store is an
20 * array where each element may be either a scalar (representing a text node)
21 * or a "descriptor", which is a two-element array where the first element is
22 * the node name and the second element is the node store for the children.
23 *
24 * Attributes are represented as children that have a node name starting with
25 * "@", and a single text node child.
26 *
27 * @todo: Consider replacing descriptor arrays with objects of a new class.
28 * Benchmark and measure resulting memory impact.
29 *
30 * @ingroup Parser
31 */
32// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
33class Preprocessor_Hash extends Preprocessor {
34    /** Cache format version */
35    protected const CACHE_VERSION = 5;
36
37    /** @var int|false Min wikitext size for which to cache DOM tree */
38    protected $cacheThreshold;
39
40    private ?string $configurationHash = null;
41
42    /**
43     * @see Preprocessor::__construct()
44     * @param Parser $parser
45     * @param WANObjectCache|null $wanCache
46     * @param array $options Additional options include:
47     *   - cacheThreshold: min text size for which to cache DOMs. [Default: false]
48     */
49    public function __construct(
50        Parser $parser,
51        ?WANObjectCache $wanCache = null,
52        array $options = []
53    ) {
54        parent::__construct( $parser, $wanCache, $options );
55
56        $this->cacheThreshold = $options['cacheThreshold'] ?? false;
57    }
58
59    /**
60     * @return PPFrame_Hash
61     */
62    public function newFrame() {
63        return new PPFrame_Hash( $this );
64    }
65
66    /**
67     * @param array $args
68     * @return PPCustomFrame_Hash
69     */
70    public function newCustomFrame( $args ) {
71        return new PPCustomFrame_Hash( $this, $args );
72    }
73
74    /**
75     * @param array $values
76     * @return PPNode_Hash_Array
77     */
78    public function newPartNodeArray( $values ) {
79        $list = [];
80
81        foreach ( $values as $k => $val ) {
82            if ( is_int( $k ) ) {
83                $store = [ [ 'part', [
84                    [ 'name', [ [ '@index', [ $k ] ] ] ],
85                    [ 'value', [ strval( $val ) ] ],
86                ] ] ];
87            } else {
88                $store = [ [ 'part', [
89                    [ 'name', [ strval( $k ) ] ],
90                    '=',
91                    [ 'value', [ strval( $val ) ] ],
92                ] ] ];
93            }
94
95            $list[] = new PPNode_Hash_Tree( $store, 0 );
96        }
97
98        return new PPNode_Hash_Array( $list );
99    }
100
101    private function getConfigurationHash(): string {
102        if ( $this->configurationHash ) {
103            return $this->configurationHash;
104        }
105
106        $configs = [
107            'tags' => $this->parser->getTags(),
108        ];
109        sort( $configs['tags'] );
110        $this->configurationHash = sha1( json_encode( $configs ) );
111
112        return $this->configurationHash;
113    }
114
115    /** @inheritDoc */
116    public function preprocessToObj( $text, $flags = 0 ) {
117        if ( $this->disableLangConversion ) {
118            // Language conversions are globally disabled; implicitly set flag
119            $flags |= self::DOM_LANG_CONVERSION_DISABLED;
120        }
121
122        $domTreeArray = null;
123        if (
124            $this->cacheThreshold !== false &&
125            strlen( $text ) >= $this->cacheThreshold &&
126            ( $flags & self::DOM_UNCACHED ) != self::DOM_UNCACHED
127        ) {
128            $domTreeJson = $this->wanCache->getWithSetCallback(
129                $this->wanCache->makeKey(
130                    'preprocess-hash', $this->getConfigurationHash(), sha1( $text ), $flags ),
131                $this->wanCache::TTL_DAY,
132                function () use ( $text, $flags, &$domTreeArray ) {
133                    $domTreeArray = $this->buildDomTreeArrayFromText( $text, $flags );
134
135                    return json_encode(
136                        $domTreeArray,
137                        JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
138                    );
139                },
140                [ 'version' => self::CACHE_VERSION, 'segmentable' => true ]
141            );
142            $domTreeArray ??= json_decode( $domTreeJson );
143        }
144
145        $domTreeArray ??= $this->buildDomTreeArrayFromText( $text, $flags );
146
147        return new PPNode_Hash_Tree( $domTreeArray, 0 );
148    }
149
150    /**
151     * @param string $text Wikitext
152     * @param int $flags Bit field of Preprocessor::DOM_* flags
153     * @return array JSON-serializable document object model array
154     */
155    private function buildDomTreeArrayFromText( $text, $flags ) {
156        $textStartsInSOLState = $flags & self::START_IN_SOL_STATE;