Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4088298
CategoryLinksTable.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
CategoryLinksTable.php
View Options
<?php
namespace
MediaWiki\Deferred\LinksUpdate
;
use
Collation
;
use
MediaWiki\DAO\WikiAwareEntity
;
use
MediaWiki\Language\ILanguageConverter
;
use
MediaWiki\Languages\LanguageConverterFactory
;
use
MediaWiki\Page\PageReferenceValue
;
use
MediaWiki\Page\WikiPageFactory
;
use
MediaWiki\Parser\ParserOutput
;
use
MediaWiki\Parser\Sanitizer
;
use
MediaWiki\Title\NamespaceInfo
;
use
MediaWiki\Title\Title
;
use
PurgeJobUtils
;
/**
* categorylinks
*
* Link ID format: string[]
* - 0: Category name
* - 1: User-specified sort key (cl_sortkey_prefix)
*
* @since 1.38
*/
class
CategoryLinksTable
extends
TitleLinksTable
{
/**
* @var array Associative array of new links, with the category name in the
* key. The value is a list consisting of the sort key prefix and the sort
* key.
*/
private
$newLinks
=
[];
/**
* @var array|null Associative array of existing links, or null if it has
* not been loaded yet
*/
private
$existingLinks
;
/**
* @var array Associative array of saved timestamps, if there is a force
* refresh due to a page move
*/
private
$savedTimestamps
=
null
;
/** @var ILanguageConverter */
private
$languageConverter
;
/** @var \Collation */
private
$collation
;
/** @var string The collation name for cl_collation */
private
$collationName
;
/** @var string The table name */
private
$tableName
=
'categorylinks'
;
/** @var bool */
private
$isTempTable
;
/** @var string The category type, which depends on the source page */
private
$categoryType
;
/** @var NamespaceInfo */
private
$namespaceInfo
;
/** @var WikiPageFactory */
private
$wikiPageFactory
;
/**
* @param LanguageConverterFactory $converterFactory
* @param NamespaceInfo $namespaceInfo
* @param WikiPageFactory $wikiPageFactory
* @param Collation $collation
* @param string $collationName
* @param string $tableName
* @param bool $isTempTable
*/
public
function
__construct
(
LanguageConverterFactory
$converterFactory
,
NamespaceInfo
$namespaceInfo
,
WikiPageFactory
$wikiPageFactory
,
Collation
$collation
,
$collationName
,
$tableName
,
$isTempTable
)
{
$this
->
languageConverter
=
$converterFactory
->
getLanguageConverter
();
$this
->
namespaceInfo
=
$namespaceInfo
;
$this
->
wikiPageFactory
=
$wikiPageFactory
;
$this
->
collation
=
$collation
;
$this
->
collationName
=
$collationName
;
$this
->
tableName
=
$tableName
;
$this
->
isTempTable
=
$isTempTable
;
}
/**
* Cache the category type after the source page has been set
*/
public
function
startUpdate
()
{
$this
->
categoryType
=
$this
->
namespaceInfo
->
getCategoryLinkType
(
$this
->
getSourcePage
()->
getNamespace
()
);
}
public
function
setParserOutput
(
ParserOutput
$parserOutput
)
{
$this
->
newLinks
=
[];
$sourceTitle
=
Title
::
castFromPageIdentity
(
$this
->
getSourcePage
()
);
$sortKeyInputs
=
[];
foreach
(
$parserOutput
->
getCategoryNames
()
as
$name
)
{
$sortKey
=
$parserOutput
->
getCategorySortKey
(
$name
);
'@phan-var string $sortKey'
;
// sort key will never be null
if
(
$sortKey
==
''
)
{
$sortKey
=
$parserOutput
->
getPageProperty
(
"defaultsort"
)
??
''
;
}
$sortKey
=
$this
->
languageConverter
->
convertCategoryKey
(
$sortKey
);
// Clean up the sort key, regardless of source
$sortKey
=
Sanitizer
::
decodeCharReferences
(
$sortKey
);
$sortKey
=
str_replace
(
"
\n
"
,
''
,
$sortKey
);
// If the sort key is longer then 255 bytes, it is truncated by DB,
// and then doesn't match when comparing existing vs current
// categories, causing T27254.
$sortKeyPrefix
=
mb_strcut
(
$sortKey
,
0
,
255
);
$targetTitle
=
Title
::
makeTitle
(
NS_CATEGORY
,
$name
);
$this
->
languageConverter
->
findVariantLink
(
$name
,
$targetTitle
,
true
);
// Ignore the returned text, DB key should be used for links (T328477).
$name
=
$targetTitle
->
getDBKey
();
// Treat custom sort keys as a prefix, so that if multiple
// things are forced to sort as '*' or something, they'll
// sort properly in the category rather than in page_id
// order or such.
$sortKeyInputs
[
$name
]
=
$sourceTitle
->
getCategorySortkey
(
$sortKeyPrefix
);
$this
->
newLinks
[
$name
]
=
[
$sortKeyPrefix
];
}
$sortKeys
=
$this
->
collation
->
getSortKeys
(
$sortKeyInputs
);
foreach
(
$sortKeys
as
$name
=>
$sortKey
)
{
$this
->
newLinks
[
$name
][
1
]
=
$sortKey
;
}
}
protected
function
getTableName
()
{
return
$this
->
tableName
;
}
protected
function
getFromField
()
{
return
'cl_from'
;
}
protected
function
getExistingFields
()
{
$fields
=
[
'cl_to'
,
'cl_sortkey_prefix'
];
if
(
$this
->
needForcedLinkRefresh
()
)
{
$fields
[]
=
'cl_timestamp'
;
}
return
$fields
;
}
/**
* Get the new link IDs. The link ID is a list with the name in the first
* element and the sort key prefix in the second element.
*
* @return iterable<array>
*/
protected
function
getNewLinkIDs
()
{
foreach
(
$this
->
newLinks
as
$name
=>
[
$prefix
,
]
)
{
yield
[
(
string
)
$name
,
$prefix
];
}
}
/**
* Get the existing links from the database
*/
private
function
fetchExistingLinks
()
{
$this
->
existingLinks
=
[];
$this
->
savedTimestamps
=
[];
$force
=
$this
->
needForcedLinkRefresh
();
foreach
(
$this
->
fetchExistingRows
()
as
$row
)
{
$this
->
existingLinks
[
$row
->
cl_to
]
=
$row
->
cl_sortkey_prefix
;
if
(
$force
)
{
$this
->
savedTimestamps
[
$row
->
cl_to
]
=
$row
->
cl_timestamp
;
}
}
}
/**
* Get the existing links as an associative array, with the category name
* in the key and the sort key prefix in the value.
*
* @return array
*/
private
function
getExistingLinks
()
{
if
(
$this
->
existingLinks
===
null
)
{
$this
->
fetchExistingLinks
();
}
return
$this
->
existingLinks
;
}
private
function
getSavedTimestamps
()
{
if
(
$this
->
savedTimestamps
===
null
)
{
$this
->
fetchExistingLinks
();
}
return
$this
->
savedTimestamps
;
}
/**
* @return \Generator
*/
protected
function
getExistingLinkIDs
()
{
foreach
(
$this
->
getExistingLinks
()
as
$name
=>
$sortkey
)
{
yield
[
(
string
)
$name
,
$sortkey
];
}
}
protected
function
isExisting
(
$linkId
)
{
$links
=
$this
->
getExistingLinks
();
[
$name
,
$prefix
]
=
$linkId
;
return
\array_key_exists
(
$name
,
$links
)
&&
$links
[
$name
]
===
$prefix
;
}
protected
function
isInNewSet
(
$linkId
)
{
[
$name
,
$prefix
]
=
$linkId
;
return
\array_key_exists
(
$name
,
$this
->
newLinks
)
&&
$this
->
newLinks
[
$name
][
0
]
===
$prefix
;
}
protected
function
insertLink
(
$linkId
)
{
[
$name
,
$prefix
]
=
$linkId
;
$sortKey
=
$this
->
newLinks
[
$name
][
1
];
$savedTimestamps
=
$this
->
getSavedTimestamps
();
// Preserve cl_timestamp in the case of a forced refresh
$timestamp
=
$this
->
getDB
()->
timestamp
(
$savedTimestamps
[
$name
]
??
0
);
$this
->
insertRow
(
[
'cl_to'
=>
$name
,
'cl_sortkey'
=>
$sortKey
,
'cl_timestamp'
=>
$timestamp
,
'cl_sortkey_prefix'
=>
$prefix
,
'cl_collation'
=>
$this
->
collationName
,
'cl_type'
=>
$this
->
categoryType
,
]
);
}
protected
function
deleteLink
(
$linkId
)
{
$this
->
deleteRow
(
[
'cl_to'
=>
$linkId
[
0
]
]
);
}
protected
function
needForcedLinkRefresh
()
{
// cl_sortkey and possibly cl_type will change if it is a page move
return
$this
->
isMove
();
}
protected
function
makePageReferenceValue
(
$linkId
):
PageReferenceValue
{
return
new
PageReferenceValue
(
NS_CATEGORY
,
$linkId
[
0
],
WikiAwareEntity
::
LOCAL
);
}
protected
function
makeTitle
(
$linkId
):
Title
{
return
Title
::
makeTitle
(
NS_CATEGORY
,
$linkId
[
0
]
);
}
protected
function
deduplicateLinkIds
(
$linkIds
)
{
$seen
=
[];
foreach
(
$linkIds
as
$linkId
)
{
if
(
!
\array_key_exists
(
$linkId
[
0
],
$seen
)
)
{
$seen
[
$linkId
[
0
]]
=
true
;
yield
$linkId
;
}
}
}
protected
function
finishUpdate
()
{
if
(
$this
->
isTempTable
)
{
// Don't do invalidations for temporary collations
return
;
}
// A update of sortkey on move is detected as insert + delete,
// but the categories does not need to update the counters or invalidate caches
$allInsertedLinks
=
array_column
(
$this
->
insertedLinks
,
0
);
$allDeletedLinks
=
array_column
(
$this
->
deletedLinks
,
0
);
$insertedLinks
=
array_diff
(
$allInsertedLinks
,
$allDeletedLinks
);
$deletedLinks
=
array_diff
(
$allDeletedLinks
,
$allInsertedLinks
);
$this
->
invalidateCategories
(
$insertedLinks
,
$deletedLinks
);
$this
->
updateCategoryCounts
(
$insertedLinks
,
$deletedLinks
);
}
private
function
invalidateCategories
(
array
$insertedLinks
,
array
$deletedLinks
)
{
$changedCategoryNames
=
array_merge
(
$insertedLinks
,
$deletedLinks
);
PurgeJobUtils
::
invalidatePages
(
$this
->
getDB
(),
NS_CATEGORY
,
$changedCategoryNames
);
}
/**
* Update all the appropriate counts in the category table.
* @param array $insertedLinks
* @param array $deletedLinks
*/
private
function
updateCategoryCounts
(
array
$insertedLinks
,
array
$deletedLinks
)
{
if
(
!
$insertedLinks
&&
!
$deletedLinks
)
{
return
;
}
$domainId
=
$this
->
getDB
()->
getDomainID
();
$wp
=
$this
->
wikiPageFactory
->
newFromTitle
(
$this
->
getSourcePage
()
);
$lbf
=
$this
->
getLBFactory
();
$size
=
$this
->
getBatchSize
();
// T163801: try to release any row locks to reduce contention
$lbf
->
commitAndWaitForReplication
(
__METHOD__
,
$this
->
getTransactionTicket
()
);
if
(
count
(
$insertedLinks
)
+
count
(
$deletedLinks
)
<
$size
)
{
$wp
->
updateCategoryCounts
(
$insertedLinks
,
$deletedLinks
,
$this
->
getSourcePageId
()
);
$lbf
->
commitAndWaitForReplication
(
__METHOD__
,
$this
->
getTransactionTicket
()
);
}
else
{
$addedChunks
=
array_chunk
(
$insertedLinks
,
$size
);
foreach
(
$addedChunks
as
$chunk
)
{
$wp
->
updateCategoryCounts
(
$chunk
,
[],
$this
->
getSourcePageId
()
);
if
(
count
(
$addedChunks
)
>
1
)
{
$lbf
->
commitAndWaitForReplication
(
__METHOD__
,
$this
->
getTransactionTicket
()
);
}
}
$deletedChunks
=
array_chunk
(
$deletedLinks
,
$size
);
foreach
(
$deletedChunks
as
$chunk
)
{
$wp
->
updateCategoryCounts
(
[],
$chunk
,
$this
->
getSourcePageId
()
);
if
(
count
(
$deletedChunks
)
>
1
)
{
$lbf
->
commitAndWaitForReplication
(
__METHOD__
,
$this
->
getTransactionTicket
()
);
}
}
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Aug 18 2026, 09:56 (6 w, 1 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c9/14/026cc06b5ba53bf17e383c4b9884
Default Alt Text
CategoryLinksTable.php (9 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment