Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
68.79% |
97 / 141 |
|
61.29% |
19 / 31 |
CRAP | |
0.00% |
0 / 1 |
LinksUpdate | |
68.79% |
97 / 141 |
|
61.29% |
19 / 31 |
118.01 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
1 | |||
setTransactionTicket | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setMoveDetails | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
doUpdate | |
62.07% |
18 / 29 |
|
0.00% |
0 / 1 |
7.96 | |||
acquirePageLock | |
40.00% |
4 / 10 |
|
0.00% |
0 / 1 |
2.86 | |||
doIncrementalUpdate | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
queueRecursiveJobs | |
54.55% |
12 / 22 |
|
0.00% |
0 / 1 |
5.50 | |||
queueRecursiveJobsForTable | |
81.25% |
13 / 16 |
|
0.00% |
0 / 1 |
3.06 | |||
setStrictTestMode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPageId | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getParserOutput | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getImages | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setRevisionRecord | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getRevisionRecord | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setTriggeringUser | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTriggeringUser | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPageLinksTable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getExternalLinksTable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPagePropsTable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAddedLinks | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRemovedLinks | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAddedExternalLinks | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRemovedExternalLinks | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAddedProperties | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRemovedProperties | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPageReferenceIterator | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getPageReferenceArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
updateLinksTimestamp | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
getDB | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
isRecursive | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * Updater for link tracking tables after a page edit. |
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 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | */ |
22 | |
23 | namespace MediaWiki\Deferred\LinksUpdate; |
24 | |
25 | use IDBAccessObject; |
26 | use Job; |
27 | use MediaWiki\Cache\BacklinkCache; |
28 | use MediaWiki\Deferred\AutoCommitUpdate; |
29 | use MediaWiki\Deferred\DataUpdate; |
30 | use MediaWiki\Deferred\DeferredUpdates; |
31 | use MediaWiki\HookContainer\ProtectedHookAccessorTrait; |
32 | use MediaWiki\Logger\LoggerFactory; |
33 | use MediaWiki\MainConfigNames; |
34 | use MediaWiki\MediaWikiServices; |
35 | use MediaWiki\Page\PageIdentity; |
36 | use MediaWiki\Page\PageReference; |
37 | use MediaWiki\Page\PageReferenceValue; |
38 | use MediaWiki\Parser\ParserOutput; |
39 | use MediaWiki\Revision\RevisionRecord; |
40 | use MediaWiki\Title\Title; |
41 | use MediaWiki\User\UserIdentity; |
42 | use RefreshLinksJob; |
43 | use RuntimeException; |
44 | use Wikimedia\Rdbms\IConnectionProvider; |
45 | use Wikimedia\Rdbms\IDatabase; |
46 | use Wikimedia\ScopedCallback; |
47 | |
48 | /** |
49 | * Class the manages updates of *_link tables as well as similar extension-managed tables |
50 | * |
51 | * @note: LinksUpdate is managed by DeferredUpdates::execute(). Do not run this in a transaction. |
52 | * |
53 | * See docs/deferred.txt |
54 | */ |
55 | class LinksUpdate extends DataUpdate { |
56 | use ProtectedHookAccessorTrait; |
57 | |
58 | /** @var int Page ID of the article linked from */ |
59 | protected $mId; |
60 | |
61 | /** @var Title Title object of the article linked from */ |
62 | protected $mTitle; |
63 | |
64 | /** @var ParserOutput */ |
65 | protected $mParserOutput; |
66 | |
67 | /** @var bool Whether to queue jobs for recursive updates */ |
68 | protected $mRecursive; |
69 | |
70 | /** @var bool Whether the page's redirect target may have changed in the latest revision */ |
71 | protected $mMaybeRedirectChanged; |
72 | |
73 | /** @var RevisionRecord Revision for which this update has been triggered */ |
74 | private $mRevisionRecord; |
75 | |
76 | /** |
77 | * @var UserIdentity|null |
78 | */ |
79 | private $user; |
80 | |
81 | /** @var IDatabase */ |
82 | private $db; |
83 | |
84 | /** @var LinksTableGroup */ |
85 | private $tableFactory; |
86 | |
87 | private IConnectionProvider $connectionProvider; |
88 | |
89 | /** |
90 | * @param PageIdentity $page The page we're updating |
91 | * @param ParserOutput $parserOutput Output from a full parse of this page |
92 | * @param bool $recursive Queue jobs for recursive updates? |
93 | * @param bool $maybeRedirectChanged True if the page's redirect target may have changed in the |
94 | * latest revision. If false, this is used as a hint to skip some unnecessary updates. |
95 | */ |
96 | public function __construct( |
97 | PageIdentity $page, |
98 | ParserOutput $parserOutput, |
99 | $recursive = true, |
100 | $maybeRedirectChanged = true |
101 | ) { |
102 | parent::__construct(); |
103 | |
104 | $this->mTitle = Title::newFromPageIdentity( $page ); |
105 | $this->mParserOutput = $parserOutput; |
106 | $this->mRecursive = $recursive; |
107 | $this->mMaybeRedirectChanged = $maybeRedirectChanged; |
108 | |
109 | $services = MediaWikiServices::getInstance(); |
110 | $config = $services->getMainConfig(); |
111 | $this->tableFactory = new LinksTableGroup( |
112 | $services->getObjectFactory(), |
113 | $services->getDBLoadBalancerFactory(), |
114 | $services->getCollationFactory(), |
115 | $page, |
116 | $services->getLinkTargetLookup(), |
117 | $config->get( MainConfigNames::UpdateRowsPerQuery ), |
118 | $config->get( MainConfigNames::TempCategoryCollations ) |
119 | ); |
120 | // TODO: this does not have to be called in LinksDeletionUpdate |
121 | $this->tableFactory->setParserOutput( $parserOutput ); |
122 | $this->connectionProvider = $services->getDBLoadBalancerFactory(); |
123 | } |
124 | |
125 | public function setTransactionTicket( $ticket ) { |
126 | parent::setTransactionTicket( $ticket ); |
127 | $this->tableFactory->setTransactionTicket( $ticket ); |
128 | } |
129 | |
130 | /** |
131 | * Notify LinksUpdate that a move has just been completed and set the |
132 | * original title |
133 | * |
134 | * @param PageReference $oldPage |
135 | */ |
136 | public function setMoveDetails( PageReference $oldPage ) { |
137 | $this->tableFactory->setMoveDetails( $oldPage ); |
138 | } |
139 | |
140 | /** |
141 | * Update link tables with outgoing links from an updated article |
142 | * |
143 | * @note this is managed by DeferredUpdates::execute(). Do not run this in a transaction. |
144 | */ |
145 | public function doUpdate() { |
146 | if ( !$this->mId ) { |
147 | // NOTE: subclasses may initialize mId directly! |
148 | $this->mId = $this->mTitle->getArticleID( IDBAccessObject::READ_LATEST ); |
149 | } |
150 | |
151 | if ( !$this->mId ) { |
152 | // Probably due to concurrent deletion or renaming of the page |
153 | $logger = LoggerFactory::getInstance( 'SecondaryDataUpdate' ); |
154 | $logger->warning( |
155 | 'LinksUpdate: The Title object yields no ID. Perhaps the page was deleted?', |
156 | [ |
157 | 'page_title' => $this->mTitle->getPrefixedDBkey(), |
158 | 'cause_action' => $this->getCauseAction(), |
159 | 'cause_agent' => $this->getCauseAgent() |
160 | ] |
161 | ); |
162 | |
163 | // nothing to do |
164 | return; |
165 | } |
166 | |
167 | // Do any setup that needs to be done prior to acquiring the lock |
168 | // Calling getAll() here has the side-effect of calling |
169 | // LinksUpdateBatch::setParserOutput() on all subclasses, allowing |
170 | // those methods to also do pre-lock operations. |
171 | foreach ( $this->tableFactory->getAll() as $table ) { |
172 | $table->beforeLock(); |
173 | } |
174 | |
175 | if ( $this->ticket ) { |
176 | // Make sure all links update threads see the changes of each other. |
177 | // This handles the case when updates have to batched into several COMMITs. |
178 | $scopedLock = self::acquirePageLock( $this->getDB(), $this->mId ); |
179 | if ( !$scopedLock ) { |
180 | throw new RuntimeException( "Could not acquire lock for page ID '{$this->mId}'." ); |
181 | } |
182 | } |
183 | |
184 | $this->getHookRunner()->onLinksUpdate( $this ); |
185 | $this->doIncrementalUpdate(); |
186 | |
187 | // Commit and release the lock (if set) |
188 | ScopedCallback::consume( $scopedLock ); |
189 | // Run post-commit hook handlers without DBO_TRX |
190 | DeferredUpdates::addUpdate( new AutoCommitUpdate( |
191 | $this->getDB(), |
192 | __METHOD__, |
193 | function () { |
194 | $this->getHookRunner()->onLinksUpdateComplete( $this, $this->ticket ); |
195 | } |
196 | ) ); |
197 | } |
198 | |
199 | /** |
200 | * Acquire a session-level lock for performing link table updates for a page on a DB |
201 | * |
202 | * @param IDatabase $dbw |
203 | * @param int $pageId |
204 | * @param string $why One of (job, atomicity) |
205 | * @return ScopedCallback|null |
206 | * @since 1.27 |
207 | */ |
208 | public static function acquirePageLock( IDatabase $dbw, $pageId, $why = 'atomicity' ) { |
209 | $key = "{$dbw->getDomainID()}:LinksUpdate:$why:pageid:$pageId"; // per-wiki |
210 | $scopedLock = $dbw->getScopedLockAndFlush( $key, __METHOD__, 15 ); |
211 | if ( !$scopedLock ) { |
212 | $logger = LoggerFactory::getInstance( 'SecondaryDataUpdate' ); |
213 | $logger->info( "Could not acquire lock '{key}' for page ID '{page_id}'.", [ |
214 | 'key' => $key, |
215 | 'page_id' => $pageId, |
216 | ] ); |
217 | return null; |
218 | } |
219 | |
220 | return $scopedLock; |
221 | } |
222 | |
223 | protected function doIncrementalUpdate() { |
224 | foreach ( $this->tableFactory->getAll() as $table ) { |
225 | $table->update(); |
226 | } |
227 | |
228 | # Refresh links of all pages including this page |
229 | # This will be in a separate transaction |
230 | if ( $this->mRecursive ) { |
231 | $this->queueRecursiveJobs(); |
232 | } |
233 | |
234 | # Update the links table freshness for this title |
235 | $this->updateLinksTimestamp(); |
236 | } |
237 | |
238 | /** |
239 | * Queue recursive jobs for this page |
240 | * |
241 | * Which means do LinksUpdate on all pages that include the current page, |
242 | * using the job queue. |
243 | */ |
244 | protected function queueRecursiveJobs() { |
245 | $services = MediaWikiServices::getInstance(); |
246 | $backlinkCache = $services->getBacklinkCacheFactory() |
247 | ->getBacklinkCache( $this->mTitle ); |
248 | $action = $this->getCauseAction(); |
249 | $agent = $this->getCauseAgent(); |
250 | |
251 | self::queueRecursiveJobsForTable( |
252 | $this->mTitle, 'templatelinks', $action, $agent, $backlinkCache |
253 | ); |
254 | if ( $this->mMaybeRedirectChanged && $this->mTitle->getNamespace() === NS_FILE ) { |
255 | // Process imagelinks in case the redirect target has changed |
256 | self::queueRecursiveJobsForTable( |
257 | $this->mTitle, 'imagelinks', $action, $agent, $backlinkCache |
258 | ); |
259 | } |
260 | |
261 | // Get jobs for cascade-protected backlinks for a high priority queue. |
262 | // If meta-templates change to using a new template, the new template |
263 | // should be implicitly protected as soon as possible, if applicable. |
264 | // These jobs duplicate a subset of the above ones, but can run sooner. |
265 | // Which ever runs first generally no-ops the other one. |
266 | $jobs = []; |
267 | foreach ( $backlinkCache->getCascadeProtectedLinkPages() as $page ) { |
268 | $jobs[] = RefreshLinksJob::newPrioritized( |
269 | $page, |
270 | [ |
271 | 'causeAction' => $action, |
272 | 'causeAgent' => $agent |
273 | ] |
274 | ); |
275 | } |
276 | $services->getJobQueueGroup()->push( $jobs ); |
277 | } |
278 | |
279 | /** |
280 | * Queue a RefreshLinks job for any table. |
281 | * |
282 | * @param PageIdentity $page Page to do job for |
283 | * @param string $table Table to use (e.g. 'templatelinks') |
284 | * @param string $action Triggering action |
285 | * @param string $userName Triggering user name |
286 | * @param BacklinkCache|null $backlinkCache |
287 | */ |
288 | public static function queueRecursiveJobsForTable( |
289 | PageIdentity $page, $table, $action = 'LinksUpdate', $userName = 'unknown', ?BacklinkCache $backlinkCache = null |
290 | ) { |
291 | $title = Title::newFromPageIdentity( $page ); |
292 | if ( !$backlinkCache ) { |
293 | wfDeprecatedMsg( __METHOD__ . " needs a BacklinkCache object, null passed", '1.37' ); |
294 | $backlinkCache = MediaWikiServices::getInstance()->getBacklinkCacheFactory() |
295 | ->getBacklinkCache( $title ); |
296 | } |
297 | if ( $backlinkCache->hasLinks( $table ) ) { |
298 | $job = new RefreshLinksJob( |
299 | $title, |
300 | [ |
301 | 'table' => $table, |
302 | 'recursive' => true, |
303 | ] + Job::newRootJobParams( // "overall" refresh links job info |
304 | "refreshlinks:{$table}:{$title->getPrefixedText()}" |
305 | ) + [ 'causeAction' => $action, 'causeAgent' => $userName ] |
306 | ); |
307 | |
308 | MediaWikiServices::getInstance()->getJobQueueGroup()->push( $job ); |
309 | } |
310 | } |
311 | |
312 | /** |
313 | * Omit conflict resolution options from the insert query so that testing |
314 | * can confirm that the incremental update logic was correct. |
315 | * |
316 | * @param bool $mode |
317 | */ |
318 | public function setStrictTestMode( $mode = true ) { |
319 | $this->tableFactory->setStrictTestMode( $mode ); |
320 | } |
321 | |
322 | /** |
323 | * Return the title object of the page being updated |
324 | * @return Title |
325 | */ |
326 | public function getTitle() { |
327 | return $this->mTitle; |
328 | } |
329 | |
330 | /** |
331 | * Get the page_id of the page being updated |
332 | * |
333 | * @since 1.38 |
334 | * @return int |
335 | */ |
336 | public function getPageId() { |
337 | if ( $this->mId ) { |
338 | return $this->mId; |
339 | } else { |
340 | return $this->mTitle->getArticleID(); |
341 | } |
342 | } |
343 | |
344 | /** |
345 | * Returns parser output |
346 | * @since 1.19 |
347 | * @return ParserOutput |
348 | */ |
349 | public function getParserOutput() { |
350 | return $this->mParserOutput; |
351 | } |
352 | |
353 | /** |
354 | * Return the list of images used as generated by the parser |
355 | * @return array |
356 | */ |
357 | public function getImages() { |
358 | return $this->getParserOutput()->getImages(); |
359 | } |
360 | |
361 | /** |
362 | * Set the RevisionRecord corresponding to this LinksUpdate |
363 | * |
364 | * @since 1.35 |
365 | * @param RevisionRecord $revisionRecord |
366 | */ |
367 | public function setRevisionRecord( RevisionRecord $revisionRecord ) { |
368 | $this->mRevisionRecord = $revisionRecord; |
369 | $this->tableFactory->setRevision( $revisionRecord ); |
370 | } |
371 | |
372 | /** |
373 | * @since 1.35 |
374 | * @return RevisionRecord|null |
375 | */ |
376 | public function getRevisionRecord() { |
377 | return $this->mRevisionRecord; |
378 | } |
379 | |
380 | /** |
381 | * Set the user who triggered this LinksUpdate |
382 | * |
383 | * @since 1.27 |
384 | * @param UserIdentity $user |
385 | */ |
386 | public function setTriggeringUser( UserIdentity $user ) { |
387 | $this->user = $user; |
388 | } |
389 | |
390 | /** |
391 | * Get the user who triggered this LinksUpdate |
392 | * |
393 | * @since 1.27 |
394 | * @return UserIdentity|null |
395 | */ |
396 | public function getTriggeringUser(): ?UserIdentity { |
397 | return $this->user; |
398 | } |
399 | |
400 | /** |
401 | * @return PageLinksTable |
402 | */ |
403 | protected function getPageLinksTable(): PageLinksTable { |
404 | // @phan-suppress-next-line PhanTypeMismatchReturnSuperType |
405 | return $this->tableFactory->get( 'pagelinks' ); |
406 | } |
407 | |
408 | /** |
409 | * @return ExternalLinksTable |
410 | */ |
411 | protected function getExternalLinksTable(): ExternalLinksTable { |
412 | // @phan-suppress-next-line PhanTypeMismatchReturnSuperType |
413 | return $this->tableFactory->get( 'externallinks' ); |
414 | } |
415 | |
416 | /** |
417 | * @return PagePropsTable |
418 | */ |
419 | protected function getPagePropsTable(): PagePropsTable { |
420 | // @phan-suppress-next-line PhanTypeMismatchReturnSuperType |
421 | return $this->tableFactory->get( 'page_props' ); |
422 | } |
423 | |
424 | /** |
425 | * Fetch page links added by this LinksUpdate. Only available after the update is complete. |
426 | * |
427 | * @since 1.22 |
428 | * @deprecated since 1.38 use getPageReferenceIterator() or getPageReferenceArray() |
429 | * @return Title[] Array of Titles |
430 | */ |
431 | public function getAddedLinks() { |
432 | return $this->getPageLinksTable()->getTitleArray( LinksTable::INSERTED ); |
433 | } |
434 | |
435 | /** |
436 | * Fetch page links removed by this LinksUpdate. Only available after the update is complete. |
437 | * |
438 | * @since 1.22 |
439 | * @deprecated since 1.38 use getPageReferenceIterator() or getPageReferenceArray() |
440 | * @return Title[] Array of Titles |
441 | */ |
442 | public function getRemovedLinks() { |
443 | return $this->getPageLinksTable()->getTitleArray( LinksTable::DELETED ); |
444 | } |
445 | |
446 | /** |
447 | * Fetch external links added by this LinksUpdate. Only available after |
448 | * the update is complete. |
449 | * @since 1.33 |
450 | * @return null|array Array of Strings |
451 | */ |
452 | public function getAddedExternalLinks() { |
453 | return $this->getExternalLinksTable()->getStringArray( LinksTable::INSERTED ); |
454 | } |
455 | |
456 | /** |
457 | * Fetch external links removed by this LinksUpdate. Only available after |
458 | * the update is complete. |
459 | * @since 1.33 |
460 | * @return null|string[] |
461 | */ |
462 | public function getRemovedExternalLinks() { |
463 | return $this->getExternalLinksTable()->getStringArray( LinksTable::DELETED ); |
464 | } |
465 | |
466 | /** |
467 | * Fetch page properties added by this LinksUpdate. |
468 | * Only available after the update is complete. |
469 | * @since 1.28 |
470 | * @return null|array |
471 | */ |
472 | public function getAddedProperties() { |
473 | return $this->getPagePropsTable()->getAssocArray( LinksTable::INSERTED ); |
474 | } |
475 | |
476 | /** |
477 | * Fetch page properties removed by this LinksUpdate. |
478 | * Only available after the update is complete. |
479 | * @since 1.28 |
480 | * @return null|array |
481 | */ |
482 | public function getRemovedProperties() { |
483 | return $this->getPagePropsTable()->getAssocArray( LinksTable::DELETED ); |
484 | } |
485 | |
486 | /** |
487 | * Get an iterator over PageReferenceValue objects corresponding to a given set |
488 | * type in a given table. |
489 | * |
490 | * @since 1.38 |
491 | * @param string $tableName The name of any table that links to local titles |
492 | * @param int $setType One of: |
493 | * - LinksTable::INSERTED: The inserted links |
494 | * - LinksTable::DELETED: The deleted links |
495 | * - LinksTable::CHANGED: Both the inserted and deleted links |
496 | * - LinksTable::OLD: The old set of links, loaded before the update |
497 | * - LinksTable::NEW: The new set of links from the ParserOutput |
498 | * @return iterable<PageReferenceValue> |
499 | * @phan-return \Traversable |
500 | */ |
501 | public function getPageReferenceIterator( $tableName, $setType ) { |
502 | $table = $this->tableFactory->get( $tableName ); |
503 | if ( $table instanceof TitleLinksTable ) { |
504 | return $table->getPageReferenceIterator( $setType ); |
505 | } else { |
506 | throw new \InvalidArgumentException( |
507 | __METHOD__ . ": $tableName does not have a list of titles" ); |
508 | } |
509 | } |
510 | |
511 | /** |
512 | * Same as getPageReferenceIterator() but converted to an array for convenience |
513 | * (at the expense of additional time and memory usage) |
514 | * |
515 | * @since 1.38 |
516 | * @param string $tableName |
517 | * @param int $setType |
518 | * @return PageReferenceValue[] |
519 | */ |
520 | public function getPageReferenceArray( $tableName, $setType ) { |
521 | return iterator_to_array( $this->getPageReferenceIterator( $tableName, $setType ) ); |
522 | } |
523 | |
524 | /** |
525 | * Update links table freshness |
526 | */ |
527 | protected function updateLinksTimestamp() { |
528 | if ( $this->mId ) { |
529 | // The link updates made here only reflect the freshness of the parser output |
530 | $timestamp = $this->mParserOutput->getCacheTime(); |
531 | $this->getDB()->newUpdateQueryBuilder() |
532 | ->update( 'page' ) |
533 | ->set( [ 'page_links_updated' => $this->getDB()->timestamp( $timestamp ) ] ) |
534 | ->where( [ 'page_id' => $this->mId ] ) |
535 | ->caller( __METHOD__ )->execute(); |
536 | } |
537 | } |
538 | |
539 | /** |
540 | * @return IDatabase |
541 | */ |
542 | protected function getDB() { |
543 | if ( !$this->db ) { |
544 | $this->db = $this->connectionProvider->getPrimaryDatabase(); |
545 | } |
546 | |
547 | return $this->db; |
548 | } |
549 | |
550 | /** |
551 | * Whether or not this LinksUpdate will also update pages which transclude the |
552 | * current page or otherwise depend on it. |
553 | * |
554 | * @return bool |
555 | */ |
556 | public function isRecursive() { |
557 | return $this->mRecursive; |
558 | } |
559 | } |