Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
83.72% |
72 / 86 |
|
75.00% |
12 / 16 |
CRAP | |
0.00% |
0 / 1 |
PageConfig | |
83.72% |
72 / 86 |
|
75.00% |
12 / 16 |
33.88 | |
0.00% |
0 / 1 |
__construct | |
68.18% |
15 / 22 |
|
0.00% |
0 / 1 |
8.58 | |||
mockPageContent | |
72.73% |
8 / 11 |
|
0.00% |
0 / 1 |
2.08 | |||
loadData | |
90.91% |
20 / 22 |
|
0.00% |
0 / 1 |
6.03 | |||
getContentModel | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getLinkTarget | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getPageId | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getPageLanguageBcp47 | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getPageLanguageDir | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getRevisionId | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getParentRevisionId | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getRevisionTimestamp | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getRevisionUser | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getRevisionUserId | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getRevisionSha1 | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getRevisionSize | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getRevisionContent | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | |
3 | declare( strict_types = 1 ); |
4 | |
5 | namespace Wikimedia\Parsoid\Config\Api; |
6 | |
7 | use Wikimedia\Assert\Assert; |
8 | use Wikimedia\Bcp47Code\Bcp47Code; |
9 | use Wikimedia\Parsoid\Config\PageConfig as IPageConfig; |
10 | use Wikimedia\Parsoid\Config\PageContent; |
11 | use Wikimedia\Parsoid\Config\SiteConfig as ISiteConfig; |
12 | use Wikimedia\Parsoid\Mocks\MockPageContent; |
13 | use Wikimedia\Parsoid\Utils\Title; |
14 | use Wikimedia\Parsoid\Utils\Utils; |
15 | |
16 | /** |
17 | * PageConfig via MediaWiki's Action API |
18 | * |
19 | * Note this is intended for testing, not performance. |
20 | */ |
21 | class PageConfig extends IPageConfig { |
22 | |
23 | /** @var ?ApiHelper */ |
24 | private $api; |
25 | |
26 | private ISiteConfig $siteConfig; |
27 | |
28 | /** @var Title */ |
29 | private $title; |
30 | |
31 | /** @var string|null */ |
32 | private $revid; |
33 | |
34 | /** @var array<string,mixed>|null */ |
35 | private $page; |
36 | |
37 | /** @var array<string,mixed>|null */ |
38 | private $rev; |
39 | |
40 | /** @var PageContent|null */ |
41 | private $content; |
42 | |
43 | /** @var ?Bcp47Code */ |
44 | private $pagelanguage; |
45 | |
46 | /** @var string|null */ |
47 | private $pagelanguageDir; |
48 | |
49 | /** |
50 | * @param ?ApiHelper $api (only needed if $opts doesn't provide page info) |
51 | * @param ISiteConfig $siteConfig |
52 | * @param array $opts |
53 | */ |
54 | public function __construct( ?ApiHelper $api, ISiteConfig $siteConfig, array $opts ) { |
55 | parent::__construct(); |
56 | $this->api = $api; |
57 | $this->siteConfig = $siteConfig; |
58 | |
59 | if ( !isset( $opts['title'] ) ) { |
60 | throw new \InvalidArgumentException( '$opts[\'title\'] must be set' ); |
61 | } |
62 | if ( !( $opts['title'] instanceof Title ) ) { |
63 | throw new \InvalidArgumentException( '$opts[\'title\'] must be a Title' ); |
64 | } |
65 | $this->title = $opts['title']; |
66 | $this->revid = $opts['revid'] ?? null; |
67 | # pageLanguage can/should be passed as a Bcp47Code object |
68 | $this->pagelanguage = !empty( $opts['pageLanguage'] ) ? |
69 | Utils::mwCodeToBcp47( $opts['pageLanguage'] ) : null; |
70 | $this->pagelanguageDir = $opts['pageLanguageDir'] ?? null; |
71 | |
72 | // This option is primarily used to mock the page content. |
73 | if ( isset( $opts['pageContent'] ) && empty( $opts['loadData'] ) ) { |
74 | $this->mockPageContent( $opts ); |
75 | } else { |
76 | Assert::invariant( $api !== null, 'Cannot load page info without an API' ); |
77 | # Lazily load later |
78 | $this->page = null; |
79 | $this->rev = null; |
80 | |
81 | if ( isset( $opts['pageContent'] ) ) { |
82 | $this->loadData(); |
83 | $this->rev = [ |
84 | 'slots' => [ 'main' => $opts['pageContent'] ], |
85 | ]; |
86 | } |
87 | } |
88 | } |
89 | |
90 | private function mockPageContent( array $opts ): void { |
91 | $this->page = [ |
92 | 'title' => $this->title->getPrefixedText(), |
93 | 'ns' => $this->title->getNamespace(), |
94 | 'pageid' => -1, |
95 | 'pagelanguage' => $opts['pageLanguage'] ?? 'en', |
96 | 'pagelanguagedir' => $opts['pageLanguageDir'] ?? 'ltr', |
97 | ]; |
98 | if ( isset( $opts['pageContent'] ) ) { |
99 | $this->rev = [ |
100 | 'slots' => [ 'main' => $opts['pageContent'] ], |
101 | ]; |
102 | } |
103 | } |
104 | |
105 | private function loadData() { |
106 | if ( $this->page !== null ) { |
107 | return; |
108 | } |
109 | |
110 | $params = [ |
111 | 'action' => 'query', |
112 | 'prop' => 'info|revisions', |
113 | 'rvprop' => 'ids|timestamp|user|userid|sha1|size|content', |
114 | 'rvslots' => '*', |
115 | ]; |
116 | |
117 | if ( !empty( $this->revid ) ) { |
118 | $params['revids'] = $this->revid; |
119 | } else { |
120 | $params['titles'] = $this->title->getPrefixedDBKey(); |
121 | $params['rvlimit'] = 1; |
122 | } |
123 | |
124 | $content = $this->api->makeRequest( $params ); |
125 | if ( !isset( $content['query']['pages'][0] ) ) { |
126 | throw new \RuntimeException( 'Request for page failed' ); |
127 | } |
128 | $this->page = $content['query']['pages'][0]; |
129 | |
130 | $this->rev = $this->page['revisions'][0] ?? []; |
131 | unset( $this->page['revisions'] ); |
132 | |
133 | if ( isset( $this->rev['timestamp'] ) ) { |
134 | $this->rev['timestamp'] = preg_replace( '/\D/', '', $this->rev['timestamp'] ); |
135 | } |
136 | |
137 | // Well, we tried but the page probably doesn't exist |
138 | if ( !$this->rev ) { |
139 | $this->mockPageContent( [] ); // FIXME: T234549 |
140 | } |
141 | } |
142 | |
143 | /** @inheritDoc */ |
144 | public function getContentModel(): string { |
145 | $this->loadData(); |
146 | return $this->rev['slots']['main']['contentmodel'] ?? 'wikitext'; |
147 | } |
148 | |
149 | /** @inheritDoc */ |
150 | public function getLinkTarget(): Title { |
151 | $this->loadData(); |
152 | return Title::newFromText( |
153 | $this->page['title'], $this->siteConfig, $this->page['ns'] |
154 | ); |
155 | } |
156 | |
157 | /** @inheritDoc */ |
158 | public function getPageId(): int { |
159 | $this->loadData(); |
160 | return $this->page['pageid'] ?? 0; |
161 | } |
162 | |
163 | /** @inheritDoc */ |
164 | public function getPageLanguageBcp47(): Bcp47Code { |
165 | $this->loadData(); |
166 | # Note that 'en' is a last-resort fail-safe fallback; it shouldn't |
167 | # ever be reached in practice. |
168 | return $this->pagelanguage ?? |
169 | # T320662: core should provide an API to get the BCP-47 form directly |
170 | Utils::mwCodeToBcp47( $this->page['pagelanguage'] ?? 'en' ); |
171 | } |
172 | |
173 | /** @inheritDoc */ |
174 | public function getPageLanguageDir(): string { |
175 | $this->loadData(); |
176 | return $this->pagelanguageDir ?? $this->page['pagelanguagedir'] ?? 'ltr'; |
177 | } |
178 | |
179 | /** @inheritDoc */ |
180 | public function getRevisionId(): ?int { |
181 | $this->loadData(); |
182 | return $this->rev['revid'] ?? null; |
183 | } |
184 | |
185 | /** @inheritDoc */ |
186 | public function getParentRevisionId(): ?int { |
187 | $this->loadData(); |
188 | return $this->rev['parentid'] ?? null; |
189 | } |
190 | |
191 | /** @inheritDoc */ |
192 | public function getRevisionTimestamp(): ?string { |
193 | $this->loadData(); |
194 | return $this->rev['timestamp'] ?? null; |
195 | } |
196 | |
197 | /** @inheritDoc */ |
198 | public function getRevisionUser(): ?string { |
199 | $this->loadData(); |
200 | return $this->rev['user'] ?? null; |
201 | } |
202 | |
203 | /** @inheritDoc */ |
204 | public function getRevisionUserId(): ?int { |
205 | $this->loadData(); |
206 | return $this->rev['userid'] ?? null; |
207 | } |
208 | |
209 | /** @inheritDoc */ |
210 | public function getRevisionSha1(): ?string { |
211 | $this->loadData(); |
212 | return $this->rev['sha1'] ?? null; |
213 | } |
214 | |
215 | /** @inheritDoc */ |
216 | public function getRevisionSize(): ?int { |
217 | $this->loadData(); |
218 | return $this->rev['size'] ?? null; |
219 | } |
220 | |
221 | /** @inheritDoc */ |
222 | public function getRevisionContent(): ?PageContent { |
223 | $this->loadData(); |
224 | if ( $this->rev && !$this->content ) { |
225 | $this->content = new MockPageContent( $this->rev['slots'] ); |
226 | } |
227 | return $this->content; |
228 | } |
229 | } |