Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4118766
CategoryLinksTable.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
11 KB
Referenced Files
None
Subscribers
None
CategoryLinksTable.php
View Options
<?php
namespace
MediaWiki\Deferred\LinksUpdate
;
use
Collation
;
use
MediaWiki\Category\Category
;
use
MediaWiki\Config\Config
;
use
MediaWiki\HookContainer\HookContainer
;
use
MediaWiki\HookContainer\HookRunner
;
use
MediaWiki\JobQueue\JobQueueGroup
;
use
MediaWiki\JobQueue\Jobs\CategoryCountUpdateJob
;
use
MediaWiki\JobQueue\Utils\PurgeJobUtils
;
use
MediaWiki\Language\ILanguageConverter
;
use
MediaWiki\Languages\LanguageConverterFactory
;
use
MediaWiki\Logger\LoggerFactory
;
use
MediaWiki\Page\PageReferenceValue
;
use
MediaWiki\Page\WikiPageFactory
;
use
MediaWiki\Parser\ParserOutput
;
use
MediaWiki\Parser\ParserOutputLinkTypes
;
use
MediaWiki\Parser\Sanitizer
;
use
MediaWiki\Storage\NameTableStore
;
use
MediaWiki\Title\NamespaceInfo
;
use
MediaWiki\Title\Title
;
use
Wikimedia\ObjectCache\WANObjectCache
;
use
Wikimedia\Rdbms\ILoadBalancer
;
use
Wikimedia\Rdbms\IResultWrapper
;
/**
* categorylinks
*
* Link ID format: string[]
* - 0: Category name
* - 1: User-specified sort key (cl_sortkey_prefix)
*
* @since 1.38
*/
class
CategoryLinksTable
extends
TitleLinksTable
{
public
const
VIRTUAL_DOMAIN
=
'virtual-categorylinks'
;
/**
* @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
;
private
NameTableStore
$collationNameStore
;
private
JobQueueGroup
$jobQueueGroup
;
private
HookRunner
$hookRunner
;
/**
* @param LanguageConverterFactory $converterFactory
* @param NamespaceInfo $namespaceInfo
* @param WikiPageFactory $wikiPageFactory
* @param ILoadBalancer $loadBalancer
* @param WANObjectCache $WANObjectCache
* @param Config $config
* @param JobqueueGroup $jobQueueGroup
* @param HookContainer $hookContainer
* @param Collation $collation
* @param string $collationName
* @param string $tableName
* @param bool $isTempTable
*/
public
function
__construct
(
LanguageConverterFactory
$converterFactory
,
NamespaceInfo
$namespaceInfo
,
WikiPageFactory
$wikiPageFactory
,
ILoadBalancer
$loadBalancer
,
WANObjectCache
$WANObjectCache
,
Config
$config
,
JobqueueGroup
$jobQueueGroup
,
HookContainer
$hookContainer
,
Collation
$collation
,
$collationName
,
$tableName
,
$isTempTable
)
{
$this
->
languageConverter
=
$converterFactory
->
getLanguageConverter
();
$this
->
namespaceInfo
=
$namespaceInfo
;
$this
->
wikiPageFactory
=
$wikiPageFactory
;
$this
->
collation
=
$collation
;
$this
->
jobQueueGroup
=
$jobQueueGroup
;
$this
->
hookRunner
=
new
HookRunner
(
$hookContainer
);
$this
->
collationName
=
$collationName
;
$this
->
tableName
=
$tableName
;
$this
->
isTempTable
=
$isTempTable
;
$this
->
collationNameStore
=
new
NameTableStore
(
$loadBalancer
,
$WANObjectCache
,
LoggerFactory
::
getInstance
(
'SecondaryDataUpdate'
),
'collation'
,
'collation_id'
,
'collation_name'
);
}
/**
* 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
->
getLinkList
(
ParserOutputLinkTypes
::
CATEGORY
)
as
[
'link'
=>
$targetTitle
,
'sort'
=>
$sortKey
]
)
{
'@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
);
$name
=
$targetTitle
->
getDBkey
();
$targetTitle
=
Title
::
castFromLinkTarget
(
$targetTitle
);
$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
;
}
}
/** @inheritDoc */
protected
function
getTableName
()
{
return
$this
->
tableName
;
}
/** @inheritDoc */
protected
function
getFromField
()
{
return
'cl_from'
;
}
/** @inheritDoc */
protected
function
getExistingFields
()
{
$fields
=
[
'lt_title'
,
'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
->
lt_title
]
=
$row
->
cl_sortkey_prefix
;
if
(
$force
)
{
$this
->
savedTimestamps
[
$row
->
lt_title
]
=
$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
():
array
{
if
(
$this
->
savedTimestamps
===
null
)
{
$this
->
fetchExistingLinks
();
}
return
$this
->
savedTimestamps
;
}
/**
* @return \Generator
*/
protected
function
getExistingLinkIDs
()
{
foreach
(
$this
->
getExistingLinks
()
as
$name
=>
$sortkey
)
{
yield
[
(
string
)
$name
,
$sortkey
];
}
}
/** @inheritDoc */
protected
function
isExisting
(
$linkId
)
{
$links
=
$this
->
getExistingLinks
();
[
$name
,
$prefix
]
=
$linkId
;
return
\array_key_exists
(
$name
,
$links
)
&&
$links
[
$name
]
===
$prefix
;
}
/** @inheritDoc */
protected
function
isInNewSet
(
$linkId
)
{
[
$name
,
$prefix
]
=
$linkId
;
return
\array_key_exists
(
$name
,
$this
->
newLinks
)
&&
$this
->
newLinks
[
$name
][
0
]
===
$prefix
;
}
/** @inheritDoc */
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
);
$targetFields
=
[];
$targetFields
[
'cl_target_id'
]
=
$this
->
linkTargetLookup
->
acquireLinkTargetId
(
$this
->
makeTitle
(
$linkId
),
$this
->
getDB
()
);
$targetFields
[
'cl_collation_id'
]
=
$this
->
collationNameStore
->
acquireId
(
$this
->
collationName
);
$this
->
insertRow
(
$targetFields
+
[
'cl_sortkey'
=>
$sortKey
,
'cl_timestamp'
=>
$timestamp
,
'cl_sortkey_prefix'
=>
$prefix
,
'cl_type'
=>
$this
->
categoryType
,
]
);
}
/** @inheritDoc */
protected
function
deleteLink
(
$linkId
)
{
$this
->
deleteRow
(
[
'cl_target_id'
=>
$this
->
linkTargetLookup
->
acquireLinkTargetId
(
$this
->
makeTitle
(
$linkId
),
$this
->
getDB
()
)
]
);
}
/** @inheritDoc */
protected
function
needForcedLinkRefresh
()
{
// cl_sortkey and possibly cl_type will change if it is a page move
return
$this
->
isMove
();
}
/** @inheritDoc */
protected
function
makePageReferenceValue
(
$linkId
):
PageReferenceValue
{
return
PageReferenceValue
::
localReference
(
NS_CATEGORY
,
$linkId
[
0
]
);
}
/** @inheritDoc */
protected
function
makeTitle
(
$linkId
):
Title
{
return
Title
::
makeTitle
(
NS_CATEGORY
,
$linkId
[
0
]
);
}
/** @inheritDoc */
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
);
if
(
$insertedLinks
||
$deletedLinks
)
{
$this
->
jobQueueGroup
->
lazyPush
(
CategoryCountUpdateJob
::
newSpec
(
$this
->
getSourcePage
(),
$insertedLinks
,
$deletedLinks
,
$this
->
getBatchSize
()
)
);
}
$wp
=
$this
->
wikiPageFactory
->
newFromTitle
(
$this
->
getSourcePage
()
);
foreach
(
$insertedLinks
as
$catName
)
{
$cat
=
Category
::
newFromName
(
$catName
);
$this
->
hookRunner
->
onCategoryAfterPageAdded
(
$cat
,
$wp
);
}
foreach
(
$deletedLinks
as
$catName
)
{
$cat
=
Category
::
newFromName
(
$catName
);
$this
->
hookRunner
->
onCategoryAfterPageRemoved
(
$cat
,
$wp
,
$this
->
getSourcePage
()->
getId
()
);
}
}
private
function
invalidateCategories
(
array
$insertedLinks
,
array
$deletedLinks
)
{
$changedCategoryNames
=
array_merge
(
$insertedLinks
,
$deletedLinks
);
PurgeJobUtils
::
invalidatePages
(
$this
->
getDB
(),
NS_CATEGORY
,
$changedCategoryNames
);
}
protected
function
linksTargetNormalizationStage
():
int
{
return
SCHEMA_COMPAT_NEW
;
}
/** @inheritDoc */
protected
function
virtualDomain
():
string
{
return
self
::
VIRTUAL_DOMAIN
;
}
protected
function
fetchExistingRows
():
IResultWrapper
{
return
$this
->
getDB
()->
newSelectQueryBuilder
()
->
select
(
$this
->
getExistingFields
()
)
->
from
(
$this
->
getTableName
()
)
->
join
(
'linktarget'
,
null
,
[
'cl_target_id=lt_id'
]
)
->
where
(
$this
->
getFromConds
()
)
->
caller
(
__METHOD__
)
->
fetchResultSet
();
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Wed, Aug 19, 02:58 (2 w, 1 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
da/06/5cfe08e133f16ecb242e3cb620fa
Default Alt Text
CategoryLinksTable.php (11 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment