3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
22 namespace MediaWiki\Category
;
24 use ExtensionRegistry
;
25 use MediaWiki\Config\ServiceOptions
;
26 use MediaWiki\Linker\LinkTarget
;
27 use MediaWiki\MainConfigNames
;
28 use MediaWiki\Page\PageReference
;
29 use MediaWiki\Title\NamespaceInfo
;
30 use MediaWiki\Title\Title
;
31 use MediaWiki\Title\TitleParser
;
33 use Psr\Log\LoggerInterface
;
36 * This class performs some operations related to tracking categories, such as
37 * adding a tracking category to a ParserOutput, and creating a list of all
41 class TrackingCategories
{
44 * @internal For use by ServiceWiring
46 public const CONSTRUCTOR_OPTIONS
= [
47 MainConfigNames
::TrackingCategories
,
48 MainConfigNames
::EnableMagicLinks
,
51 /** @var ServiceOptions */
54 /** @var NamespaceInfo */
55 private $namespaceInfo;
57 /** @var TitleParser */
60 /** @var ExtensionRegistry */
61 private $extensionRegistry;
63 /** @var LoggerInterface */
67 * Tracking categories that exist in core
71 private const CORE_TRACKING_CATEGORIES
= [
72 'broken-file-category',
73 'duplicate-args-category',
74 'expansion-depth-exceeded-category',
75 'expensive-parserfunction-category',
76 'hidden-category-category',
78 'node-count-exceeded-category',
80 'nonnumeric-formatnum',
81 'post-expand-template-argument-category',
82 'post-expand-template-inclusion-category',
83 'restricted-displaytitle-ignored',
84 # template-equals-category is unused in MW>=1.39, but the category
85 # can be left around for a major release or so for an easier
86 # transition for anyone who didn't do the cleanup. T91154
87 'template-equals-category',
88 'template-loop-category',
89 'unstrip-depth-category',
90 'unstrip-size-category',
94 * @param ServiceOptions $options
95 * @param NamespaceInfo $namespaceInfo
96 * @param TitleParser $titleParser
97 * @param LoggerInterface $logger
99 public function __construct(
100 ServiceOptions
$options,
101 NamespaceInfo
$namespaceInfo,
102 TitleParser
$titleParser,
103 LoggerInterface
$logger
105 $options->assertRequiredOptions( self
::CONSTRUCTOR_OPTIONS
);
106 $this->options
= $options;
107 $this->namespaceInfo
= $namespaceInfo;
108 $this->titleParser
= $titleParser;
109 $this->logger
= $logger;
111 // TODO convert ExtensionRegistry to a service and inject it
112 $this->extensionRegistry
= ExtensionRegistry
::getInstance();
116 * Read the global and extract title objects from the corresponding messages
118 * TODO consider renaming this method, since this class is retrieved from
119 * MediaWikiServices, resulting in calls like:
120 * MediaWikiServices::getInstance()->getTrackingCategories()->getTrackingCategories()
122 * @return array[] [ 'msg' => LinkTarget, 'cats' => LinkTarget[] ]
123 * @phan-return array<string,array{msg:LinkTarget,cats:LinkTarget[]}>
125 public function getTrackingCategories() {
126 $categories = array_merge(
127 self
::CORE_TRACKING_CATEGORIES
,
128 $this->extensionRegistry
->getAttribute( MainConfigNames
::TrackingCategories
),
129 $this->options
->get( MainConfigNames
::TrackingCategories
) // deprecated
132 // Only show magic link tracking categories if they are enabled
133 $enableMagicLinks = $this->options
->get( MainConfigNames
::EnableMagicLinks
);
134 if ( $enableMagicLinks['ISBN'] ) {
135 $categories[] = 'magiclink-tracking-isbn';
137 if ( $enableMagicLinks['RFC'] ) {
138 $categories[] = 'magiclink-tracking-rfc';
140 if ( $enableMagicLinks['PMID'] ) {
141 $categories[] = 'magiclink-tracking-pmid';
144 $trackingCategories = [];
145 foreach ( $categories as $catMsg ) {
147 * Check if the tracking category varies by namespace
148 * Otherwise only pages in the current namespace will be displayed
149 * If it does vary, show pages considering all namespaces
151 * TODO replace uses of wfMessage with an injected service once that is available
153 $msgObj = wfMessage( $catMsg )->inContentLanguage();
155 $catMsgTitle = $this->titleParser
->makeTitleValueSafe( NS_MEDIAWIKI
, $catMsg );
156 if ( !$catMsgTitle ) {
160 // Match things like {{NAMESPACE}} and {{NAMESPACENUMBER}}.
161 // False positives are ok, this is just an efficiency shortcut
162 if ( strpos( $msgObj->plain(), '{{' ) !== false ) {
163 $ns = $this->namespaceInfo
->getValidNamespaces();
164 foreach ( $ns as $namesp ) {
165 $tempTitle = $this->titleParser
->makeTitleValueSafe( $namesp, $catMsg );
169 // XXX: should be a better way to convert a TitleValue
170 // to a PageReference!
171 $tempTitle = Title
::newFromLinkTarget( $tempTitle );
172 $catName = $msgObj->page( $tempTitle )->text();
173 # Allow tracking categories to be disabled by setting them to "-"
174 if ( $catName !== '-' ) {
175 $catTitle = $this->titleParser
->makeTitleValueSafe( NS_CATEGORY
, $catName );
177 $allCats[] = $catTitle;
182 $catName = $msgObj->text();
183 # Allow tracking categories to be disabled by setting them to "-"
184 if ( $catName !== '-' ) {
185 $catTitle = $this->titleParser
->makeTitleValueSafe( NS_CATEGORY
, $catName );
187 $allCats[] = $catTitle;
191 $trackingCategories[$catMsg] = [
193 'msg' => $catMsgTitle,
197 return $trackingCategories;
201 * Resolve a tracking category.
202 * @param string $msg Message key
203 * @param ?PageReference $contextPage Context page title
204 * @return ?LinkTarget the proper category page, or null if
205 * the tracking category is disabled or unsafe
208 public function resolveTrackingCategory( string $msg, ?PageReference
$contextPage ): ?LinkTarget
{
209 if ( !$contextPage ) {
210 $this->logger
->debug( "Not adding tracking category $msg to missing page!" );
214 if ( $contextPage->getNamespace() === NS_SPECIAL
) {
215 $this->logger
->debug( "Not adding tracking category $msg to special page!" );
219 // Important to parse with correct title (T33469)
220 // TODO replace uses of wfMessage with an injected service once that is available
221 $cat = wfMessage( $msg )
222 ->page( $contextPage )
223 ->inContentLanguage()
226 # Allow tracking categories to be disabled by setting them to "-"
227 if ( $cat === '-' ) {
231 $containerCategory = $this->titleParser
->makeTitleValueSafe( NS_CATEGORY
, $cat );
232 if ( $containerCategory === null ) {
233 $this->logger
->debug( "[[MediaWiki:$msg]] is not a valid title!" );
236 return $containerCategory;
240 * Add a tracking category to a ParserOutput, getting the title from a
243 * Any message used with this function should be registered so it will
244 * show up on [[Special:TrackingCategories]]. Core messages should be
245 * added to TrackingCategories::CORE_TRACKING_CATEGORIES, and extensions
246 * should add to "TrackingCategories" in their extension.json.
248 * @param ParserOutput $parserOutput The target ParserOutput which will
249 * store the new category
250 * @param string $msg Message key
251 * @param ?PageReference $contextPage Context page title
252 * @return bool Whether the addition was successful
254 * @see Parser::addTrackingCategory
256 public function addTrackingCategory( ParserOutput
$parserOutput, string $msg, ?PageReference
$contextPage ): bool {
257 $categoryPage = $this->resolveTrackingCategory( $msg, $contextPage );
258 if ( $categoryPage === null ) {
261 $parserOutput->addCategory(
262 $categoryPage->getDBkey(),
263 $parserOutput->getPageProperty( 'defaultsort' ) ??
''
270 * @deprecated since 1.40
272 class_alias( TrackingCategories
::class, 'TrackingCategories' );