Merge "Add .gitignore to the /skins directory"
[lhc/web/wiklou.git] / includes / job / jobs / RefreshLinksJob.php
1 <?php
2 /**
3 * Job to update link tables for pages
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 * @ingroup JobQueue
22 */
23
24 /**
25 * Job to update link tables for pages
26 *
27 * This job comes in a few variants:
28 * - a) Recursive jobs to update links for backlink pages for a given title
29 * - b) Jobs to update links for a set of titles (the job title is ignored)
30 * - c) Jobs to update links for a single title (the job title)
31 *
32 * @ingroup JobQueue
33 */
34 class RefreshLinksJob extends Job {
35 const VERSION = 1;
36
37 function __construct( $title, $params = '', $id = 0 ) {
38 parent::__construct( 'refreshLinks', $title, $params, $id );
39 $this->params['version'] = self::VERSION;
40 // Base backlink update jobs and per-title update jobs can be de-duplicated.
41 // If template A changes twice before any jobs run, a clean queue will have:
42 // (A base, A base)
43 // The second job is ignored by the queue on insertion.
44 // Suppose, many pages use template A, and that template itself uses template B.
45 // An edit to both will first create two base jobs. A clean FIFO queue will have:
46 // (A base, B base)
47 // When these jobs run, the queue will have per-title and remnant partition jobs:
48 // (titleX,titleY,titleZ,...,A remnant,titleM,titleN,titleO,...,B remnant)
49 // Some these jobs will be the same, and will automatically be ignored by
50 // the queue upon insertion. Some title jobs will run before the duplicate is
51 // inserted, so the work will still be done twice in those cases. More titles
52 // can be de-duplicated as the remnant jobs continue to be broken down. This
53 // works best when $wgUpdateRowsPerJob, and either the pages have few backlinks
54 // and/or the backlink sets for pages A and B are almost identical.
55 $this->removeDuplicates = !isset( $params['range'] )
56 && ( !isset( $params['pages'] ) || count( $params['pages'] ) == 1 );
57 }
58
59 function run() {
60 global $wgUpdateRowsPerJob;
61
62 if ( is_null( $this->title ) ) {
63 $this->setLastError( "Invalid page title" );
64 return false;
65 }
66
67 // Job to update all (or a range of) backlink pages for a page
68 if ( isset( $this->params['recursive'] ) ) {
69 // Carry over information for de-duplication
70 $extraParams = $this->getRootJobParams();
71 // Avoid slave lag when fetching templates.
72 // When the outermost job is run, we know that the caller that enqueued it must have
73 // committed the relevant changes to the DB by now. At that point, record the master
74 // position and pass it along as the job recursively breaks into smaller range jobs.
75 // Hopefully, when leaf jobs are popped, the slaves will have reached that position.
76 if ( isset( $this->params['masterPos'] ) ) {
77 $extraParams['masterPos'] = $this->params['masterPos'];
78 } elseif ( wfGetLB()->getServerCount() > 1 ) {
79 $extraParams['masterPos'] = wfGetLB()->getMasterPos();
80 } else {
81 $extraParams['masterPos'] = false;
82 }
83 // Convert this into no more than $wgUpdateRowsPerJob RefreshLinks per-title
84 // jobs and possibly a recursive RefreshLinks job for the rest of the backlinks
85 $jobs = BacklinkJobUtils::partitionBacklinkJob(
86 $this,
87 $wgUpdateRowsPerJob,
88 1, // job-per-title
89 array( 'params' => $extraParams )
90 );
91 JobQueueGroup::singleton()->push( $jobs );
92 // Job to update link tables for for a set of titles
93 } elseif ( isset( $this->params['pages'] ) ) {
94 foreach ( $this->params['pages'] as $pageId => $nsAndKey ) {
95 list( $ns, $dbKey ) = $nsAndKey;
96 $this->runForTitle( Title::makeTitleSafe( $ns, $dbKey ) );
97 }
98 // Job to update link tables for a given title
99 } else {
100 $this->runForTitle( $this->mTitle );
101 }
102
103 return true;
104 }
105
106 protected function runForTitle( Title $title = null ) {
107 $linkCache = LinkCache::singleton();
108 $linkCache->clear();
109
110 if ( is_null( $title ) ) {
111 $this->setLastError( "refreshLinks: Invalid title" );
112 return false;
113 }
114
115 // Wait for the DB of the current/next slave DB handle to catch up to the master.
116 // This way, we get the correct page_latest for templates or files that just changed
117 // milliseconds ago, having triggered this job to begin with.
118 if ( isset( $this->params['masterPos'] ) && $this->params['masterPos'] !== false ) {
119 wfGetLB()->waitFor( $this->params['masterPos'] );
120 }
121
122 $revision = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
123 if ( !$revision ) {
124 $this->setLastError( "refreshLinks: Article not found {$title->getPrefixedDBkey()}" );
125 return false; // XXX: what if it was just deleted?
126 }
127
128 $content = $revision->getContent( Revision::RAW );
129 if ( !$content ) {
130 // If there is no content, pretend the content is empty
131 $content = $revision->getContentHandler()->makeEmptyContent();
132 }
133
134 // Revision ID must be passed to the parser output to get revision variables correct
135 $parserOutput = $content->getParserOutput( $title, $revision->getId(), null, false );
136
137 $updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput );
138 DataUpdate::runUpdates( $updates );
139
140 InfoAction::invalidateCache( $title );
141
142 return true;
143 }
144
145 public function getDeduplicationInfo() {
146 $info = parent::getDeduplicationInfo();
147 if ( is_array( $info['params'] ) ) {
148 // Don't let highly unique "masterPos" values ruin duplicate detection
149 unset( $info['params']['masterPos'] );
150 // For per-pages jobs, the job title is that of the template that changed
151 // (or similar), so remove that since it ruins duplicate detection
152 if ( isset( $info['pages'] ) ) {
153 unset( $info['namespace'] );
154 unset( $info['title'] );
155 }
156 }
157
158 return $info;
159 }
160 }