Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 58 |
|
0.00% |
0 / 2 |
CRAP | |
0.00% |
0 / 1 |
PageConfigFactory | |
0.00% |
0 / 58 |
|
0.00% |
0 / 2 |
272 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
create | |
0.00% |
0 / 55 |
|
0.00% |
0 / 1 |
240 |
1 | <?php |
2 | /** |
3 | * Copyright (C) 2011-2022 Wikimedia Foundation and others. |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
18 | */ |
19 | |
20 | namespace MediaWiki\Parser\Parsoid\Config; |
21 | |
22 | use IDBAccessObject; |
23 | use MediaWiki\Languages\LanguageFactory; |
24 | use MediaWiki\Logger\LoggerFactory; |
25 | use MediaWiki\Page\PageIdentity; |
26 | use MediaWiki\Revision\RevisionAccessException; |
27 | use MediaWiki\Revision\RevisionRecord; |
28 | use MediaWiki\Revision\RevisionStore; |
29 | use MediaWiki\Revision\SlotRecord; |
30 | use MediaWiki\Revision\SlotRoleRegistry; |
31 | use MediaWiki\Revision\SuppressedDataException; |
32 | use MediaWiki\Title\Title; |
33 | use MediaWiki\User\UserIdentity; |
34 | use ParserOptions; |
35 | use Wikimedia\Bcp47Code\Bcp47Code; |
36 | |
37 | /** |
38 | * Helper class used by MediaWiki to create Parsoid PageConfig objects. |
39 | * |
40 | * @since 1.39 |
41 | * @internal |
42 | */ |
43 | class PageConfigFactory extends \Wikimedia\Parsoid\Config\PageConfigFactory { |
44 | private RevisionStore $revisionStore; |
45 | private SlotRoleRegistry $slotRoleRegistry; |
46 | private LanguageFactory $languageFactory; |
47 | |
48 | /** |
49 | * @param RevisionStore $revisionStore |
50 | * @param SlotRoleRegistry $slotRoleRegistry |
51 | * @param LanguageFactory $languageFactory |
52 | */ |
53 | public function __construct( |
54 | RevisionStore $revisionStore, |
55 | SlotRoleRegistry $slotRoleRegistry, |
56 | LanguageFactory $languageFactory |
57 | ) { |
58 | $this->revisionStore = $revisionStore; |
59 | $this->slotRoleRegistry = $slotRoleRegistry; |
60 | $this->languageFactory = $languageFactory; |
61 | } |
62 | |
63 | /** |
64 | * Create a new PageConfig. |
65 | * |
66 | * Note that Parsoid isn't supposed to use the user context by design; all |
67 | * user-specific processing is expected to be introduced as a post-parse |
68 | * transform. The $user parameter is therefore usually null, especially |
69 | * in background job parsing, although there are corner cases during |
70 | * extension processing where a non-null $user could affect the output. |
71 | * |
72 | * @param PageIdentity $pageId The page represented by the PageConfig. |
73 | * @param ?UserIdentity $user User who is doing rendering (for parsing options). |
74 | * @param int|RevisionRecord|null $revision Revision id or a revision record |
75 | * @param ?string $unused |
76 | * @param ?Bcp47Code $pageLanguageOverride |
77 | * @param bool $ensureAccessibleContent If true, ensures that we can get content |
78 | * from the newly constructed pageConfig's RevisionRecord and throws a |
79 | * RevisionAccessException if not. |
80 | * @return \Wikimedia\Parsoid\Config\PageConfig |
81 | * @throws RevisionAccessException |
82 | */ |
83 | public function create( |
84 | PageIdentity $pageId, |
85 | ?UserIdentity $user = null, |
86 | $revision = null, |
87 | ?string $unused = null, /* Added to mollify CI with cross-repo uses */ |
88 | ?Bcp47Code $pageLanguageOverride = null, |
89 | bool $ensureAccessibleContent = false |
90 | ): \Wikimedia\Parsoid\Config\PageConfig { |
91 | $title = Title::newFromPageIdentity( $pageId ); |
92 | |
93 | if ( $unused !== null ) { |
94 | wfDeprecated( __METHOD__ . ' with non-null 4th arg', '1.40' ); |
95 | } |
96 | |
97 | if ( $revision === null ) { |
98 | // Fetch the 'latest' revision for the given title. |
99 | // Note: This initial fetch of the page context revision is |
100 | // *not* using Parser::fetchCurrentRevisionRecordOfTitle() |
101 | // (which usually invokes Parser::statelessFetchRevisionRecord |
102 | // and from there RevisionStore::getKnownCurrentRevision) |
103 | // because we don't have a Parser object to give to that callback. |
104 | // We could create one if needed for greater compatibility. |
105 | $revisionRecord = $this->revisionStore->getKnownCurrentRevision( |
106 | $title |
107 | ) ?: null; |
108 | // Note that $revisionRecord could still be null here if no |
109 | // page with that $title yet exists. |
110 | } elseif ( !is_int( $revision ) ) { |
111 | $revisionRecord = $revision; |
112 | } else { |
113 | if ( $revision === 0 ) { |
114 | // The client may explicitly provide 0 as the revision ID to indicate that |
115 | // the content doesn't belong to any saved revision, and provide wikitext |
116 | // in some way. Calling code should handle this case and provide a (fake) |
117 | // RevisionRecord based on the data in the request. If we get here, the |
118 | // code processing the request didn't handle this case properly. |
119 | throw new \UnexpectedValueException( |
120 | "Got revision ID 0 indicating unsaved content. " . |
121 | "Unsaved content must be provided as a RevisionRecord object." |
122 | ); |
123 | } |
124 | |
125 | // Fetch the correct revision record by the supplied id. |
126 | // This accesses the replica DB and may (or may not) fail over to |
127 | // the primary DB if the revision isn't found. |
128 | $revisionRecord = $this->revisionStore->getRevisionById( $revision ); |
129 | if ( $revisionRecord === null ) { |
130 | // This revision really ought to exist. Check the primary DB. |
131 | // This *could* cause two requests to the primary DB if there |
132 | // were pending writes, but this codepath should be very rare. |
133 | // [T259855] |
134 | $revisionRecord = $this->revisionStore->getRevisionById( |
135 | $revision, IDBAccessObject::READ_LATEST |
136 | ); |
137 | $success = ( $revisionRecord !== null ) ? 'success' : 'failure'; |
138 | LoggerFactory::getInstance( 'Parsoid' )->error( |
139 | "Retried revision fetch after failure: {$success}", [ |
140 | 'id' => $revision, |
141 | 'title' => $title->getPrefixedText(), |
142 | ] |
143 | ); |
144 | } |
145 | if ( $revisionRecord === null ) { |
146 | throw new RevisionAccessException( "Can't find revision {$revision}" ); |
147 | } |
148 | } |
149 | |
150 | // If we have a revision record, check that we are allowed to see it. |
151 | // Mirrors the check from RevisionRecord::getContent |
152 | if ( |
153 | $revisionRecord !== null && |
154 | !$revisionRecord->audienceCan( |
155 | RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC |
156 | ) |
157 | ) { |
158 | throw new SuppressedDataException( 'Not an available content version.' ); |
159 | } |
160 | |
161 | $parserOptions = |
162 | $user |
163 | ? ParserOptions::newFromUser( $user ) |
164 | : ParserOptions::newFromAnon(); |
165 | |
166 | // Turn off some options since Parsoid/JS currently doesn't |
167 | // do anything with this. As we proceed with closer integration, |
168 | // we can figure out if there is any value to these limit reports. |
169 | $parserOptions->setOption( 'enableLimitReport', false ); |
170 | |
171 | $slotRoleHandler = $this->slotRoleRegistry->getRoleHandler( SlotRecord::MAIN ); |
172 | if ( $pageLanguageOverride ) { |
173 | $pageLanguage = $this->languageFactory->getLanguage( $pageLanguageOverride ); |
174 | } else { |
175 | $pageLanguage = $title->getPageLanguage(); |
176 | } |
177 | |
178 | $pageConfig = new PageConfig( |
179 | $parserOptions, |
180 | $slotRoleHandler, |
181 | $title, |
182 | $revisionRecord, |
183 | $pageLanguage, |
184 | $pageLanguage->getDir() |
185 | ); |
186 | |
187 | if ( $ensureAccessibleContent ) { |
188 | if ( $revisionRecord === null ) { |
189 | // T234549 |
190 | throw new RevisionAccessException( 'The specified revision does not exist.' ); |
191 | } |
192 | // Try to get the content so that we can fail early. Otherwise, |
193 | // a RevisionAccessException is thrown. It's expensive, but the |
194 | // result will be cached for later calls. |
195 | $pageConfig->getRevisionContent()->getContent( SlotRecord::MAIN ); |
196 | } |
197 | |
198 | return $pageConfig; |
199 | } |
200 | |
201 | } |