Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
80.50% |
1135 / 1410 |
|
58.90% |
43 / 73 |
CRAP | |
0.00% |
0 / 1 |
RevisionStore | |
80.50% |
1135 / 1410 |
|
58.90% |
43 / 73 |
1022.96 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
1 | |||
setLogger | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isReadOnly | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getWikiId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDBConnectionRefForQueryFlags | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getReplicaConnection | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPrimaryConnection | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTitle | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
getPage | |
93.55% |
29 / 31 |
|
0.00% |
0 / 1 |
12.04 | |||
wrapPage | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
failOnNull | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
failOnEmpty | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
insertRevisionOn | |
98.57% |
69 / 70 |
|
0.00% |
0 / 1 |
5 | |||
updateSlotsOn | |
0.00% |
0 / 36 |
|
0.00% |
0 / 1 |
20 | |||
updateSlotsInternal | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
insertRevisionInternal | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
4 | |||
insertSlotOn | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
3 | |||
insertIpChangesRow | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
insertRevisionRowOn | |
20.00% |
14 / 70 |
|
0.00% |
0 / 1 |
40.77 | |||
getBaseRevisionRow | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
3 | |||
storeContentBlob | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
1 | |||
insertSlotRowOn | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
insertContentRowOn | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
checkContent | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
newNullRevision | |
83.33% |
30 / 36 |
|
0.00% |
0 / 1 |
3.04 | |||
getRcIdIfUnpatrolled | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
getRecentChange | |
91.67% |
11 / 12 |
|
0.00% |
0 / 1 |
2.00 | |||
loadSlotContent | |
65.91% |
29 / 44 |
|
0.00% |
0 / 1 |
12.21 | |||
getRevisionById | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRevisionByTitle | |
83.33% |
10 / 12 |
|
0.00% |
0 / 1 |
4.07 | |||
getRevisionByPageId | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
getRevisionByTimestamp | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
loadSlotRecords | |
8.57% |
3 / 35 |
|
0.00% |
0 / 1 |
16.23 | |||
loadSlotRecordsFromDb | |
42.86% |
9 / 21 |
|
0.00% |
0 / 1 |
4.68 | |||
constructSlotRecords | |
57.58% |
19 / 33 |
|
0.00% |
0 / 1 |
15.18 | |||
newRevisionSlots | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
newRevisionFromArchiveRow | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newRevisionFromRow | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newRevisionFromArchiveRowAndSlots | |
87.50% |
28 / 32 |
|
0.00% |
0 / 1 |
10.20 | |||
newRevisionFromRowAndSlots | |
78.38% |
58 / 74 |
|
0.00% |
0 / 1 |
13.46 | |||
ensureRevisionRowMatchesPage | |
95.24% |
40 / 42 |
|
0.00% |
0 / 1 |
7 | |||
newRevisionsFromBatch | |
86.47% |
115 / 133 |
|
0.00% |
0 / 1 |
33.38 | |||
getSlotRowsForBatch | |
100.00% |
55 / 55 |
|
100.00% |
1 / 1 |
20 | |||
getContentBlobsForBatch | |
87.50% |
14 / 16 |
|
0.00% |
0 / 1 |
6.07 | |||
newRevisionFromConds | |
70.00% |
7 / 10 |
|
0.00% |
0 / 1 |
5.68 | |||
loadRevisionFromConds | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
checkDatabaseDomain | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 | |||
fetchRevisionRowFromConds | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
2.00 | |||
getQueryInfo | |
94.23% |
49 / 52 |
|
0.00% |
0 / 1 |
4.00 | |||
newSelectQueryBuilder | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newArchiveSelectQueryBuilder | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSlotsQueryInfo | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
4 | |||
isRevisionRow | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
6 | |||
getArchiveQueryInfo | |
100.00% |
27 / 27 |
|
100.00% |
1 / 1 |
1 | |||
getRevisionSizes | |
91.67% |
11 / 12 |
|
0.00% |
0 / 1 |
3.01 | |||
getRelativeRevision | |
79.41% |
27 / 34 |
|
0.00% |
0 / 1 |
9.71 | |||
getPreviousRevision | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getNextRevision | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPreviousRevisionId | |
52.94% |
9 / 17 |
|
0.00% |
0 / 1 |
3.94 | |||
getTimestampFromId | |
76.92% |
10 / 13 |
|
0.00% |
0 / 1 |
6.44 | |||
countRevisionsByPageId | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
countRevisionsByTitle | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
userWasLastToEdit | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
4.00 | |||
getKnownCurrentRevision | |
76.19% |
32 / 42 |
|
0.00% |
0 / 1 |
10.09 | |||
getFirstRevision | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
3 | |||
getRevisionRowCacheKey | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
assertRevisionParameter | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
getRevisionLimitConditions | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
7 | |||
getRevisionIdsBetween | |
100.00% |
24 / 24 |
|
100.00% |
1 / 1 |
10 | |||
getAuthorsBetween | |
89.19% |
33 / 37 |
|
0.00% |
0 / 1 |
7.06 | |||
countAuthorsBetween | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
countRevisionsBetween | |
100.00% |
25 / 25 |
|
100.00% |
1 / 1 |
5 | |||
findIdenticalRevision | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * Attribution notice: when this file was created, much of its content was taken |
19 | * from the Revision.php file as present in release 1.30. Refer to the history |
20 | * of that file for original authorship (that file was removed entirely in 1.37, |
21 | * but its history can still be found in prior versions of MediaWiki). |
22 | * |
23 | * @file |
24 | */ |
25 | |
26 | namespace MediaWiki\Revision; |
27 | |
28 | use InvalidArgumentException; |
29 | use LogicException; |
30 | use MediaWiki\CommentStore\CommentStore; |
31 | use MediaWiki\CommentStore\CommentStoreComment; |
32 | use MediaWiki\Content\Content; |
33 | use MediaWiki\Content\FallbackContent; |
34 | use MediaWiki\Content\IContentHandlerFactory; |
35 | use MediaWiki\DAO\WikiAwareEntity; |
36 | use MediaWiki\HookContainer\HookContainer; |
37 | use MediaWiki\HookContainer\HookRunner; |
38 | use MediaWiki\Linker\LinkTarget; |
39 | use MediaWiki\MainConfigNames; |
40 | use MediaWiki\MediaWikiServices; |
41 | use MediaWiki\Page\LegacyArticleIdAccess; |
42 | use MediaWiki\Page\PageIdentity; |
43 | use MediaWiki\Page\PageIdentityValue; |
44 | use MediaWiki\Page\PageStore; |
45 | use MediaWiki\Permissions\Authority; |
46 | use MediaWiki\Storage\BadBlobException; |
47 | use MediaWiki\Storage\BlobAccessException; |
48 | use MediaWiki\Storage\BlobStore; |
49 | use MediaWiki\Storage\NameTableAccessException; |
50 | use MediaWiki\Storage\NameTableStore; |
51 | use MediaWiki\Storage\RevisionSlotsUpdate; |
52 | use MediaWiki\Storage\SqlBlobStore; |
53 | use MediaWiki\Title\Title; |
54 | use MediaWiki\Title\TitleFactory; |
55 | use MediaWiki\User\ActorStore; |
56 | use MediaWiki\User\UserIdentity; |
57 | use MediaWiki\Utils\MWTimestamp; |
58 | use MWException; |
59 | use MWUnknownContentModelException; |
60 | use Psr\Log\LoggerAwareInterface; |
61 | use Psr\Log\LoggerInterface; |
62 | use Psr\Log\NullLogger; |
63 | use RecentChange; |
64 | use RuntimeException; |
65 | use StatusValue; |
66 | use stdClass; |
67 | use Traversable; |
68 | use Wikimedia\Assert\Assert; |
69 | use Wikimedia\IPUtils; |
70 | use Wikimedia\ObjectCache\BagOStuff; |
71 | use Wikimedia\ObjectCache\WANObjectCache; |
72 | use Wikimedia\Rdbms\Database; |
73 | use Wikimedia\Rdbms\DBAccessObjectUtils; |
74 | use Wikimedia\Rdbms\IDatabase; |
75 | use Wikimedia\Rdbms\IDBAccessObject; |
76 | use Wikimedia\Rdbms\ILoadBalancer; |
77 | use Wikimedia\Rdbms\IReadableDatabase; |
78 | use Wikimedia\Rdbms\IResultWrapper; |
79 | use Wikimedia\Rdbms\Platform\ISQLPlatform; |
80 | use Wikimedia\Rdbms\SelectQueryBuilder; |
81 | |
82 | /** |
83 | * Service for looking up page revisions. |
84 | * |
85 | * @since 1.31 |
86 | * @since 1.32 Renamed from MediaWiki\Storage\RevisionStore |
87 | * |
88 | * @note This was written to act as a drop-in replacement for the corresponding |
89 | * static methods in the old Revision class (which was later removed in 1.37). |
90 | */ |
91 | class RevisionStore implements RevisionFactory, RevisionLookup, LoggerAwareInterface { |
92 | |
93 | use LegacyArticleIdAccess; |
94 | |
95 | public const ROW_CACHE_KEY = 'revision-row-1.29'; |
96 | |
97 | public const ORDER_OLDEST_TO_NEWEST = 'ASC'; |
98 | public const ORDER_NEWEST_TO_OLDEST = 'DESC'; |
99 | |
100 | // Constants for get(...)Between methods |
101 | public const INCLUDE_OLD = 'include_old'; |
102 | public const INCLUDE_NEW = 'include_new'; |
103 | public const INCLUDE_BOTH = 'include_both'; |
104 | |
105 | /** |
106 | * @var SqlBlobStore |
107 | */ |
108 | private $blobStore; |
109 | |
110 | /** |
111 | * @var false|string |
112 | */ |
113 | private $wikiId; |
114 | |
115 | /** |
116 | * @var ILoadBalancer |
117 | */ |
118 | private $loadBalancer; |
119 | |
120 | /** |
121 | * @var WANObjectCache |
122 | */ |
123 | private $cache; |
124 | |
125 | /** |
126 | * @var BagOStuff |
127 | */ |
128 | private $localCache; |
129 | |
130 | /** |
131 | * @var CommentStore |
132 | */ |
133 | private $commentStore; |
134 | |
135 | /** @var ActorStore */ |
136 | private $actorStore; |
137 | |
138 | /** |
139 | * @var LoggerInterface |
140 | */ |
141 | private $logger; |
142 | |
143 | /** |
144 | * @var NameTableStore |
145 | */ |
146 | private $contentModelStore; |
147 | |
148 | /** |
149 | * @var NameTableStore |
150 | */ |
151 | private $slotRoleStore; |
152 | |
153 | /** @var SlotRoleRegistry */ |
154 | private $slotRoleRegistry; |
155 | |
156 | /** @var IContentHandlerFactory */ |
157 | private $contentHandlerFactory; |
158 | |
159 | /** @var HookRunner */ |
160 | private $hookRunner; |
161 | |
162 | /** @var PageStore */ |
163 | private $pageStore; |
164 | |
165 | /** @var TitleFactory */ |
166 | private $titleFactory; |
167 | |
168 | /** |
169 | * @param ILoadBalancer $loadBalancer |
170 | * @param SqlBlobStore $blobStore |
171 | * @param WANObjectCache $cache A cache for caching revision rows. This can be the local |
172 | * wiki's default instance even if $wikiId refers to a different wiki, since |
173 | * makeGlobalKey() is used to constructed a key that allows cached revision rows from |
174 | * the same database to be re-used between wikis. For example, enwiki and frwiki will |
175 | * use the same cache keys for revision rows from the wikidatawiki database, regardless |
176 | * of the cache's default key space. |
177 | * @param BagOStuff $localCache Another layer of cache, best to use APCu here. |
178 | * @param CommentStore $commentStore |
179 | * @param NameTableStore $contentModelStore |
180 | * @param NameTableStore $slotRoleStore |
181 | * @param SlotRoleRegistry $slotRoleRegistry |
182 | * @param ActorStore $actorStore |
183 | * @param IContentHandlerFactory $contentHandlerFactory |
184 | * @param PageStore $pageStore |
185 | * @param TitleFactory $titleFactory |
186 | * @param HookContainer $hookContainer |
187 | * @param false|string $wikiId Relevant wiki id or WikiAwareEntity::LOCAL for the current one |
188 | * |
189 | * @todo $blobStore should be allowed to be any BlobStore! |
190 | * |
191 | */ |
192 | public function __construct( |
193 | ILoadBalancer $loadBalancer, |
194 | SqlBlobStore $blobStore, |
195 | WANObjectCache $cache, |
196 | BagOStuff $localCache, |
197 | CommentStore $commentStore, |
198 | NameTableStore $contentModelStore, |
199 | NameTableStore $slotRoleStore, |
200 | SlotRoleRegistry $slotRoleRegistry, |
201 | ActorStore $actorStore, |
202 | IContentHandlerFactory $contentHandlerFactory, |
203 | PageStore $pageStore, |
204 | TitleFactory $titleFactory, |
205 | HookContainer $hookContainer, |
206 | $wikiId = WikiAwareEntity::LOCAL |
207 | ) { |
208 | Assert::parameterType( [ 'string', 'false' ], $wikiId, '$wikiId' ); |
209 | |
210 | $this->loadBalancer = $loadBalancer; |
211 | $this->blobStore = $blobStore; |
212 | $this->cache = $cache; |
213 | $this->localCache = $localCache; |
214 | $this->commentStore = $commentStore; |
215 | $this->contentModelStore = $contentModelStore; |
216 | $this->slotRoleStore = $slotRoleStore; |
217 | $this->slotRoleRegistry = $slotRoleRegistry; |
218 | $this->actorStore = $actorStore; |
219 | $this->wikiId = $wikiId; |
220 | $this->logger = new NullLogger(); |
221 | $this->contentHandlerFactory = $contentHandlerFactory; |
222 | $this->pageStore = $pageStore; |
223 | $this->titleFactory = $titleFactory; |
224 | $this->hookRunner = new HookRunner( $hookContainer ); |
225 | } |
226 | |
227 | public function setLogger( LoggerInterface $logger ) { |
228 | $this->logger = $logger; |
229 | } |
230 | |
231 | /** |
232 | * @return bool Whether the store is read-only |
233 | */ |
234 | public function isReadOnly() { |
235 | return $this->blobStore->isReadOnly(); |
236 | } |
237 | |
238 | /** |
239 | * Get the ID of the wiki this revision belongs to. |
240 | * |
241 | * @return string|false The wiki's logical name, of false to indicate the local wiki. |
242 | */ |
243 | public function getWikiId() { |
244 | return $this->wikiId; |
245 | } |
246 | |
247 | /** |
248 | * @param int $queryFlags a bit field composed of READ_XXX flags |
249 | * |
250 | * @return IReadableDatabase |
251 | */ |
252 | private function getDBConnectionRefForQueryFlags( $queryFlags ) { |
253 | if ( ( $queryFlags & IDBAccessObject::READ_LATEST ) == IDBAccessObject::READ_LATEST ) { |
254 | return $this->getPrimaryConnection(); |
255 | } else { |
256 | return $this->getReplicaConnection(); |
257 | } |
258 | } |
259 | |
260 | /** |
261 | * @param string|array $groups |
262 | * @return IReadableDatabase |
263 | */ |
264 | private function getReplicaConnection( $groups = [] ) { |
265 | // TODO: Replace with ICP |
266 | return $this->loadBalancer->getConnection( DB_REPLICA, $groups, $this->wikiId ); |
267 | } |
268 | |
269 | private function getPrimaryConnection(): IDatabase { |
270 | // TODO: Replace with ICP |
271 | return $this->loadBalancer->getConnection( DB_PRIMARY, [], $this->wikiId ); |
272 | } |
273 | |
274 | /** |
275 | * Determines the page Title based on the available information. |
276 | * |
277 | * MCR migration note: this corresponded to Revision::getTitle |
278 | * |
279 | * @deprecated since 1.36, Use RevisionRecord::getPage() instead. |
280 | * @note The resulting Title object will be misleading if the RevisionStore is not |
281 | * for the local wiki. |
282 | * |
283 | * @param int|null $pageId |
284 | * @param int|null $revId |
285 | * @param int $queryFlags |
286 | * |
287 | * @return Title |
288 | * @throws RevisionAccessException |
289 | */ |
290 | public function getTitle( $pageId, $revId, $queryFlags = IDBAccessObject::READ_NORMAL ) { |
291 | // TODO: Hard-deprecate this once getPage() returns a PageRecord. T195069 |
292 | if ( $this->wikiId !== WikiAwareEntity::LOCAL ) { |
293 | wfDeprecatedMsg( 'Using a Title object to refer to a page on another site.', '1.36' ); |
294 | } |
295 | |
296 | $page = $this->getPage( $pageId, $revId, $queryFlags ); |
297 | return $this->titleFactory->newFromPageIdentity( $page ); |
298 | } |
299 | |
300 | /** |
301 | * Determines the page based on the available information. |
302 | * |
303 | * @param int|null $pageId |
304 | * @param int|null $revId |
305 | * @param int $queryFlags |
306 | * |
307 | * @return PageIdentity |
308 | * @throws RevisionAccessException |
309 | */ |
310 | private function getPage( ?int $pageId, ?int $revId, int $queryFlags = IDBAccessObject::READ_NORMAL ) { |
311 | if ( !$pageId && !$revId ) { |
312 | throw new InvalidArgumentException( '$pageId and $revId cannot both be 0 or null' ); |
313 | } |
314 | |
315 | // This method recalls itself with READ_LATEST if READ_NORMAL doesn't get us a Title |
316 | // So ignore READ_LATEST_IMMUTABLE flags and handle the fallback logic in this method |
317 | if ( DBAccessObjectUtils::hasFlags( $queryFlags, IDBAccessObject::READ_LATEST_IMMUTABLE ) ) { |
318 | $queryFlags = IDBAccessObject::READ_NORMAL; |
319 | } |
320 | |
321 | // Loading by ID is best |
322 | if ( $pageId !== null && $pageId > 0 ) { |
323 | $page = $this->pageStore->getPageById( $pageId, $queryFlags ); |
324 | if ( $page ) { |
325 | return $this->wrapPage( $page ); |
326 | } |
327 | } |
328 | |
329 | // rev_id is defined as NOT NULL, but this revision may not yet have been inserted. |
330 | if ( $revId !== null && $revId > 0 ) { |
331 | $pageQuery = $this->pageStore->newSelectQueryBuilder( $queryFlags ) |
332 | ->join( 'revision', null, 'page_id=rev_page' ) |
333 | ->conds( [ 'rev_id' => $revId ] ) |
334 | ->caller( __METHOD__ ); |
335 | |
336 | $page = $pageQuery->fetchPageRecord(); |
337 | if ( $page ) { |
338 | return $this->wrapPage( $page ); |
339 | } |
340 | } |
341 | |
342 | // If we still don't have a title, fallback to primary DB if that wasn't already happening. |
343 | if ( $queryFlags === IDBAccessObject::READ_NORMAL ) { |
344 | $title = $this->getPage( $pageId, $revId, IDBAccessObject::READ_LATEST ); |
345 | if ( $title ) { |
346 | $this->logger->info( |
347 | __METHOD__ . ' fell back to READ_LATEST and got a Title.', |
348 | [ 'exception' => new RuntimeException() ] |
349 | ); |
350 | return $title; |
351 | } |
352 | } |
353 | |
354 | throw new RevisionAccessException( |
355 | 'Could not determine title for page ID {page_id} and revision ID {rev_id}', |
356 | [ |
357 | 'page_id' => $pageId, |
358 | 'rev_id' => $revId, |
359 | ] |
360 | ); |
361 | } |
362 | |
363 | /** |
364 | * @param PageIdentity $page |
365 | * |
366 | * @return PageIdentity |
367 | */ |
368 | private function wrapPage( PageIdentity $page ): PageIdentity { |
369 | if ( $this->wikiId === WikiAwareEntity::LOCAL ) { |
370 | // NOTE: since there is still a lot of code that needs a full Title, |
371 | // and uses Title::castFromPageIdentity() to get one, it's beneficial |
372 | // to create a Title right away if we can, so we don't have to convert |
373 | // over and over later on. |
374 | // When there is less need to convert to Title, this special case can |
375 | // be removed. |
376 | return $this->titleFactory->newFromPageIdentity( $page ); |
377 | } else { |
378 | return $page; |
379 | } |
380 | } |
381 | |
382 | /** |
383 | * @param mixed $value |
384 | * @param string $name |
385 | * |
386 | * @throws IncompleteRevisionException if $value is null |
387 | * @return mixed $value, if $value is not null |
388 | */ |
389 | private function failOnNull( $value, $name ) { |
390 | if ( $value === null ) { |
391 | throw new IncompleteRevisionException( |
392 | "$name must not be " . var_export( $value, true ) . "!" |
393 | ); |
394 | } |
395 | |
396 | return $value; |
397 | } |
398 | |
399 | /** |
400 | * @param mixed $value |
401 | * @param string $name |
402 | * |
403 | * @throws IncompleteRevisionException if $value is empty |
404 | * @return mixed $value, if $value is not null |
405 | */ |
406 | private function failOnEmpty( $value, $name ) { |
407 | if ( $value === null || $value === 0 || $value === '' ) { |
408 | throw new IncompleteRevisionException( |
409 | "$name must not be " . var_export( $value, true ) . "!" |
410 | ); |
411 | } |
412 | |
413 | return $value; |
414 | } |
415 | |
416 | /** |
417 | * Insert a new revision into the database, returning the new revision record |
418 | * on success and dies horribly on failure. |
419 | * |
420 | * This should be followed up by a WikiPage::updateRevisionOn on call to update |
421 | * page_latest on the page the revision is added to. |
422 | * |
423 | * MCR migration note: this replaced Revision::insertOn |
424 | * |
425 | * @param RevisionRecord $rev |
426 | * @param IDatabase $dbw (primary connection) |
427 | * |
428 | * @return RevisionRecord the new revision record. |
429 | */ |
430 | public function insertRevisionOn( RevisionRecord $rev, IDatabase $dbw ) { |
431 | // TODO: pass in a DBTransactionContext instead of a database connection. |
432 | $this->checkDatabaseDomain( $dbw ); |
433 | |
434 | $slotRoles = $rev->getSlotRoles(); |
435 | |
436 | // Make sure the main slot is always provided throughout migration |
437 | if ( !in_array( SlotRecord::MAIN, $slotRoles ) ) { |
438 | throw new IncompleteRevisionException( |
439 | 'main slot must be provided' |
440 | ); |
441 | } |
442 | |
443 | // Checks |
444 | $this->failOnNull( $rev->getSize(), 'size field' ); |
445 | $this->failOnEmpty( $rev->getSha1(), 'sha1 field' ); |
446 | $this->failOnEmpty( $rev->getTimestamp(), 'timestamp field' ); |
447 | $comment = $this->failOnNull( $rev->getComment( RevisionRecord::RAW ), 'comment' ); |
448 | $user = $this->failOnNull( $rev->getUser( RevisionRecord::RAW ), 'user' ); |
449 | $this->failOnNull( $user->getId(), 'user field' ); |
450 | $this->failOnEmpty( $user->getName(), 'user_text field' ); |
451 | |
452 | if ( !$rev->isReadyForInsertion() ) { |
453 | // This is here for future-proofing. At the time this check being added, it |
454 | // was redundant to the individual checks above. |
455 | throw new IncompleteRevisionException( 'Revision is incomplete' ); |
456 | } |
457 | |
458 | if ( $slotRoles == [ SlotRecord::MAIN ] ) { |
459 | // T239717: If the main slot is the only slot, make sure the revision's nominal size |
460 | // and hash match the main slot's nominal size and hash. |
461 | $mainSlot = $rev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW ); |