Update git submodules
[mediawiki.git] / includes / Category / TrackingCategories.php
blob87e4e8496e8818aa1bc632f786e4e53bfb1675d3
1 <?php
2 /**
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
18 * @file
19 * @ingroup Categories
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;
32 use ParserOutput;
33 use Psr\Log\LoggerInterface;
35 /**
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
38 * such categories.
39 * @since 1.29
41 class TrackingCategories {
43 /**
44 * @internal For use by ServiceWiring
46 public const CONSTRUCTOR_OPTIONS = [
47 MainConfigNames::TrackingCategories,
48 MainConfigNames::EnableMagicLinks,
51 /** @var ServiceOptions */
52 private $options;
54 /** @var NamespaceInfo */
55 private $namespaceInfo;
57 /** @var TitleParser */
58 private $titleParser;
60 /** @var ExtensionRegistry */
61 private $extensionRegistry;
63 /** @var LoggerInterface */
64 private $logger;
66 /**
67 * Tracking categories that exist in core
69 * @var array
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',
77 'index-category',
78 'node-count-exceeded-category',
79 'noindex-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',
93 /**
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();
154 $allCats = [];
155 $catMsgTitle = $this->titleParser->makeTitleValueSafe( NS_MEDIAWIKI, $catMsg );
156 if ( !$catMsgTitle ) {
157 continue;
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 );
166 if ( !$tempTitle ) {
167 continue;
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 );
176 if ( $catTitle ) {
177 $allCats[] = $catTitle;
181 } else {
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 );
186 if ( $catTitle ) {
187 $allCats[] = $catTitle;
191 $trackingCategories[$catMsg] = [
192 'cats' => $allCats,
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
206 * @since 1.38
208 public function resolveTrackingCategory( string $msg, ?PageReference $contextPage ): ?LinkTarget {
209 if ( !$contextPage ) {
210 $this->logger->debug( "Not adding tracking category $msg to missing page!" );
211 return null;
214 if ( $contextPage->getNamespace() === NS_SPECIAL ) {
215 $this->logger->debug( "Not adding tracking category $msg to special page!" );
216 return null;
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()
224 ->text();
226 # Allow tracking categories to be disabled by setting them to "-"
227 if ( $cat === '-' ) {
228 return null;
231 $containerCategory = $this->titleParser->makeTitleValueSafe( NS_CATEGORY, $cat );
232 if ( $containerCategory === null ) {
233 $this->logger->debug( "[[MediaWiki:$msg]] is not a valid title!" );
234 return null;
236 return $containerCategory;
240 * Add a tracking category to a ParserOutput, getting the title from a
241 * system message.
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
253 * @since 1.38
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 ) {
259 return false;
261 $parserOutput->addCategory(
262 $categoryPage->getDBkey(),
263 $parserOutput->getPageProperty( 'defaultsort' ) ?? ''
265 return true;
270 * @deprecated since 1.40
272 class_alias( TrackingCategories::class, 'TrackingCategories' );