Update formatting on includes/deferred/
[lhc/web/wiklou.git] / includes / deferred / LinksUpdate.php
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 /**
24 * See docs/deferred.txt
25 *
26 * @todo document (e.g. one-sentence top-level class description).
27 */
28 class LinksUpdate extends SqlDataUpdate {
29 // @todo make members protected, but make sure extensions don't break
30
31 public $mId, //!< Page ID of the article linked from
32 $mTitle, //!< Title object of the article linked from
33 $mParserOutput, //!< Parser output
34 $mLinks, //!< Map of title strings to IDs for the links in the document
35 $mImages, //!< DB keys of the images used, in the array key only
36 $mTemplates, //!< Map of title strings to IDs for the template references, including broken ones
37 $mExternals, //!< URLs of external links, array key only
38 $mCategories, //!< Map of category names to sort keys
39 $mInterlangs, //!< Map of language codes to titles
40 $mProperties, //!< Map of arbitrary name to value
41 $mDb, //!< Database connection reference
42 $mOptions, //!< SELECT options to be used (array)
43 $mRecursive; //!< Whether to queue jobs for recursive updates
44
45 /**
46 * @var null|array Added links if calculated.
47 */
48 private $linkInsertions = null;
49
50 /**
51 * @var null|array Deleted links if calculated.
52 */
53 private $linkDeletions = null;
54
55 /**
56 * Constructor
57 *
58 * @param $title Title of the page we're updating
59 * @param $parserOutput ParserOutput: output from a full parse of this page
60 * @param $recursive Boolean: queue jobs for recursive updates?
61 * @throws MWException
62 */
63 function __construct( $title, $parserOutput, $recursive = true ) {
64 parent::__construct( false ); // no implicit transaction
65
66 if ( !( $title instanceof Title ) ) {
67 throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " .
68 "Please see Article::editUpdates() for an invocation example.\n" );
69 }
70
71 if ( !( $parserOutput instanceof ParserOutput ) ) {
72 throw new MWException( "The calling convention to LinksUpdate::__construct() has changed. " .
73 "Please see WikiPage::doEditUpdates() for an invocation example.\n" );
74 }
75
76 $this->mTitle = $title;
77 $this->mId = $title->getArticleID();
78
79 if ( !$this->mId ) {
80 throw new MWException( "The Title object did not provide an article ID. Perhaps the page doesn't exist?" );
81 }
82
83 $this->mParserOutput = $parserOutput;
84
85 $this->mLinks = $parserOutput->getLinks();
86 $this->mImages = $parserOutput->getImages();
87 $this->mTemplates = $parserOutput->getTemplates();
88 $this->mExternals = $parserOutput->getExternalLinks();
89 $this->mCategories = $parserOutput->getCategories();
90 $this->mProperties = $parserOutput->getProperties();
91 $this->mInterwikis = $parserOutput->getInterwikiLinks();
92
93 # Convert the format of the interlanguage links
94 # I didn't want to change it in the ParserOutput, because that array is passed all
95 # the way back to the skin, so either a skin API break would be required, or an
96 # inefficient back-conversion.
97 $ill = $parserOutput->getLanguageLinks();
98 $this->mInterlangs = array();
99 foreach ( $ill as $link ) {
100 list( $key, $title ) = explode( ':', $link, 2 );
101 $this->mInterlangs[$key] = $title;
102 }
103
104 foreach ( $this->mCategories as &$sortkey ) {
105 # If the sortkey is longer then 255 bytes,
106 # it truncated by DB, and then doesn't get
107 # matched when comparing existing vs current
108 # categories, causing bug 25254.
109 # Also. substr behaves weird when given "".
110 if ( $sortkey !== '' ) {
111 $sortkey = substr( $sortkey, 0, 255 );
112 }
113 }
114
115 $this->mRecursive = $recursive;
116
117 wfRunHooks( 'LinksUpdateConstructed', array( &$this ) );
118 }
119
120 /**
121 * Update link tables with outgoing links from an updated article
122 */
123 public function doUpdate() {
124 wfRunHooks( 'LinksUpdate', array( &$this ) );
125 $this->doIncrementalUpdate();
126 wfRunHooks( 'LinksUpdateComplete', array( &$this ) );
127 }
128
129 protected function doIncrementalUpdate() {
130 wfProfileIn( __METHOD__ );
131
132 # Page links
133 $existing = $this->getExistingLinks();
134 $this->linkDeletions = $this->getLinkDeletions( $existing );
135 $this->linkInsertions = $this->getLinkInsertions( $existing );
136 $this->incrTableUpdate( 'pagelinks', 'pl', $this->linkDeletions, $this->linkInsertions );
137
138 # Image links
139 $existing = $this->getExistingImages();
140
141 $imageDeletes = $this->getImageDeletions( $existing );
142 $this->incrTableUpdate( 'imagelinks', 'il', $imageDeletes,
143 $this->getImageInsertions( $existing ) );
144
145 # Invalidate all image description pages which had links added or removed
146 $imageUpdates = $imageDeletes + array_diff_key( $this->mImages, $existing );
147 $this->invalidateImageDescriptions( $imageUpdates );
148
149 # External links
150 $existing = $this->getExistingExternals();
151 $this->incrTableUpdate( 'externallinks', 'el', $this->getExternalDeletions( $existing ),
152 $this->getExternalInsertions( $existing ) );
153
154 # Language links
155 $existing = $this->getExistingInterlangs();
156 $this->incrTableUpdate( 'langlinks', 'll', $this->getInterlangDeletions( $existing ),
157 $this->getInterlangInsertions( $existing ) );
158
159 # Inline interwiki links
160 $existing = $this->getExistingInterwikis();
161 $this->incrTableUpdate( 'iwlinks', 'iwl', $this->getInterwikiDeletions( $existing ),
162 $this->getInterwikiInsertions( $existing ) );
163
164 # Template links
165 $existing = $this->getExistingTemplates();
166 $this->incrTableUpdate( 'templatelinks', 'tl', $this->getTemplateDeletions( $existing ),
167 $this->getTemplateInsertions( $existing ) );
168
169 # Category links
170 $existing = $this->getExistingCategories();
171
172 $categoryDeletes = $this->getCategoryDeletions( $existing );
173
174 $this->incrTableUpdate( 'categorylinks', 'cl', $categoryDeletes,
175 $this->getCategoryInsertions( $existing ) );
176
177 # Invalidate all categories which were added, deleted or changed (set symmetric difference)
178 $categoryInserts = array_diff_assoc( $this->mCategories, $existing );
179 $categoryUpdates = $categoryInserts + $categoryDeletes;
180 $this->invalidateCategories( $categoryUpdates );
181 $this->updateCategoryCounts( $categoryInserts, $categoryDeletes );
182
183 # Page properties
184 $existing = $this->getExistingProperties();
185
186 $propertiesDeletes = $this->getPropertyDeletions( $existing );
187
188 $this->incrTableUpdate( 'page_props', 'pp', $propertiesDeletes,
189 $this->getPropertyInsertions( $existing ) );
190
191 # Invalidate the necessary pages
192 $changed = $propertiesDeletes + array_diff_assoc( $this->mProperties, $existing );
193 $this->invalidateProperties( $changed );
194
195 # Refresh links of all pages including this page
196 # This will be in a separate transaction
197 if ( $this->mRecursive ) {
198 $this->queueRecursiveJobs();
199 }
200
201 wfProfileOut( __METHOD__ );
202 }
203
204 /**
205 * Queue recursive jobs for this page
206 *
207 * Which means do LinksUpdate on all templates
208 * that include the current page, using the job queue.
209 */
210 function queueRecursiveJobs() {
211 self::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
212 }
213
214 /**
215 * Queue a RefreshLinks job for any table.
216 *
217 * @param Title $title Title to do job for
218 * @param String $table Table to use (e.g. 'templatelinks')
219 */
220 public static function queueRecursiveJobsForTable( Title $title, $table ) {
221 wfProfileIn( __METHOD__ );
222 if ( $title->getBacklinkCache()->hasLinks( $table ) ) {
223 $job = new RefreshLinksJob2(
224 $title,
225 array(
226 'table' => $table,
227 ) + Job::newRootJobParams( // "overall" refresh links job info
228 "refreshlinks:{$table}:{$title->getPrefixedText()}"
229 )
230 );
231 JobQueueGroup::singleton()->push( $job );
232 JobQueueGroup::singleton()->deduplicateRootJob( $job );
233 }
234 wfProfileOut( __METHOD__ );
235 }
236
237 /**
238 * @param $cats
239 */
240 function invalidateCategories( $cats ) {
241 $this->invalidatePages( NS_CATEGORY, array_keys( $cats ) );
242 }
243
244 /**
245 * Update all the appropriate counts in the category table.
246 * @param array $added associative array of category name => sort key
247 * @param array $deleted associative array of category name => sort key
248 */
249 function updateCategoryCounts( $added, $deleted ) {
250 $a = WikiPage::factory( $this->mTitle );
251 $a->updateCategoryCounts(
252 array_keys( $added ), array_keys( $deleted )
253 );
254 }
255
256 /**
257 * @param $images
258 */
259 function invalidateImageDescriptions( $images ) {
260 $this->invalidatePages( NS_FILE, array_keys( $images ) );
261 }
262
263 /**
264 * Update a table by doing a delete query then an insert query
265 * @param $table
266 * @param $prefix
267 * @param $deletions
268 * @param $insertions
269 */
270 function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
271 if ( $table == 'page_props' ) {
272 $fromField = 'pp_page';
273 } else {
274 $fromField = "{$prefix}_from";
275 }
276 $where = array( $fromField => $this->mId );
277 if ( $table == 'pagelinks' || $table == 'templatelinks' || $table == 'iwlinks' ) {
278 if ( $table == 'iwlinks' ) {
279 $baseKey = 'iwl_prefix';
280 } else {
281 $baseKey = "{$prefix}_namespace";
282 }
283 $clause = $this->mDb->makeWhereFrom2d( $deletions, $baseKey, "{$prefix}_title" );
284 if ( $clause ) {
285 $where[] = $clause;
286 } else {
287 $where = false;
288 }
289 } else {
290 if ( $table == 'langlinks' ) {
291 $toField = 'll_lang';
292 } elseif ( $table == 'page_props' ) {
293 $toField = 'pp_propname';
294 } else {
295 $toField = $prefix . '_to';
296 }
297 if ( count( $deletions ) ) {
298 $where[] = "$toField IN (" . $this->mDb->makeList( array_keys( $deletions ) ) . ')';
299 } else {
300 $where = false;
301 }
302 }
303 if ( $where ) {
304 $this->mDb->delete( $table, $where, __METHOD__ );
305 }
306 if ( count( $insertions ) ) {
307 $this->mDb->insert( $table, $insertions, __METHOD__, 'IGNORE' );
308 wfRunHooks( 'LinksUpdateAfterInsert', array( $this, $table, $insertions ) );
309 }
310 }
311
312 /**
313 * Get an array of pagelinks insertions for passing to the DB
314 * Skips the titles specified by the 2-D array $existing
315 * @param $existing array
316 * @return array
317 */
318 private function getLinkInsertions( $existing = array() ) {
319 $arr = array();
320 foreach ( $this->mLinks as $ns => $dbkeys ) {
321 $diffs = isset( $existing[$ns] )
322 ? array_diff_key( $dbkeys, $existing[$ns] )
323 : $dbkeys;
324 foreach ( $diffs as $dbk => $id ) {
325 $arr[] = array(
326 'pl_from' => $this->mId,
327 'pl_namespace' => $ns,
328 'pl_title' => $dbk
329 );
330 }
331 }
332
333 return $arr;
334 }
335
336 /**
337 * Get an array of template insertions. Like getLinkInsertions()
338 * @param $existing array
339 * @return array
340 */
341 private function getTemplateInsertions( $existing = array() ) {
342 $arr = array();
343 foreach ( $this->mTemplates as $ns => $dbkeys ) {
344 $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
345 foreach ( $diffs as $dbk => $id ) {
346 $arr[] = array(
347 'tl_from' => $this->mId,
348 'tl_namespace' => $ns,
349 'tl_title' => $dbk
350 );
351 }
352 }
353
354 return $arr;
355 }
356
357 /**
358 * Get an array of image insertions
359 * Skips the names specified in $existing
360 * @param $existing array
361 * @return array
362 */
363 private function getImageInsertions( $existing = array() ) {
364 $arr = array();
365 $diffs = array_diff_key( $this->mImages, $existing );
366 foreach ( $diffs as $iname => $dummy ) {
367 $arr[] = array(
368 'il_from' => $this->mId,
369 'il_to' => $iname
370 );
371 }
372
373 return $arr;
374 }
375
376 /**
377 * Get an array of externallinks insertions. Skips the names specified in $existing
378 * @param $existing array
379 * @return array
380 */
381 private function getExternalInsertions( $existing = array() ) {
382 $arr = array();
383 $diffs = array_diff_key( $this->mExternals, $existing );
384 foreach ( $diffs as $url => $dummy ) {
385 foreach ( wfMakeUrlIndexes( $url ) as $index ) {
386 $arr[] = array(
387 'el_from' => $this->mId,
388 'el_to' => $url,
389 'el_index' => $index,
390 );
391 }
392 }
393
394 return $arr;
395 }
396
397 /**
398 * Get an array of category insertions
399 *
400 * @param array $existing mapping existing category names to sort keys. If both
401 * match a link in $this, the link will be omitted from the output
402 *
403 * @return array
404 */
405 private function getCategoryInsertions( $existing = array() ) {
406 global $wgContLang, $wgCategoryCollation;
407 $diffs = array_diff_assoc( $this->mCategories, $existing );
408 $arr = array();
409 foreach ( $diffs as $name => $prefix ) {
410 $nt = Title::makeTitleSafe( NS_CATEGORY, $name );
411 $wgContLang->findVariantLink( $name, $nt, true );
412
413 if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
414 $type = 'subcat';
415 } elseif ( $this->mTitle->getNamespace() == NS_FILE ) {
416 $type = 'file';
417 } else {
418 $type = 'page';
419 }
420
421 # Treat custom sortkeys as a prefix, so that if multiple
422 # things are forced to sort as '*' or something, they'll
423 # sort properly in the category rather than in page_id
424 # order or such.
425 $sortkey = Collation::singleton()->getSortKey(
426 $this->mTitle->getCategorySortkey( $prefix ) );
427
428 $arr[] = array(
429 'cl_from' => $this->mId,
430 'cl_to' => $name,
431 'cl_sortkey' => $sortkey,
432 'cl_timestamp' => $this->mDb->timestamp(),
433 'cl_sortkey_prefix' => $prefix,
434 'cl_collation' => $wgCategoryCollation,
435 'cl_type' => $type,
436 );
437 }
438
439 return $arr;
440 }
441
442 /**
443 * Get an array of interlanguage link insertions
444 *
445 * @param array $existing mapping existing language codes to titles
446 *
447 * @return array
448 */
449 private function getInterlangInsertions( $existing = array() ) {
450 $diffs = array_diff_assoc( $this->mInterlangs, $existing );
451 $arr = array();
452 foreach ( $diffs as $lang => $title ) {
453 $arr[] = array(
454 'll_from' => $this->mId,
455 'll_lang' => $lang,
456 'll_title' => $title
457 );
458 }
459
460 return $arr;
461 }
462
463 /**
464 * Get an array of page property insertions
465 * @param $existing array
466 * @return array
467 */
468 function getPropertyInsertions( $existing = array() ) {
469 $diffs = array_diff_assoc( $this->mProperties, $existing );
470 $arr = array();
471 foreach ( $diffs as $name => $value ) {
472 $arr[] = array(
473 'pp_page' => $this->mId,
474 'pp_propname' => $name,
475 'pp_value' => $value,
476 );
477 }
478
479 return $arr;
480 }
481
482 /**
483 * Get an array of interwiki insertions for passing to the DB
484 * Skips the titles specified by the 2-D array $existing
485 * @param $existing array
486 * @return array
487 */
488 private function getInterwikiInsertions( $existing = array() ) {
489 $arr = array();
490 foreach ( $this->mInterwikis as $prefix => $dbkeys ) {
491 $diffs = isset( $existing[$prefix] ) ? array_diff_key( $dbkeys, $existing[$prefix] ) : $dbkeys;
492 foreach ( $diffs as $dbk => $id ) {
493 $arr[] = array(
494 'iwl_from' => $this->mId,
495 'iwl_prefix' => $prefix,
496 'iwl_title' => $dbk
497 );
498 }
499 }
500
501 return $arr;
502 }
503
504 /**
505 * Given an array of existing links, returns those links which are not in $this
506 * and thus should be deleted.
507 * @param $existing array
508 * @return array
509 */
510 private function getLinkDeletions( $existing ) {
511 $del = array();
512 foreach ( $existing as $ns => $dbkeys ) {
513 if ( isset( $this->mLinks[$ns] ) ) {
514 $del[$ns] = array_diff_key( $existing[$ns], $this->mLinks[$ns] );
515 } else {
516 $del[$ns] = $existing[$ns];
517 }
518 }
519
520 return $del;
521 }
522
523 /**
524 * Given an array of existing templates, returns those templates which are not in $this
525 * and thus should be deleted.
526 * @param $existing array
527 * @return array
528 */
529 private function getTemplateDeletions( $existing ) {
530 $del = array();
531 foreach ( $existing as $ns => $dbkeys ) {
532 if ( isset( $this->mTemplates[$ns] ) ) {
533 $del[$ns] = array_diff_key( $existing[$ns], $this->mTemplates[$ns] );
534 } else {
535 $del[$ns] = $existing[$ns];
536 }
537 }
538
539 return $del;
540 }
541
542 /**
543 * Given an array of existing images, returns those images which are not in $this
544 * and thus should be deleted.
545 * @param $existing array
546 * @return array
547 */
548 private function getImageDeletions( $existing ) {
549 return array_diff_key( $existing, $this->mImages );
550 }
551
552 /**
553 * Given an array of existing external links, returns those links which are not
554 * in $this and thus should be deleted.
555 * @param $existing array
556 * @return array
557 */
558 private function getExternalDeletions( $existing ) {
559 return array_diff_key( $existing, $this->mExternals );
560 }
561
562 /**
563 * Given an array of existing categories, returns those categories which are not in $this
564 * and thus should be deleted.
565 * @param $existing array
566 * @return array
567 */
568 private function getCategoryDeletions( $existing ) {
569 return array_diff_assoc( $existing, $this->mCategories );
570 }
571
572 /**
573 * Given an array of existing interlanguage links, returns those links which are not
574 * in $this and thus should be deleted.
575 * @param $existing array
576 * @return array
577 */
578 private function getInterlangDeletions( $existing ) {
579 return array_diff_assoc( $existing, $this->mInterlangs );
580 }
581
582 /**
583 * Get array of properties which should be deleted.
584 * @param $existing array
585 * @return array
586 */
587 function getPropertyDeletions( $existing ) {
588 return array_diff_assoc( $existing, $this->mProperties );
589 }
590
591 /**
592 * Given an array of existing interwiki links, returns those links which are not in $this
593 * and thus should be deleted.
594 * @param $existing array
595 * @return array
596 */
597 private function getInterwikiDeletions( $existing ) {
598 $del = array();
599 foreach ( $existing as $prefix => $dbkeys ) {
600 if ( isset( $this->mInterwikis[$prefix] ) ) {
601 $del[$prefix] = array_diff_key( $existing[$prefix], $this->mInterwikis[$prefix] );
602 } else {
603 $del[$prefix] = $existing[$prefix];
604 }
605 }
606
607 return $del;
608 }
609
610 /**
611 * Get an array of existing links, as a 2-D array
612 *
613 * @return array
614 */
615 private function getExistingLinks() {
616 $res = $this->mDb->select( 'pagelinks', array( 'pl_namespace', 'pl_title' ),
617 array( 'pl_from' => $this->mId ), __METHOD__, $this->mOptions );
618 $arr = array();
619 foreach ( $res as $row ) {
620 if ( !isset( $arr[$row->pl_namespace] ) ) {
621 $arr[$row->pl_namespace] = array();
622 }
623 $arr[$row->pl_namespace][$row->pl_title] = 1;
624 }
625
626 return $arr;
627 }
628
629 /**
630 * Get an array of existing templates, as a 2-D array
631 *
632 * @return array
633 */
634 private function getExistingTemplates() {
635 $res = $this->mDb->select( 'templatelinks', array( 'tl_namespace', 'tl_title' ),
636 array( 'tl_from' => $this->mId ), __METHOD__, $this->mOptions );
637 $arr = array();
638 foreach ( $res as $row ) {
639 if ( !isset( $arr[$row->tl_namespace] ) ) {
640 $arr[$row->tl_namespace] = array();
641 }
642 $arr[$row->tl_namespace][$row->tl_title] = 1;
643 }
644
645 return $arr;
646 }
647
648 /**
649 * Get an array of existing images, image names in the keys
650 *
651 * @return array
652 */
653 private function getExistingImages() {
654 $res = $this->mDb->select( 'imagelinks', array( 'il_to' ),
655 array( 'il_from' => $this->mId ), __METHOD__, $this->mOptions );
656 $arr = array();
657 foreach ( $res as $row ) {
658 $arr[$row->il_to] = 1;
659 }
660
661 return $arr;
662 }
663
664 /**
665 * Get an array of existing external links, URLs in the keys
666 *
667 * @return array
668 */
669 private function getExistingExternals() {
670 $res = $this->mDb->select( 'externallinks', array( 'el_to' ),
671 array( 'el_from' => $this->mId ), __METHOD__, $this->mOptions );
672 $arr = array();
673 foreach ( $res as $row ) {
674 $arr[$row->el_to] = 1;
675 }
676
677 return $arr;
678 }
679
680 /**
681 * Get an array of existing categories, with the name in the key and sort key in the value.
682 *
683 * @return array
684 */
685 private function getExistingCategories() {
686 $res = $this->mDb->select( 'categorylinks', array( 'cl_to', 'cl_sortkey_prefix' ),
687 array( 'cl_from' => $this->mId ), __METHOD__, $this->mOptions );
688 $arr = array();
689 foreach ( $res as $row ) {
690 $arr[$row->cl_to] = $row->cl_sortkey_prefix;
691 }
692
693 return $arr;
694 }
695
696 /**
697 * Get an array of existing interlanguage links, with the language code in the key and the
698 * title in the value.
699 *
700 * @return array
701 */
702 private function getExistingInterlangs() {
703 $res = $this->mDb->select( 'langlinks', array( 'll_lang', 'll_title' ),
704 array( 'll_from' => $this->mId ), __METHOD__, $this->mOptions );
705 $arr = array();
706 foreach ( $res as $row ) {
707 $arr[$row->ll_lang] = $row->ll_title;
708 }
709
710 return $arr;
711 }
712
713 /**
714 * Get an array of existing inline interwiki links, as a 2-D array
715 * @return array (prefix => array(dbkey => 1))
716 */
717 protected function getExistingInterwikis() {
718 $res = $this->mDb->select( 'iwlinks', array( 'iwl_prefix', 'iwl_title' ),
719 array( 'iwl_from' => $this->mId ), __METHOD__, $this->mOptions );
720 $arr = array();
721 foreach ( $res as $row ) {
722 if ( !isset( $arr[$row->iwl_prefix] ) ) {
723 $arr[$row->iwl_prefix] = array();
724 }
725 $arr[$row->iwl_prefix][$row->iwl_title] = 1;
726 }
727
728 return $arr;
729 }
730
731 /**
732 * Get an array of existing categories, with the name in the key and sort key in the value.
733 *
734 * @return array
735 */
736 private function getExistingProperties() {
737 $res = $this->mDb->select( 'page_props', array( 'pp_propname', 'pp_value' ),
738 array( 'pp_page' => $this->mId ), __METHOD__, $this->mOptions );
739 $arr = array();
740 foreach ( $res as $row ) {
741 $arr[$row->pp_propname] = $row->pp_value;
742 }
743
744 return $arr;
745 }
746
747 /**
748 * Return the title object of the page being updated
749 * @return Title
750 */
751 public function getTitle() {
752 return $this->mTitle;
753 }
754
755 /**
756 * Returns parser output
757 * @since 1.19
758 * @return ParserOutput
759 */
760 public function getParserOutput() {
761 return $this->mParserOutput;
762 }
763
764 /**
765 * Return the list of images used as generated by the parser
766 * @return array
767 */
768 public function getImages() {
769 return $this->mImages;
770 }
771
772 /**
773 * Invalidate any necessary link lists related to page property changes
774 * @param $changed
775 */
776 private function invalidateProperties( $changed ) {
777 global $wgPagePropLinkInvalidations;
778
779 foreach ( $changed as $name => $value ) {
780 if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
781 $inv = $wgPagePropLinkInvalidations[$name];
782 if ( !is_array( $inv ) ) {
783 $inv = array( $inv );
784 }
785 foreach ( $inv as $table ) {
786 $update = new HTMLCacheUpdate( $this->mTitle, $table );
787 $update->doUpdate();
788 }
789 }
790 }
791 }
792
793 /**
794 * Fetch page links added by this LinksUpdate. Only available after the update is complete.
795 * @since 1.22
796 * @return null|array of Titles
797 */
798 public function getAddedLinks() {
799 if ( $this->linkInsertions === null ) {
800 return null;
801 }
802 $result = array();
803 foreach ( $this->linkInsertions as $insertion ) {
804 $result[] = Title::makeTitle( $insertion['pl_namespace'], $insertion['pl_title'] );
805 }
806
807 return $result;
808 }
809
810 /**
811 * Fetch page links removed by this LinksUpdate. Only available after the update is complete.
812 * @since 1.22
813 * @return null|array of Titles
814 */
815 public function getRemovedLinks() {
816 if ( $this->linkDeletions === null ) {
817 return null;
818 }
819 $result = array();
820 foreach ( $this->linkDeletions as $ns => $titles ) {
821 foreach ( $titles as $title => $unused ) {
822 $result[] = Title::makeTitle( $ns, $title );
823 }
824 }
825
826 return $result;
827 }
828 }
829
830 /**
831 * Update object handling the cleanup of links tables after a page was deleted.
832 **/
833 class LinksDeletionUpdate extends SqlDataUpdate {
834 protected $mPage; //!< WikiPage the wikipage that was deleted
835
836 /**
837 * Constructor
838 *
839 * @param $page WikiPage Page we are updating
840 * @throws MWException
841 */
842 function __construct( WikiPage $page ) {
843 parent::__construct( false ); // no implicit transaction
844
845 $this->mPage = $page;
846
847 if ( !$page->exists() ) {
848 throw new MWException( "Page ID not known, perhaps the page doesn't exist?" );
849 }
850 }
851
852 /**
853 * Do some database updates after deletion
854 */
855 public function doUpdate() {
856 $title = $this->mPage->getTitle();
857 $id = $this->mPage->getId();
858
859 # Delete restrictions for it
860 $this->mDb->delete( 'page_restrictions', array( 'pr_page' => $id ), __METHOD__ );
861
862 # Fix category table counts
863 $cats = array();
864 $res = $this->mDb->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
865
866 foreach ( $res as $row ) {
867 $cats[] = $row->cl_to;
868 }
869
870 $this->mPage->updateCategoryCounts( array(), $cats );
871
872 # If using cascading deletes, we can skip some explicit deletes
873 if ( !$this->mDb->cascadingDeletes() ) {
874 # Delete outgoing links
875 $this->mDb->delete( 'pagelinks', array( 'pl_from' => $id ), __METHOD__ );
876 $this->mDb->delete( 'imagelinks', array( 'il_from' => $id ), __METHOD__ );
877 $this->mDb->delete( 'categorylinks', array( 'cl_from' => $id ), __METHOD__ );
878 $this->mDb->delete( 'templatelinks', array( 'tl_from' => $id ), __METHOD__ );
879 $this->mDb->delete( 'externallinks', array( 'el_from' => $id ), __METHOD__ );
880 $this->mDb->delete( 'langlinks', array( 'll_from' => $id ), __METHOD__ );
881 $this->mDb->delete( 'iwlinks', array( 'iwl_from' => $id ), __METHOD__ );
882 $this->mDb->delete( 'redirect', array( 'rd_from' => $id ), __METHOD__ );
883 $this->mDb->delete( 'page_props', array( 'pp_page' => $id ), __METHOD__ );
884 }
885
886 # If using cleanup triggers, we can skip some manual deletes
887 if ( !$this->mDb->cleanupTriggers() ) {
888 # Clean up recentchanges entries...
889 $this->mDb->delete( 'recentchanges',
890 array( 'rc_type != ' . RC_LOG,
891 'rc_namespace' => $title->getNamespace(),
892 'rc_title' => $title->getDBkey() ),
893 __METHOD__ );
894 $this->mDb->delete( 'recentchanges',
895 array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
896 __METHOD__ );
897 }
898 }
899
900 /**
901 * Update all the appropriate counts in the category table.
902 * @param array $added associative array of category name => sort key
903 * @param array $deleted associative array of category name => sort key
904 */
905 function updateCategoryCounts( $added, $deleted ) {
906 $a = WikiPage::factory( $this->mTitle );
907 $a->updateCategoryCounts(
908 array_keys( $added ), array_keys( $deleted )
909 );
910 }
911 }