MediaWiki master
DoubleRedirectJob.php
Go to the documentation of this file.
1<?php
31
37class DoubleRedirectJob extends Job {
43 public const MAX_DR_JOBS_COUNTER = 10000;
44
48 private $redirTitle;
49
51 private static $user;
52
54 private $revisionLookup;
55
57 private $magicWordFactory;
58
60 private $wikiPageFactory;
61
74 public function __construct(
75 PageReference $page,
76 array $params,
77 RevisionLookup $revisionLookup,
78 MagicWordFactory $magicWordFactory,
79 WikiPageFactory $wikiPageFactory
80 ) {
81 parent::__construct( 'fixDoubleRedirect', $page, $params );
82 $this->redirTitle = Title::newFromText( $params['redirTitle'] );
83 $this->revisionLookup = $revisionLookup;
84 $this->magicWordFactory = $magicWordFactory;
85 $this->wikiPageFactory = $wikiPageFactory;
86 }
87
95 public static function fixRedirects( $reason, $redirTitle ) {
96 # Need to use the primary DB to get the redirect table updated in the same transaction
97 $services = MediaWikiServices::getInstance();
98 $dbw = $services->getConnectionProvider()->getPrimaryDatabase();
99 $res = $dbw->newSelectQueryBuilder()
100 ->select( [ 'page_namespace', 'page_title' ] )
101 ->from( 'redirect' )
102 ->join( 'page', null, 'page_id = rd_from' )
103 ->where( [ 'rd_namespace' => $redirTitle->getNamespace(), 'rd_title' => $redirTitle->getDBkey() ] )
104 ->andWhere( [ 'rd_interwiki' => '' ] )
105 ->caller( __METHOD__ )->fetchResultSet();
106 if ( !$res->numRows() ) {
107 return;
108 }
109 $jobs = [];
110 $jobQueueGroup = $services->getJobQueueGroup();
111 foreach ( $res as $row ) {
112 $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
113 if ( !$title || !$title->canExist() ) {
114 continue;
115 }
116
117 $jobs[] = new self(
118 $title,
119 [
120 'reason' => $reason,
121 'redirTitle' => $services->getTitleFormatter()
122 ->getPrefixedDBkey( $redirTitle )
123 ],
124 $services->getRevisionLookup(),
125 $services->getMagicWordFactory(),
126 $services->getWikiPageFactory()
127 );
128 # Avoid excessive memory usage
129 if ( count( $jobs ) > self::MAX_DR_JOBS_COUNTER ) {
130 $jobQueueGroup->push( $jobs );
131 $jobs = [];
132 }
133 }
134 $jobQueueGroup->push( $jobs );
135 }
136
140 public function run() {
141 if ( !$this->redirTitle ) {
142 $this->setLastError( 'Invalid title' );
143
144 return false;
145 }
146
147 if ( !$this->title->canExist() ) {
148 // Needs a proper title for WikiPageFactory::newFromTitle and RevisionStore::getRevisionByTitle
149 $this->setLastError( 'Cannot edit title' );
150
151 return false;
152 }
153
154 $targetRev = $this->revisionLookup
155 ->getRevisionByTitle( $this->title, 0, IDBAccessObject::READ_LATEST );
156 if ( !$targetRev ) {
157 wfDebug( __METHOD__ . ": target redirect already deleted, ignoring" );
158
159 return true;
160 }
161 $content = $targetRev->getContent( SlotRecord::MAIN );
162 $currentDest = $content ? $content->getRedirectTarget() : null;
163 if ( !$currentDest || !$currentDest->equals( $this->redirTitle ) ) {
164 wfDebug( __METHOD__ . ": Redirect has changed since the job was queued" );
165
166 return true;
167 }
168
169 // Check for a suppression tag (used e.g. in periodically archived discussions)
170 $mw = $this->magicWordFactory->get( 'staticredirect' );
171 if ( $content->matchMagicWord( $mw ) ) {
172 wfDebug( __METHOD__ . ": skipping: suppressed with __STATICREDIRECT__" );
173
174 return true;
175 }
176
177 // Find the current final destination
178 $newTitle = self::getFinalDestination( $this->redirTitle );
179 if ( !$newTitle ) {
180 wfDebug( __METHOD__ .
181 ": skipping: single redirect, circular redirect or invalid redirect destination" );
182
183 return true;
184 }
185 if ( $newTitle->equals( $this->redirTitle ) ) {
186 // The redirect is already right, no need to change it
187 // This can happen if the page was moved back (say after vandalism)
188 wfDebug( __METHOD__ . " : skipping, already good" );
189 }
190
191 // Preserve fragment (T16904)
192 $newTitle = Title::makeTitle( $newTitle->getNamespace(), $newTitle->getDBkey(),
193 $currentDest->getFragment(), $newTitle->getInterwiki() );
194
195 // Fix the text
196 $newContent = $content->updateRedirect( $newTitle );
197
198 if ( $newContent->equals( $content ) ) {
199 $this->setLastError( 'Content unchanged???' );
200
201 return false;
202 }
203
204 $user = $this->getUser();
205 if ( !$user ) {
206 $this->setLastError( 'Invalid user' );
207
208 return false;
209 }
210
211 // Save it
212 // phpcs:ignore MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgUser
213 global $wgUser;
214 $oldUser = $wgUser;
215 $wgUser = $user;
216 $article = $this->wikiPageFactory->newFromTitle( $this->title );
217
218 // Messages: double-redirect-fixed-move, double-redirect-fixed-maintenance
219 $reason = wfMessage( 'double-redirect-fixed-' . $this->params['reason'],
220 $this->redirTitle->getPrefixedText(), $newTitle->getPrefixedText()
221 )->inContentLanguage()->text();
222 // Avoid RC flood, and use minor to avoid email notifs
224 $article->doUserEditContent( $newContent, $user, $reason, $flags );
225 $wgUser = $oldUser;
226
227 return true;
228 }
229
238 public static function getFinalDestination( $title ) {
239 $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
240
241 // Circular redirect check
242 $seenTitles = [];
243 $dest = false;
244
245 while ( true ) {
246 $titleText = CacheKeyHelper::getKeyForPage( $title );
247 if ( isset( $seenTitles[$titleText] ) ) {
248 wfDebug( __METHOD__, "Circular redirect detected, aborting" );
249
250 return false;
251 }
252 $seenTitles[$titleText] = true;
253
254 if ( $title->isExternal() ) {
255 // If the target is interwiki, we have to break early (T42352).
256 // Otherwise it will look up a row in the local page table
257 // with the namespace/page of the interwiki target which can cause
258 // unexpected results (e.g. X -> foo:Bar -> Bar -> .. )
259 break;
260 }
261 $row = $dbw->newSelectQueryBuilder()
262 ->select( [ 'rd_namespace', 'rd_title', 'rd_interwiki' ] )
263 ->from( 'redirect' )
264 ->join( 'page', null, 'page_id = rd_from' )
265 ->where( [ 'page_namespace' => $title->getNamespace() ] )
266 ->andWhere( [ 'page_title' => $title->getDBkey() ] )
267 ->caller( __METHOD__ )->fetchRow();
268 if ( !$row ) {
269 # No redirect from here, chain terminates
270 break;
271 } else {
272 $dest = $title = Title::makeTitle(
273 $row->rd_namespace,
274 $row->rd_title,
275 '',
276 $row->rd_interwiki
277 );
278 }
279 }
280
281 return $dest;
282 }
283
291 private function getUser() {
292 if ( !self::$user ) {
293 $username = wfMessage( 'double-redirect-fixer' )->inContentLanguage()->text();
294 self::$user = User::newFromName( $username );
295 # User::newFromName() can return false on a badly configured wiki.
296 if ( self::$user && !self::$user->isRegistered() ) {
297 self::$user->addToDatabase();
298 }
299 }
300
301 return self::$user;
302 }
303}
getUser()
const EDIT_INTERNAL
Definition Defines.php:133
const EDIT_UPDATE
Definition Defines.php:127
const EDIT_SUPPRESS_RC
Definition Defines.php:129
const EDIT_MINOR
Definition Defines.php:128
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
$wgUser
Definition Setup.php:543
Fix any double redirects after moving a page.
static getFinalDestination( $title)
Get the final destination of a redirect.
__construct(PageReference $page, array $params, RevisionLookup $revisionLookup, MagicWordFactory $magicWordFactory, WikiPageFactory $wikiPageFactory)
static fixRedirects( $reason, $redirTitle)
Insert jobs into the job queue to fix redirects to the given title.
Class to both describe a background job and handle jobs.
Definition Job.php:40
Title $title
Definition Job.php:51
setLastError( $error)
Definition Job.php:434
Helper class for mapping value objects representing basic entities to cache keys.
Service locator for MediaWiki core services.
Service for creating WikiPage objects.
Store information about magic words, and create/cache MagicWord objects.
Value object representing a content slot associated with a page revision.
Represents a title within MediaWiki.
Definition Title.php:78
canExist()
Can this title represent a page in the wiki's database?
Definition Title.php:1212
getNamespace()
Get the namespace index, i.e.
Definition Title.php:1044
getDBkey()
Get the main part with underscores.
Definition Title.php:1035
internal since 1.36
Definition User.php:93
Represents the target of a wiki link.
isExternal()
Whether this LinkTarget has an interwiki component.
Interface for objects (potentially) representing a page that can be viewable and linked to on a wiki.
Service for looking up page revisions.