Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
BasePageBundle
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 10
650
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
 validate
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 hasValidCounters
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 withHtml
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 withDocument
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 toBasePageBundle
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 isEmpty
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasContent
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
42
 toJsonArray
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 newFromJsonArray
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Core;
5
6use Composer\Semver\Semver;
7use Wikimedia\Assert\Assert;
8use Wikimedia\JsonCodec\JsonCodecable;
9use Wikimedia\JsonCodec\JsonCodecableTrait;
10use Wikimedia\Parsoid\DOM\Document;
11use Wikimedia\Parsoid\DOM\DocumentFragment;
12
13/**
14 * A page bundle stores metadata and separated data-parsoid and
15 * data-mw content.  The data-parsoid and data-mw content is indexed
16 * by the id attributes on individual nodes.  This content needs to
17 * be loaded before the data-parsoid and/or data-mw information can be
18 * used.
19 *
20 * Note that the parsoid/mw properties of the page bundle are in "serialized
21 * array" form; that is, they are flat arrays appropriate for json-encoding
22 * and do not contain DataParsoid or DataMw objects.
23 *
24 * See DomPageBundle and HtmlPageBundle for similar structures which include
25 * the actual HTML/DOM content.
26 */
27class BasePageBundle implements JsonCodecable {
28    use JsonCodecableTrait;
29
30    public function __construct(
31        /**
32         * A map from ID to the array serialization of DataParsoid for the Node
33         * with that ID.
34         *
35         * @var ?array{counter?:int,offsetType?:'byte'|'ucs2'|'char',ids:array<string,array>}
36         */
37        public ?array $parsoid = null,
38        /**
39         * A map from ID to the array serialization of DataMw for the Node
40         * with that ID.
41         *
42         * @var ?array{ids:array<string,array>}
43         */
44        public ?array $mw = null,
45        /**
46         * Records the max counter values for different counter types
47         * @var ?array{nodedata?:int,annotation?:int,transclusion?:int}
48         */
49        public ?array $counters = null,
50        public ?string $version = null,
51        /**
52         * A map of HTTP headers: both name and value should be strings.
53         * @var ?array<string,string>
54         */
55        public ?array $headers = null,
56        public ?string $contentmodel = null,
57    ) {
58        Assert::invariant(
59            !isset( $parsoid['counter'] ), "counter removed in Parsoid 0.23"
60        );
61    }
62
63    /**
64     * Check if this pagebundle is valid.
65     * @param string $contentVersion Document content version to validate against.
66     * @param ?string &$errorMessage Error message will be returned here.
67     * @return bool
68     */
69    public function validate(
70        string $contentVersion, ?string &$errorMessage = null
71    ): bool {
72        if ( !$this->parsoid || !isset( $this->parsoid['ids'] ) ) {
73            $errorMessage = 'Invalid data-parsoid was provided.';
74            return false;
75        } elseif ( Semver::satisfies( $contentVersion, '^999.0.0' )
76            && ( !$this->mw || !isset( $this->mw['ids'] ) )
77        ) {
78            $errorMessage = 'Invalid data-mw was provided.';
79            return false;
80        }
81        return true;
82    }
83
84    /**
85     * Does this pagebundle have valid counters?
86     *
87     * If initialized from ParserCache or from the <head> element
88     * in the HTML, annotation & transclusion values will both be >= 0.
89     * But, it is not necessary to test for both because either both are
90     * valid are neither one is.
91     */
92    public function hasValidCounters(): bool {
93        $counters = $this->counters;
94        if ( $counters !== null ) {
95            // Temporary assertion in the initial stages of lazy loading implementation
96            // which we'll remove once things are stable. This verifies the claim in the
97            // function doc comment above.
98            Assert::invariant(
99                ( $counters['annotation'] >= 0 && $counters['transclusion'] >= 0 ) ||
100                ( $counters['annotation'] === -1 && $counters['transclusion'] === -1 ),
101                "Invalid counters state: " . json_encode( $counters )
102            );
103            return $counters['annotation'] >= 0;
104        } else {
105            return false;
106        }
107    }
108
109    /**
110     * Build an HtmlPageBundle by adding HTML string contents to this
111     * base page bundle.
112     * @param string $html The main document HTML
113     * @param array<string,string> $fragments Additional named HTML fragments
114     * @return HtmlPageBundle
115     */
116    public function withHtml( string $html, array $fragments = [] ): HtmlPageBundle {
117        return new HtmlPageBundle(
118            html: $html,
119            fragments: $fragments,
120            parsoid: $this->parsoid,
121            mw: $this->mw,
122            counters: $this->counters,
123            version: $this->version,
124            headers: $this->headers,
125            contentmodel: $this->contentmodel,
126        );
127    }
128
129    /**
130     * Build an DomPageBundle by adding DOM contents to this
131     * base page bundle.
132     * @param Document $doc The owner Document
133     * @param array<string,DocumentFragment> $fragments Additional named
134     *   DocumentFragments
135     * @return DomPageBundle
136     */
137    public function withDocument( Document $doc, array $fragments = [] ): DomPageBundle {
138        return new DomPageBundle(
139            doc: $doc,
140            fragments: $fragments,
141            parsoid: $this->parsoid,
142            mw: $this->mw,
143            counters: $this->counters,
144            version: $this->version,
145            headers: $this->headers,
146            contentmodel: $this->contentmodel,
147        );
148    }
149
150    /**
151     * Build a BasePageBundle with just the metadata from another page bundle.
152     */
153    public function toBasePageBundle(): BasePageBundle {
154        return new BasePageBundle(
155            parsoid: $this->parsoid,
156            mw: $this->mw,
157            counters: $this->counters,
158            version: $this->version,
159            headers: $this->headers,
160            contentmodel: $this->contentmodel,
161        );
162    }
163
164    /**
165     * @deprecated
166     * // todo remove this method when the dependent code has been adjusted to use hasContent
167     */
168    public function isEmpty(): bool {
169        return !$this->hasContent();
170    }
171
172    /**
173     * Returns true if the BasePageBundle has any content that's worth serializing.
174     * A BasePageBundle created without any constructor parameters does not have content.
175     */
176    public function hasContent(): bool {
177        return $this->parsoid !== null ||
178            $this->mw !== null ||
179            $this->counters !== null ||
180            $this->version !== null ||
181            $this->headers !== null ||
182            $this->contentmodel !== null;
183    }
184
185    // JsonCodecable -------------
186
187    /** @inheritDoc */
188    public function toJsonArray(): array {
189        return [
190            'parsoid' => $this->parsoid,
191            'mw' => $this->mw,
192            'counters' => $this->counters,
193            'version' => $this->version,
194            'headers' => $this->headers,
195            'contentmodel' => $this->contentmodel,
196        ];
197    }
198
199    /** @inheritDoc */
200    public static function newFromJsonArray( array $json ): BasePageBundle {
201        if ( isset( $json['parsoid']['counter'] ) ) {
202            // Backward compatibility with Parsoid < 0.23
203            $json['counters'] ??= [
204                'nodedata' => $json['parsoid']['counter'],
205                'annotation' => -1,
206                'transclusion' => -1,
207            ];
208            unset( $json['parsoid']['counter'] );
209        }
210        return new BasePageBundle(
211            parsoid: $json['parsoid'] ?? null,
212            mw: $json['mw'] ?? null,
213            counters: $json['counters'] ?? null,
214            version: $json['version'] ?? null,
215            headers: $json['headers'] ?? null,
216            contentmodel: $json['contentmodel'] ?? null
217        );
218    }
219}