Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4152514
DoubleRedirectJob.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
DoubleRedirectJob.php
View Options
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
use
MediaWiki\Cache\CacheKeyHelper
;
use
MediaWiki\Linker\LinkTarget
;
use
MediaWiki\MediaWikiServices
;
use
MediaWiki\Page\PageReference
;
use
MediaWiki\Page\WikiPageFactory
;
use
MediaWiki\Parser\MagicWordFactory
;
use
MediaWiki\Revision\RevisionLookup
;
use
MediaWiki\Revision\SlotRecord
;
use
MediaWiki\Title\Title
;
use
MediaWiki\User\User
;
use
Wikimedia\Rdbms\IDBAccessObject
;
/**
* Fix any double redirects after moving a page.
*
* @ingroup JobQueue
*/
class
DoubleRedirectJob
extends
Job
{
/**
* @var int Max number of double redirect jobs counter.
* This is meant to avoid excessive memory usage. This is
* also used in fixDoubleRedirects.php script.
*/
public
const
MAX_DR_JOBS_COUNTER
=
10000
;
/** @var Title The title which has changed, redirects pointing to this
* title are fixed
*/
private
$redirTitle
;
/** @var User */
private
static
$user
;
/** @var RevisionLookup */
private
$revisionLookup
;
/** @var MagicWordFactory */
private
$magicWordFactory
;
/** @var WikiPageFactory */
private
$wikiPageFactory
;
/**
* @param PageReference $page
* @param array $params Expected to contain these elements:
* - 'redirTitle' => string The title that changed and should be fixed.
* - 'reason' => string Reason for the change, can be "move" or "maintenance". Used as a suffix
* for the message keys "double-redirect-fixed-move" and
* "double-redirect-fixed-maintenance".
* ]
* @param RevisionLookup $revisionLookup
* @param MagicWordFactory $magicWordFactory
* @param WikiPageFactory $wikiPageFactory
*/
public
function
__construct
(
PageReference
$page
,
array
$params
,
RevisionLookup
$revisionLookup
,
MagicWordFactory
$magicWordFactory
,
WikiPageFactory
$wikiPageFactory
)
{
parent
::
__construct
(
'fixDoubleRedirect'
,
$page
,
$params
);
$this
->
redirTitle
=
Title
::
newFromText
(
$params
[
'redirTitle'
]
);
$this
->
revisionLookup
=
$revisionLookup
;
$this
->
magicWordFactory
=
$magicWordFactory
;
$this
->
wikiPageFactory
=
$wikiPageFactory
;
}
/**
* Insert jobs into the job queue to fix redirects to the given title
* @param string $reason The reason for the fix, see message
* "double-redirect-fixed-<reason>"
* @param LinkTarget $redirTitle The title which has changed, redirects
* pointing to this title are fixed
*/
public
static
function
fixRedirects
(
$reason
,
$redirTitle
)
{
# Need to use the primary DB to get the redirect table updated in the same transaction
$services
=
MediaWikiServices
::
getInstance
();
$dbw
=
$services
->
getConnectionProvider
()->
getPrimaryDatabase
();
$res
=
$dbw
->
newSelectQueryBuilder
()
->
select
(
[
'page_namespace'
,
'page_title'
]
)
->
from
(
'redirect'
)
->
join
(
'page'
,
null
,
'page_id = rd_from'
)
->
where
(
[
'rd_namespace'
=>
$redirTitle
->
getNamespace
(),
'rd_title'
=>
$redirTitle
->
getDBkey
()
]
)
->
andWhere
(
[
'rd_interwiki'
=>
''
]
)
->
caller
(
__METHOD__
)->
fetchResultSet
();
if
(
!
$res
->
numRows
()
)
{
return
;
}
$jobs
=
[];
$jobQueueGroup
=
$services
->
getJobQueueGroup
();
foreach
(
$res
as
$row
)
{
$title
=
Title
::
makeTitleSafe
(
$row
->
page_namespace
,
$row
->
page_title
);
if
(
!
$title
||
!
$title
->
canExist
()
)
{
continue
;
}
$jobs
[]
=
new
self
(
$title
,
[
'reason'
=>
$reason
,
'redirTitle'
=>
$services
->
getTitleFormatter
()
->
getPrefixedDBkey
(
$redirTitle
)
],
$services
->
getRevisionLookup
(),
$services
->
getMagicWordFactory
(),
$services
->
getWikiPageFactory
()
);
# Avoid excessive memory usage
if
(
count
(
$jobs
)
>
self
::
MAX_DR_JOBS_COUNTER
)
{
$jobQueueGroup
->
push
(
$jobs
);
$jobs
=
[];
}
}
$jobQueueGroup
->
push
(
$jobs
);
}
/**
* @return bool
*/
public
function
run
()
{
if
(
!
$this
->
redirTitle
)
{
$this
->
setLastError
(
'Invalid title'
);
return
false
;
}
if
(
!
$this
->
title
->
canExist
()
)
{
// Needs a proper title for WikiPageFactory::newFromTitle and RevisionStore::getRevisionByTitle
$this
->
setLastError
(
'Cannot edit title'
);
return
false
;
}
$targetRev
=
$this
->
revisionLookup
->
getRevisionByTitle
(
$this
->
title
,
0
,
IDBAccessObject
::
READ_LATEST
);
if
(
!
$targetRev
)
{
wfDebug
(
__METHOD__
.
": target redirect already deleted, ignoring"
);
return
true
;
}
$content
=
$targetRev
->
getContent
(
SlotRecord
::
MAIN
);
$currentDest
=
$content
?
$content
->
getRedirectTarget
()
:
null
;
if
(
!
$currentDest
||
!
$currentDest
->
equals
(
$this
->
redirTitle
)
)
{
wfDebug
(
__METHOD__
.
": Redirect has changed since the job was queued"
);
return
true
;
}
// Check for a suppression tag (used e.g. in periodically archived discussions)
$mw
=
$this
->
magicWordFactory
->
get
(
'staticredirect'
);
if
(
$content
->
matchMagicWord
(
$mw
)
)
{
wfDebug
(
__METHOD__
.
": skipping: suppressed with __STATICREDIRECT__"
);
return
true
;
}
// Find the current final destination
$newTitle
=
self
::
getFinalDestination
(
$this
->
redirTitle
);
if
(
!
$newTitle
)
{
wfDebug
(
__METHOD__
.
": skipping: single redirect, circular redirect or invalid redirect destination"
);
return
true
;
}
if
(
$newTitle
->
equals
(
$this
->
redirTitle
)
)
{
// The redirect is already right, no need to change it
// This can happen if the page was moved back (say after vandalism)
wfDebug
(
__METHOD__
.
" : skipping, already good"
);
}
// Preserve fragment (T16904)
$newTitle
=
Title
::
makeTitle
(
$newTitle
->
getNamespace
(),
$newTitle
->
getDBkey
(),
$currentDest
->
getFragment
(),
$newTitle
->
getInterwiki
()
);
// Fix the text
$newContent
=
$content
->
updateRedirect
(
$newTitle
);
if
(
$newContent
->
equals
(
$content
)
)
{
$this
->
setLastError
(
'Content unchanged???'
);
return
false
;
}
$user
=
$this
->
getUser
();
if
(
!
$user
)
{
$this
->
setLastError
(
'Invalid user'
);
return
false
;
}
// Save it
// phpcs:ignore MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgUser
global
$wgUser
;
$oldUser
=
$wgUser
;
$wgUser
=
$user
;
$article
=
$this
->
wikiPageFactory
->
newFromTitle
(
$this
->
title
);
// Messages: double-redirect-fixed-move, double-redirect-fixed-maintenance
$reason
=
wfMessage
(
'double-redirect-fixed-'
.
$this
->
params
[
'reason'
],
$this
->
redirTitle
->
getPrefixedText
(),
$newTitle
->
getPrefixedText
()
)->
inContentLanguage
()->
text
();
// Avoid RC flood, and use minor to avoid email notifs
$flags
=
EDIT_UPDATE
|
EDIT_SUPPRESS_RC
|
EDIT_INTERNAL
|
EDIT_MINOR
;
$article
->
doUserEditContent
(
$newContent
,
$user
,
$reason
,
$flags
);
$wgUser
=
$oldUser
;
return
true
;
}
/**
* Get the final destination of a redirect
*
* @param LinkTarget $title
*
* @return Title|false The final Title after following all redirects, or false if
* the page is not a redirect or the redirect loops.
*/
public
static
function
getFinalDestination
(
$title
)
{
$dbw
=
MediaWikiServices
::
getInstance
()->
getConnectionProvider
()->
getPrimaryDatabase
();
// Circular redirect check
$seenTitles
=
[];
$dest
=
false
;
while
(
true
)
{
$titleText
=
CacheKeyHelper
::
getKeyForPage
(
$title
);
if
(
isset
(
$seenTitles
[
$titleText
]
)
)
{
wfDebug
(
__METHOD__
,
"Circular redirect detected, aborting"
);
return
false
;
}
$seenTitles
[
$titleText
]
=
true
;
if
(
$title
->
isExternal
()
)
{
// If the target is interwiki, we have to break early (T42352).
// Otherwise it will look up a row in the local page table
// with the namespace/page of the interwiki target which can cause
// unexpected results (e.g. X -> foo:Bar -> Bar -> .. )
break
;
}
$row
=
$dbw
->
newSelectQueryBuilder
()
->
select
(
[
'rd_namespace'
,
'rd_title'
,
'rd_interwiki'
]
)
->
from
(
'redirect'
)
->
join
(
'page'
,
null
,
'page_id = rd_from'
)
->
where
(
[
'page_namespace'
=>
$title
->
getNamespace
()
]
)
->
andWhere
(
[
'page_title'
=>
$title
->
getDBkey
()
]
)
->
caller
(
__METHOD__
)->
fetchRow
();
if
(
!
$row
)
{
# No redirect from here, chain terminates
break
;
}
else
{
$dest
=
$title
=
Title
::
makeTitle
(
$row
->
rd_namespace
,
$row
->
rd_title
,
''
,
$row
->
rd_interwiki
);
}
}
return
$dest
;
}
/**
* Get a user object for doing edits, from a request-lifetime cache
* False will be returned if the user name specified in the
* 'double-redirect-fixer' message is invalid.
*
* @return User|false
*/
private
function
getUser
()
{
if
(
!
self
::
$user
)
{
$username
=
wfMessage
(
'double-redirect-fixer'
)->
inContentLanguage
()->
text
();
self
::
$user
=
User
::
newFromName
(
$username
);
# User::newFromName() can return false on a badly configured wiki.
if
(
self
::
$user
&&
!
self
::
$user
->
isRegistered
()
)
{
self
::
$user
->
addToDatabase
();
}
}
return
self
::
$user
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Aug 19 2026, 20:23 (7 w, 1 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
59/9d/90ef25d2248fc073aeb82592e142
Default Alt Text
DoubleRedirectJob.php (9 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment