]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/LinksUpdate.php
MediaWiki 1.15.0
[autoinstallsdev/mediawiki.git] / includes / LinksUpdate.php
1 <?php
2 /**
3  * See docs/deferred.txt
4  *
5  * @todo document (e.g. one-sentence top-level class description).
6  */
7 class LinksUpdate {
8
9         /**@{{
10          * @private
11          */
12         var $mId,            //!< Page ID of the article linked from
13                 $mTitle,         //!< Title object of the article linked from
14                 $mLinks,         //!< Map of title strings to IDs for the links in the document
15                 $mImages,        //!< DB keys of the images used, in the array key only
16                 $mTemplates,     //!< Map of title strings to IDs for the template references, including broken ones
17                 $mExternals,     //!< URLs of external links, array key only
18                 $mCategories,    //!< Map of category names to sort keys
19                 $mInterlangs,    //!< Map of language codes to titles
20                 $mProperties,    //!< Map of arbitrary name to value
21                 $mDb,            //!< Database connection reference
22                 $mOptions,       //!< SELECT options to be used (array)
23                 $mRecursive;     //!< Whether to queue jobs for recursive updates
24         /**@}}*/
25
26         /**
27          * Constructor
28          *
29          * @param Title $title Title of the page we're updating
30          * @param ParserOutput $parserOutput Output from a full parse of this page
31          * @param bool $recursive Queue jobs for recursive updates?
32          */
33         function LinksUpdate( $title, $parserOutput, $recursive = true ) {
34                 global $wgAntiLockFlags;
35
36                 if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) {
37                         $this->mOptions = array();
38                 } else {
39                         $this->mOptions = array( 'FOR UPDATE' );
40                 }
41                 $this->mDb = wfGetDB( DB_MASTER );
42
43                 if ( !is_object( $title ) ) {
44                         throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " .
45                                 "Please see Article::editUpdates() for an invocation example.\n" );
46                 }
47                 $this->mTitle = $title;
48                 $this->mId = $title->getArticleID();
49
50                 $this->mParserOutput = $parserOutput;
51                 $this->mLinks = $parserOutput->getLinks();
52                 $this->mImages = $parserOutput->getImages();
53                 $this->mTemplates = $parserOutput->getTemplates();
54                 $this->mExternals = $parserOutput->getExternalLinks();
55                 $this->mCategories = $parserOutput->getCategories();
56                 $this->mProperties = $parserOutput->getProperties();
57
58                 # Convert the format of the interlanguage links
59                 # I didn't want to change it in the ParserOutput, because that array is passed all
60                 # the way back to the skin, so either a skin API break would be required, or an
61                 # inefficient back-conversion.
62                 $ill = $parserOutput->getLanguageLinks();
63                 $this->mInterlangs = array();
64                 foreach ( $ill as $link ) {
65                         list( $key, $title ) = explode( ':', $link, 2 );
66                         $this->mInterlangs[$key] = $title;
67                 }
68
69                 $this->mRecursive = $recursive;
70                 $this->mTouchTmplLinks = false;
71
72                 wfRunHooks( 'LinksUpdateConstructed', array( &$this ) );
73         }
74
75         /**
76          * Update link tables with outgoing links from an updated article
77          */
78         public function doUpdate() {
79                 global $wgUseDumbLinkUpdate;
80
81                 wfRunHooks( 'LinksUpdate', array( &$this ) );
82                 if ( $wgUseDumbLinkUpdate ) {
83                         $this->doDumbUpdate();
84                 } else {
85                         $this->doIncrementalUpdate();
86                 }
87                 wfRunHooks( 'LinksUpdateComplete', array( &$this ) );
88         }
89
90         protected function doIncrementalUpdate() {
91                 wfProfileIn( __METHOD__ );
92
93                 # Page links
94                 $existing = $this->getExistingLinks();
95                 $this->incrTableUpdate( 'pagelinks', 'pl', $this->getLinkDeletions( $existing ),
96                         $this->getLinkInsertions( $existing ) );
97
98                 # Image links
99                 $existing = $this->getExistingImages();
100
101                 $imageDeletes = $this->getImageDeletions( $existing );
102                 $this->incrTableUpdate( 'imagelinks', 'il', $imageDeletes, $this->getImageInsertions( $existing ) );
103
104                 # Invalidate all image description pages which had links added or removed
105                 $imageUpdates = $imageDeletes + array_diff_key( $this->mImages, $existing );
106                 $this->invalidateImageDescriptions( $imageUpdates );
107
108                 # External links
109                 $existing = $this->getExistingExternals();
110                 $this->incrTableUpdate( 'externallinks', 'el', $this->getExternalDeletions( $existing ),
111                 $this->getExternalInsertions( $existing ) );
112
113                 # Language links
114                 $existing = $this->getExistingInterlangs();
115                 $this->incrTableUpdate( 'langlinks', 'll', $this->getInterlangDeletions( $existing ),
116                         $this->getInterlangInsertions( $existing ) );
117
118                 # Template links
119                 $existing = $this->getExistingTemplates();
120                 $this->incrTableUpdate( 'templatelinks', 'tl', $this->getTemplateDeletions( $existing ),
121                         $this->getTemplateInsertions( $existing ) );
122
123                 # Category links
124                 $existing = $this->getExistingCategories();
125
126                 $categoryDeletes = $this->getCategoryDeletions( $existing );
127
128                 $this->incrTableUpdate( 'categorylinks', 'cl', $categoryDeletes, $this->getCategoryInsertions( $existing ) );
129
130                 # Invalidate all categories which were added, deleted or changed (set symmetric difference)
131                 $categoryInserts = array_diff_assoc( $this->mCategories, $existing );
132                 $categoryUpdates = $categoryInserts + $categoryDeletes;
133                 $this->invalidateCategories( $categoryUpdates );
134                 $this->updateCategoryCounts( $categoryInserts, $categoryDeletes );
135
136                 # Page properties
137                 $existing = $this->getExistingProperties();
138
139                 $propertiesDeletes = $this->getPropertyDeletions( $existing );
140
141                 $this->incrTableUpdate( 'page_props', 'pp', $propertiesDeletes, $this->getPropertyInsertions( $existing ) );
142
143                 # Invalidate the necessary pages
144                 $changed = $propertiesDeletes + array_diff_assoc( $this->mProperties, $existing );
145                 $this->invalidateProperties( $changed );
146
147                 # Refresh links of all pages including this page
148                 # This will be in a separate transaction
149                 if ( $this->mRecursive ) {
150                         $this->queueRecursiveJobs();
151                 }
152
153                 wfProfileOut( __METHOD__ );
154         }
155
156         /**
157          * Link update which clears the previous entries and inserts new ones
158          * May be slower or faster depending on level of lock contention and write speed of DB
159          * Also useful where link table corruption needs to be repaired, e.g. in refreshLinks.php
160          */
161         protected function doDumbUpdate() {
162                 wfProfileIn( __METHOD__ );
163
164                 # Refresh category pages and image description pages
165                 $existing = $this->getExistingCategories();
166                 $categoryInserts = array_diff_assoc( $this->mCategories, $existing );
167                 $categoryDeletes = array_diff_assoc( $existing, $this->mCategories );
168                 $categoryUpdates = $categoryInserts + $categoryDeletes;
169                 $existing = $this->getExistingImages();
170                 $imageUpdates = array_diff_key( $existing, $this->mImages ) + array_diff_key( $this->mImages, $existing );
171
172                 $this->dumbTableUpdate( 'pagelinks',     $this->getLinkInsertions(),     'pl_from' );
173                 $this->dumbTableUpdate( 'imagelinks',    $this->getImageInsertions(),    'il_from' );
174                 $this->dumbTableUpdate( 'categorylinks', $this->getCategoryInsertions(), 'cl_from' );
175                 $this->dumbTableUpdate( 'templatelinks', $this->getTemplateInsertions(), 'tl_from' );
176                 $this->dumbTableUpdate( 'externallinks', $this->getExternalInsertions(), 'el_from' );
177                 $this->dumbTableUpdate( 'langlinks',     $this->getInterlangInsertions(),'ll_from' );
178                 $this->dumbTableUpdate( 'page_props',    $this->getPropertyInsertions(), 'pp_page' );
179
180                 # Update the cache of all the category pages and image description
181                 # pages which were changed, and fix the category table count
182                 $this->invalidateCategories( $categoryUpdates );
183                 $this->updateCategoryCounts( $categoryInserts, $categoryDeletes );
184                 $this->invalidateImageDescriptions( $imageUpdates );
185
186                 # Refresh links of all pages including this page
187                 # This will be in a separate transaction
188                 if ( $this->mRecursive ) {
189                         $this->queueRecursiveJobs();
190                 }
191
192                 wfProfileOut( __METHOD__ );
193         }
194
195         function queueRecursiveJobs() {
196                 global $wgUpdateRowsPerJob;
197                 wfProfileIn( __METHOD__ );
198
199                 $cache = $this->mTitle->getBacklinkCache();
200                 $batches = $cache->partition( 'templatelinks', $wgUpdateRowsPerJob );
201                 if ( !$batches ) {
202                         wfProfileOut( __METHOD__ );
203                         return;
204                 }
205                 $jobs = array();
206                 foreach ( $batches as $batch ) {
207                         list( $start, $end ) = $batch;
208                         $params = array(
209                                 'start' => $start,
210                                 'end' => $end,
211                         );
212                         $jobs[] = new RefreshLinksJob2( $this->mTitle, $params );
213                 }
214                 Job::batchInsert( $jobs );
215
216                 wfProfileOut( __METHOD__ );
217         }
218
219         /**
220          * Invalidate the cache of a list of pages from a single namespace
221          *
222          * @param integer $namespace
223          * @param array $dbkeys
224          */
225         function invalidatePages( $namespace, $dbkeys ) {
226                 if ( !count( $dbkeys ) ) {
227                         return;
228                 }
229
230                 /**
231                  * Determine which pages need to be updated
232                  * This is necessary to prevent the job queue from smashing the DB with
233                  * large numbers of concurrent invalidations of the same page
234                  */
235                 $now = $this->mDb->timestamp();
236                 $ids = array();
237                 $res = $this->mDb->select( 'page', array( 'page_id' ),
238                         array(
239                                 'page_namespace' => $namespace,
240                                 'page_title IN (' . $this->mDb->makeList( $dbkeys ) . ')',
241                                 'page_touched < ' . $this->mDb->addQuotes( $now )
242                         ), __METHOD__
243                 );
244                 while ( $row = $this->mDb->fetchObject( $res ) ) {
245                         $ids[] = $row->page_id;
246                 }
247                 if ( !count( $ids ) ) {
248                         return;
249                 }
250
251                 /**
252                  * Do the update
253                  * We still need the page_touched condition, in case the row has changed since
254                  * the non-locking select above.
255                  */
256                 $this->mDb->update( 'page', array( 'page_touched' => $now ),
257                         array(
258                                 'page_id IN (' . $this->mDb->makeList( $ids ) . ')',
259                                 'page_touched < ' . $this->mDb->addQuotes( $now )
260                         ), __METHOD__
261                 );
262         }
263
264         function invalidateCategories( $cats ) {
265                 $this->invalidatePages( NS_CATEGORY, array_keys( $cats ) );
266         }
267
268         /**
269          * Update all the appropriate counts in the category table.
270          * @param $added associative array of category name => sort key
271          * @param $deleted associative array of category name => sort key
272          */
273         function updateCategoryCounts( $added, $deleted ) {
274                 $a = new Article($this->mTitle);
275                 $a->updateCategoryCounts(
276                         array_keys( $added ), array_keys( $deleted )
277                 );
278         }
279
280         function invalidateImageDescriptions( $images ) {
281                 $this->invalidatePages( NS_FILE, array_keys( $images ) );
282         }
283
284         function dumbTableUpdate( $table, $insertions, $fromField ) {
285                 $this->mDb->delete( $table, array( $fromField => $this->mId ), __METHOD__ );
286                 if ( count( $insertions ) ) {
287                         # The link array was constructed without FOR UPDATE, so there may
288                         # be collisions.  This may cause minor link table inconsistencies,
289                         # which is better than crippling the site with lock contention.
290                         $this->mDb->insert( $table, $insertions, __METHOD__, array( 'IGNORE' ) );
291                 }
292         }
293
294         /**
295          * Make a WHERE clause from a 2-d NS/dbkey array
296          *
297          * @param array $arr 2-d array indexed by namespace and DB key
298          * @param string $prefix Field name prefix, without the underscore
299          */
300         function makeWhereFrom2d( &$arr, $prefix ) {
301                 $lb = new LinkBatch;
302                 $lb->setArray( $arr );
303                 return $lb->constructSet( $prefix, $this->mDb );
304         }
305
306         /**
307          * Update a table by doing a delete query then an insert query
308          * @private
309          */
310         function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
311                 if ( $table == 'page_props' ) {
312                         $fromField = 'pp_page';
313                 } else {
314                         $fromField = "{$prefix}_from";
315                 }
316                 $where = array( $fromField => $this->mId );
317                 if ( $table == 'pagelinks' || $table == 'templatelinks' ) {
318                         $clause = $this->makeWhereFrom2d( $deletions, $prefix );
319                         if ( $clause ) {
320                                 $where[] = $clause;
321                         } else {
322                                 $where = false;
323                         }
324                 } else {
325                         if ( $table == 'langlinks' ) {
326                                 $toField = 'll_lang';
327                         } elseif ( $table == 'page_props' ) {
328                                 $toField = 'pp_propname';
329                         } else {
330                                 $toField = $prefix . '_to';
331                         }
332                         if ( count( $deletions ) ) {
333                                 $where[] = "$toField IN (" . $this->mDb->makeList( array_keys( $deletions ) ) . ')';
334                         } else {
335                                 $where = false;
336                         }
337                 }
338                 if ( $where ) {
339                         $this->mDb->delete( $table, $where, __METHOD__ );
340                 }
341                 if ( count( $insertions ) ) {
342                         $this->mDb->insert( $table, $insertions, __METHOD__, 'IGNORE' );
343                 }
344         }
345
346
347         /**
348          * Get an array of pagelinks insertions for passing to the DB
349          * Skips the titles specified by the 2-D array $existing
350          * @private
351          */
352         function getLinkInsertions( $existing = array() ) {
353                 $arr = array();
354                 foreach( $this->mLinks as $ns => $dbkeys ) {
355                         # array_diff_key() was introduced in PHP 5.1, there is a compatibility function
356                         # in GlobalFunctions.php
357                         $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
358                         foreach ( $diffs as $dbk => $id ) {
359                                 $arr[] = array(
360                                         'pl_from'      => $this->mId,
361                                         'pl_namespace' => $ns,
362                                         'pl_title'     => $dbk
363                                 );
364                         }
365                 }
366                 return $arr;
367         }
368
369         /**
370          * Get an array of template insertions. Like getLinkInsertions()
371          * @private
372          */
373         function getTemplateInsertions( $existing = array() ) {
374                 $arr = array();
375                 foreach( $this->mTemplates as $ns => $dbkeys ) {
376                         $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
377                         foreach ( $diffs as $dbk => $id ) {
378                                 $arr[] = array(
379                                         'tl_from'      => $this->mId,
380                                         'tl_namespace' => $ns,
381                                         'tl_title'     => $dbk
382                                 );
383                         }
384                 }
385                 return $arr;
386         }
387
388         /**
389          * Get an array of image insertions
390          * Skips the names specified in $existing
391          * @private
392          */
393         function getImageInsertions( $existing = array() ) {
394                 $arr = array();
395                 $diffs = array_diff_key( $this->mImages, $existing );
396                 foreach( $diffs as $iname => $dummy ) {
397                         $arr[] = array(
398                                 'il_from' => $this->mId,
399                                 'il_to'   => $iname
400                         );
401                 }
402                 return $arr;
403         }
404
405         /**
406          * Get an array of externallinks insertions. Skips the names specified in $existing
407          * @private
408          */
409         function getExternalInsertions( $existing = array() ) {
410                 $arr = array();
411                 $diffs = array_diff_key( $this->mExternals, $existing );
412                 foreach( $diffs as $url => $dummy ) {
413                         $arr[] = array(
414                                 'el_from'   => $this->mId,
415                                 'el_to'     => $url,
416                                 'el_index'  => wfMakeUrlIndex( $url ),
417                         );
418                 }
419                 return $arr;
420         }
421
422         /**
423          * Get an array of category insertions
424          * @param array $existing Array mapping existing category names to sort keys. If both
425          * match a link in $this, the link will be omitted from the output
426          * @private
427          */
428         function getCategoryInsertions( $existing = array() ) {
429                 global $wgContLang;
430                 $diffs = array_diff_assoc( $this->mCategories, $existing );
431                 $arr = array();
432                 foreach ( $diffs as $name => $sortkey ) {
433                         $nt = Title::makeTitleSafe( NS_CATEGORY, $name );
434                         $wgContLang->findVariantLink( $name, $nt, true );
435                         $arr[] = array(
436                                 'cl_from'    => $this->mId,
437                                 'cl_to'      => $name,
438                                 'cl_sortkey' => $sortkey,
439                                 'cl_timestamp' => $this->mDb->timestamp()
440                         );
441                 }
442                 return $arr;
443         }
444
445         /**
446          * Get an array of interlanguage link insertions
447          * @param array $existing Array mapping existing language codes to titles
448          * @private
449          */
450         function getInterlangInsertions( $existing = array() ) {
451             $diffs = array_diff_assoc( $this->mInterlangs, $existing );
452             $arr = array();
453             foreach( $diffs as $lang => $title ) {
454                 $arr[] = array(
455                     'll_from'  => $this->mId,
456                     'll_lang'  => $lang,
457                     'll_title' => $title
458                 );
459             }
460             return $arr;
461         }
462
463         /**
464          * Get an array of page property insertions
465          */
466         function getPropertyInsertions( $existing = array() ) {
467                 $diffs = array_diff_assoc( $this->mProperties, $existing );
468                 $arr = array();
469                 foreach ( $diffs as $name => $value ) {
470                         $arr[] = array(
471                                 'pp_page'      => $this->mId,
472                                 'pp_propname'  => $name,
473                                 'pp_value'     => $value,
474                         );
475                 }
476                 return $arr;
477         }
478
479
480         /**
481          * Given an array of existing links, returns those links which are not in $this
482          * and thus should be deleted.
483          * @private
484          */
485         function getLinkDeletions( $existing ) {
486                 $del = array();
487                 foreach ( $existing as $ns => $dbkeys ) {
488                         if ( isset( $this->mLinks[$ns] ) ) {
489                                 $del[$ns] = array_diff_key( $existing[$ns], $this->mLinks[$ns] );
490                         } else {
491                                 $del[$ns] = $existing[$ns];
492                         }
493                 }
494                 return $del;
495         }
496
497         /**
498          * Given an array of existing templates, returns those templates which are not in $this
499          * and thus should be deleted.
500          * @private
501          */
502         function getTemplateDeletions( $existing ) {
503                 $del = array();
504                 foreach ( $existing as $ns => $dbkeys ) {
505                         if ( isset( $this->mTemplates[$ns] ) ) {
506                                 $del[$ns] = array_diff_key( $existing[$ns], $this->mTemplates[$ns] );
507                         } else {
508                                 $del[$ns] = $existing[$ns];
509                         }
510                 }
511                 return $del;
512         }
513
514         /**
515          * Given an array of existing images, returns those images which are not in $this
516          * and thus should be deleted.
517          * @private
518          */
519         function getImageDeletions( $existing ) {
520                 return array_diff_key( $existing, $this->mImages );
521         }
522
523         /**
524          * Given an array of existing external links, returns those links which are not
525          * in $this and thus should be deleted.
526          * @private
527          */
528         function getExternalDeletions( $existing ) {
529                 return array_diff_key( $existing, $this->mExternals );
530         }
531
532         /**
533          * Given an array of existing categories, returns those categories which are not in $this
534          * and thus should be deleted.
535          * @private
536          */
537         function getCategoryDeletions( $existing ) {
538                 return array_diff_assoc( $existing, $this->mCategories );
539         }
540
541         /**
542          * Given an array of existing interlanguage links, returns those links which are not
543          * in $this and thus should be deleted.
544          * @private
545          */
546         function getInterlangDeletions( $existing ) {
547             return array_diff_assoc( $existing, $this->mInterlangs );
548         }
549
550         /**
551          * Get array of properties which should be deleted.
552          * @private
553          */
554         function getPropertyDeletions( $existing ) {
555                 return array_diff_assoc( $existing, $this->mProperties );
556         }
557
558         /**
559          * Get an array of existing links, as a 2-D array
560          * @private
561          */
562         function getExistingLinks() {
563                 $res = $this->mDb->select( 'pagelinks', array( 'pl_namespace', 'pl_title' ),
564                         array( 'pl_from' => $this->mId ), __METHOD__, $this->mOptions );
565                 $arr = array();
566                 while ( $row = $this->mDb->fetchObject( $res ) ) {
567                         if ( !isset( $arr[$row->pl_namespace] ) ) {
568                                 $arr[$row->pl_namespace] = array();
569                         }
570                         $arr[$row->pl_namespace][$row->pl_title] = 1;
571                 }
572                 $this->mDb->freeResult( $res );
573                 return $arr;
574         }
575
576         /**
577          * Get an array of existing templates, as a 2-D array
578          * @private
579          */
580         function getExistingTemplates() {
581                 $res = $this->mDb->select( 'templatelinks', array( 'tl_namespace', 'tl_title' ),
582                         array( 'tl_from' => $this->mId ), __METHOD__, $this->mOptions );
583                 $arr = array();
584                 while ( $row = $this->mDb->fetchObject( $res ) ) {
585                         if ( !isset( $arr[$row->tl_namespace] ) ) {
586                                 $arr[$row->tl_namespace] = array();
587                         }
588                         $arr[$row->tl_namespace][$row->tl_title] = 1;
589                 }
590                 $this->mDb->freeResult( $res );
591                 return $arr;
592         }
593
594         /**
595          * Get an array of existing images, image names in the keys
596          * @private
597          */
598         function getExistingImages() {
599                 $res = $this->mDb->select( 'imagelinks', array( 'il_to' ),
600                         array( 'il_from' => $this->mId ), __METHOD__, $this->mOptions );
601                 $arr = array();
602                 while ( $row = $this->mDb->fetchObject( $res ) ) {
603                         $arr[$row->il_to] = 1;
604                 }
605                 $this->mDb->freeResult( $res );
606                 return $arr;
607         }
608
609         /**
610          * Get an array of existing external links, URLs in the keys
611          * @private
612          */
613         function getExistingExternals() {
614                 $res = $this->mDb->select( 'externallinks', array( 'el_to' ),
615                         array( 'el_from' => $this->mId ), __METHOD__, $this->mOptions );
616                 $arr = array();
617                 while ( $row = $this->mDb->fetchObject( $res ) ) {
618                         $arr[$row->el_to] = 1;
619                 }
620                 $this->mDb->freeResult( $res );
621                 return $arr;
622         }
623
624         /**
625          * Get an array of existing categories, with the name in the key and sort key in the value.
626          * @private
627          */
628         function getExistingCategories() {
629                 $res = $this->mDb->select( 'categorylinks', array( 'cl_to', 'cl_sortkey' ),
630                         array( 'cl_from' => $this->mId ), __METHOD__, $this->mOptions );
631                 $arr = array();
632                 while ( $row = $this->mDb->fetchObject( $res ) ) {
633                         $arr[$row->cl_to] = $row->cl_sortkey;
634                 }
635                 $this->mDb->freeResult( $res );
636                 return $arr;
637         }
638
639         /**
640          * Get an array of existing interlanguage links, with the language code in the key and the
641          * title in the value.
642          * @private
643          */
644         function getExistingInterlangs() {
645                 $res = $this->mDb->select( 'langlinks', array( 'll_lang', 'll_title' ),
646                         array( 'll_from' => $this->mId ), __METHOD__, $this->mOptions );
647                 $arr = array();
648                 while ( $row = $this->mDb->fetchObject( $res ) ) {
649                         $arr[$row->ll_lang] = $row->ll_title;
650                 }
651                 return $arr;
652         }
653
654         /**
655          * Get an array of existing categories, with the name in the key and sort key in the value.
656          * @private
657          */
658         function getExistingProperties() {
659                 $res = $this->mDb->select( 'page_props', array( 'pp_propname', 'pp_value' ),
660                         array( 'pp_page' => $this->mId ), __METHOD__, $this->mOptions );
661                 $arr = array();
662                 while ( $row = $this->mDb->fetchObject( $res ) ) {
663                         $arr[$row->pp_propname] = $row->pp_value;
664                 }
665                 $this->mDb->freeResult( $res );
666                 return $arr;
667         }
668
669
670         /**
671          * Return the title object of the page being updated
672          */
673         function getTitle() {
674                 return $this->mTitle;
675         }
676
677         /**
678          * Invalidate any necessary link lists related to page property changes
679          */
680         function invalidateProperties( $changed ) {
681                 global $wgPagePropLinkInvalidations;
682
683                 foreach ( $changed as $name => $value ) {
684                         if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
685                                 $inv = $wgPagePropLinkInvalidations[$name];
686                                 if ( !is_array( $inv ) ) {
687                                         $inv = array( $inv );
688                                 }
689                                 foreach ( $inv as $table ) {
690                                         $update = new HTMLCacheUpdate( $this->mTitle, $table );
691                                         $update->doUpdate();
692                                 }
693                         }
694                 }
695         }
696 }