MediaWiki 1.44.0
DoubleRedirectJob.php
Go to the documentation of this file.
1<?php
22
35
41class DoubleRedirectJob extends Job {
47 public const MAX_DR_JOBS_COUNTER = 10000;
48
52 private $redirTitle;
53
55 private static $user;
56
58 private $revisionLookup;
59
61 private $magicWordFactory;
62
64 private $wikiPageFactory;
65
78 public function __construct(
79 PageReference $page,
80 array $params,
81 RevisionLookup $revisionLookup,
82 MagicWordFactory $magicWordFactory,
83 WikiPageFactory $wikiPageFactory
84 ) {
85 parent::__construct( 'fixDoubleRedirect', $page, $params );
86 $this->redirTitle = Title::newFromText( $params['redirTitle'] );
87 $this->revisionLookup = $revisionLookup;
88 $this->magicWordFactory = $magicWordFactory;
89 $this->wikiPageFactory = $wikiPageFactory;
90 }
91
99 public static function fixRedirects( $reason, $redirTitle ) {
100 # Need to use the primary DB to get the redirect table updated in the same transaction
101 $services = MediaWikiServices::getInstance();
102 $dbw = $services->getConnectionProvider()->getPrimaryDatabase();
103 $res = $dbw->newSelectQueryBuilder()
104 ->select( [ 'page_namespace', 'page_title' ] )
105 ->from( 'redirect' )
106 ->join( 'page', null, 'page_id = rd_from' )
107 ->where( [ 'rd_namespace' => $redirTitle->getNamespace(), 'rd_title' => $redirTitle->getDBkey() ] )
108 ->andWhere( [ 'rd_interwiki' => '' ] )
109 ->caller( __METHOD__ )->fetchResultSet();
110 if ( !$res->numRows() ) {
111 return;
112 }
113 $jobs = [];
114 $jobQueueGroup = $services->getJobQueueGroup();
115 foreach ( $res as $row ) {
116 $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
117 if ( !$title || !$title->canExist() ) {
118 continue;
119 }
120
121 $jobs[] = new self(
122 $title,
123 [
124 'reason' => $reason,
125 'redirTitle' => $services->getTitleFormatter()
126 ->getPrefixedDBkey( $redirTitle )
127 ],
128 $services->getRevisionLookup(),
129 $services->getMagicWordFactory(),
130 $services->getWikiPageFactory()
131 );
132 # Avoid excessive memory usage
133 if ( count( $jobs ) > self::MAX_DR_JOBS_COUNTER ) {
134 $jobQueueGroup->push( $jobs );
135 $jobs = [];
136 }
137 }
138 $jobQueueGroup->push( $jobs );
139 }
140
144 public function run() {
145 if ( !$this->redirTitle ) {
146 $this->setLastError( 'Invalid title' );
147
148 return false;
149 }
150
151 if ( !$this->title->canExist() ) {
152 // Needs a proper title for WikiPageFactory::newFromTitle and RevisionStore::getRevisionByTitle
153 $this->setLastError( 'Cannot edit title' );
154
155 return false;
156 }
157
158 $targetRev = $this->revisionLookup
159 ->getRevisionByTitle( $this->title, 0, IDBAccessObject::READ_LATEST );
160 if ( !$targetRev ) {
161 wfDebug( __METHOD__ . ": target redirect already deleted, ignoring" );
162
163 return true;
164 }
165 $content = $targetRev->getContent( SlotRecord::MAIN );
166 $currentDest = $content ? $content->getRedirectTarget() : null;
167 if ( !$currentDest || !$currentDest->equals( $this->redirTitle ) ) {
168 wfDebug( __METHOD__ . ": Redirect has changed since the job was queued" );
169
170 return true;
171 }
172
173 // Check for a suppression tag (used e.g. in periodically archived discussions)
174 $mw = $this->magicWordFactory->get( 'staticredirect' );
175 if ( $content->matchMagicWord( $mw ) ) {
176 wfDebug( __METHOD__ . ": skipping: suppressed with __STATICREDIRECT__" );
177
178 return true;
179 }
180
181 // Find the current final destination
182 $newTitle = self::getFinalDestination( $this->redirTitle );
183 if ( !$newTitle ) {
184 wfDebug( __METHOD__ .
185 ": skipping: single redirect, circular redirect or invalid redirect destination" );
186
187 return true;
188 }
189 if ( $newTitle->equals( $this->redirTitle ) ) {
190 // The redirect is already right, no need to change it
191 // This can happen if the page was moved back (say after vandalism)
192 wfDebug( __METHOD__ . " : skipping, already good" );
193 }
194
195 // Preserve fragment (T16904)
196 $newTitle = Title::makeTitle( $newTitle->getNamespace(), $newTitle->getDBkey(),
197 $currentDest->getFragment(), $newTitle->getInterwiki() );
198
199 // Fix the text
200 $newContent = $content->updateRedirect( $newTitle );
201
202 if ( $newContent->equals( $content ) ) {
203 $this->setLastError( 'Content unchanged???' );
204
205 return false;
206 }
207
208 $user = $this->getUser();
209 if ( !$user ) {
210 $this->setLastError( 'Invalid user' );
211
212 return false;
213 }
214
215 // Save it
216 // phpcs:ignore MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgUser
217 global $wgUser;
218 $oldUser = $wgUser;
219 $wgUser = $user;
220 $article = $this->wikiPageFactory->newFromTitle( $this->title );
221
222 // Messages: double-redirect-fixed-move, double-redirect-fixed-maintenance
223 $reason = wfMessage( 'double-redirect-fixed-' . $this->params['reason'],
224 $this->redirTitle->getPrefixedText(), $newTitle->getPrefixedText()
225 )->inContentLanguage()->text();
226 // Avoid RC flood, and use minor to avoid email notifs
228 $article->doUserEditContent( $newContent, $user, $reason, $flags );
229 $wgUser = $oldUser;
230
231 return true;
232 }
233
242 public static function getFinalDestination( $title ) {
243 $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
244
245 // Circular redirect check
246 $seenTitles = [];
247 $dest = false;
248
249 while ( true ) {
250 $titleText = CacheKeyHelper::getKeyForPage( $title );
251 if ( isset( $seenTitles[$titleText] ) ) {
252 wfDebug( __METHOD__, "Circular redirect detected, aborting" );
253
254 return false;
255 }
256 $seenTitles[$titleText] = true;
257
258 if ( $title->isExternal() ) {
259 // If the target is interwiki, we have to break early (T42352).
260 // Otherwise it will look up a row in the local page table
261 // with the namespace/page of the interwiki target which can cause
262 // unexpected results (e.g. X -> foo:Bar -> Bar -> .. )
263 break;
264 }
265 $row = $dbw->newSelectQueryBuilder()
266 ->select( [ 'rd_namespace', 'rd_title', 'rd_interwiki' ] )
267 ->from( 'redirect' )
268 ->join( 'page', null, 'page_id = rd_from' )
269 ->where( [ 'page_namespace' => $title->getNamespace() ] )
270 ->andWhere( [ 'page_title' => $title->getDBkey() ] )
271 ->caller( __METHOD__ )->fetchRow();
272 if ( !$row ) {
273 # No redirect from here, chain terminates
274 break;
275 } else {
276 $dest = $title = Title::makeTitle(
277 $row->rd_namespace,
278 $row->rd_title,
279 '',
280 $row->rd_interwiki
281 );
282 }
283 }
284
285 return $dest;
286 }
287
295 private function getUser() {
296 if ( !self::$user ) {
297 $username = wfMessage( 'double-redirect-fixer' )->inContentLanguage()->text();
298 self::$user = User::newFromName( $username );
299 # User::newFromName() can return false on a badly configured wiki.
300 if ( self::$user && !self::$user->isRegistered() ) {
301 self::$user->addToDatabase();
302 }
303 }
304
305 return self::$user;
306 }
307}
308
310class_alias( DoubleRedirectJob::class, 'DoubleRedirectJob' );
const EDIT_INTERNAL
Signal that the page retrieve/save cycle happened entirely in this request.
Definition Defines.php:152
const EDIT_UPDATE
Article is assumed to be pre-existing, fail if it doesn't exist.
Definition Defines.php:131
const EDIT_SUPPRESS_RC
Definition Defines.php:140
const EDIT_MINOR
Mark this edit minor, if the user is allowed to do so.
Definition Defines.php:134
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:572
Helper class for mapping value objects representing basic entities to cache keys.
Describe and execute a background job.
Definition Job.php:41
array $params
Array of job parameters.
Definition Job.php:46
setLastError( $error)
Definition Job.php:435
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.
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
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:1214
getNamespace()
Get the namespace index, i.e.
Definition Title.php:1046
getDBkey()
Get the main part with underscores.
Definition Title.php:1037
User class for the MediaWiki software.
Definition User.php:121
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.
Interface for database access objects.