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 use MediaWiki\Config\ServiceOptions
;
23 use MediaWiki\Linker\LinkTarget
;
24 use MediaWiki\MainConfigNames
;
25 use MediaWiki\Page\PageReference
;
26 use Psr\Log\LoggerInterface
;
29 * This class performs some operations related to tracking categories, such as creating
30 * a list of all such categories.
33 class TrackingCategories
{
36 * @internal For use by ServiceWiring
38 public const CONSTRUCTOR_OPTIONS
= [
39 MainConfigNames
::TrackingCategories
,
40 MainConfigNames
::EnableMagicLinks
,
43 /** @var ServiceOptions */
46 /** @var NamespaceInfo */
47 private $namespaceInfo;
49 /** @var TitleParser */
52 /** @var ExtensionRegistry */
53 private $extensionRegistry;
55 /** @var LoggerInterface */
59 * Tracking categories that exist in core
63 private const CORE_TRACKING_CATEGORIES
= [
64 'broken-file-category',
65 'duplicate-args-category',
66 'expansion-depth-exceeded-category',
67 'expensive-parserfunction-category',
68 'hidden-category-category',
70 'node-count-exceeded-category',
72 'nonnumeric-formatnum',
73 'post-expand-template-argument-category',
74 'post-expand-template-inclusion-category',
75 'restricted-displaytitle-ignored',
76 # template-equals-category is unused in MW>=1.39, but the category
77 # can be left around for a major release or so for an easier
78 # transition for anyone who didn't do the cleanup. T91154
79 'template-equals-category',
80 'template-loop-category',
81 'unstrip-depth-category',
82 'unstrip-size-category',
86 * @param ServiceOptions $options
87 * @param NamespaceInfo $namespaceInfo
88 * @param TitleParser $titleParser
89 * @param LoggerInterface $logger
91 public function __construct(
92 ServiceOptions
$options,
93 NamespaceInfo
$namespaceInfo,
94 TitleParser
$titleParser,
95 LoggerInterface
$logger
97 $options->assertRequiredOptions( self
::CONSTRUCTOR_OPTIONS
);
98 $this->options
= $options;
99 $this->namespaceInfo
= $namespaceInfo;
100 $this->titleParser
= $titleParser;
101 $this->logger
= $logger;
103 // TODO convert ExtensionRegistry to a service and inject it
104 $this->extensionRegistry
= ExtensionRegistry
::getInstance();
108 * Read the global and extract title objects from the corresponding messages
110 * TODO consider renaming this method, since this class is retrieved from
111 * MediaWikiServices, resulting in calls like:
112 * MediaWikiServices::getInstance()->getTrackingCategories()->getTrackingCategories()
114 * @return array[] [ 'msg' => Title, 'cats' => Title[] ]
115 * @phan-return array<string,array{msg:Title,cats:Title[]}>
117 public function getTrackingCategories() {
118 $categories = array_merge(
119 self
::CORE_TRACKING_CATEGORIES
,
120 $this->extensionRegistry
->getAttribute( MainConfigNames
::TrackingCategories
),
121 $this->options
->get( MainConfigNames
::TrackingCategories
) // deprecated
124 // Only show magic link tracking categories if they are enabled
125 $enableMagicLinks = $this->options
->get( MainConfigNames
::EnableMagicLinks
);
126 if ( $enableMagicLinks['ISBN'] ) {
127 $categories[] = 'magiclink-tracking-isbn';
129 if ( $enableMagicLinks['RFC'] ) {
130 $categories[] = 'magiclink-tracking-rfc';
132 if ( $enableMagicLinks['PMID'] ) {
133 $categories[] = 'magiclink-tracking-pmid';
136 $trackingCategories = [];
137 foreach ( $categories as $catMsg ) {
139 * Check if the tracking category varies by namespace
140 * Otherwise only pages in the current namespace will be displayed
141 * If it does vary, show pages considering all namespaces
143 * TODO replace uses of wfMessage with an injected service once that is available
145 $msgObj = wfMessage( $catMsg )->inContentLanguage();
147 $catMsgTitle = $this->titleParser
->makeTitleValueSafe( NS_MEDIAWIKI
, $catMsg );
148 if ( !$catMsgTitle ) {
152 // Match things like {{NAMESPACE}} and {{NAMESPACENUMBER}}.
153 // False positives are ok, this is just an efficiency shortcut
154 if ( strpos( $msgObj->plain(), '{{' ) !== false ) {
155 $ns = $this->namespaceInfo
->getValidNamespaces();
156 foreach ( $ns as $namesp ) {
157 $tempTitle = $this->titleParser
->makeTitleValueSafe( $namesp, $catMsg );
161 // XXX: should be a better way to convert a TitleValue
162 // to a PageReference!
163 $tempTitle = Title
::newFromLinkTarget( $tempTitle );
164 $catName = $msgObj->page( $tempTitle )->text();
165 # Allow tracking categories to be disabled by setting them to "-"
166 if ( $catName !== '-' ) {
167 $catTitle = $this->titleParser
->makeTitleValueSafe( NS_CATEGORY
, $catName );
169 $allCats[] = $catTitle;
174 $catName = $msgObj->text();
175 # Allow tracking categories to be disabled by setting them to "-"
176 if ( $catName !== '-' ) {
177 $catTitle = $this->titleParser
->makeTitleValueSafe( NS_CATEGORY
, $catName );
179 $allCats[] = $catTitle;
183 $trackingCategories[$catMsg] = [
185 'msg' => $catMsgTitle,
189 return $trackingCategories;
193 * Resolve a tracking category.
194 * @param string $msg Message key
195 * @param ?PageReference $contextPage Context page title
196 * @return ?LinkTarget the proper category page, or null if
197 * the tracking category is disabled or unsafe
200 public function resolveTrackingCategory( string $msg, ?PageReference
$contextPage ): ?LinkTarget
{
201 if ( !$contextPage ) {
202 $this->logger
->debug( "Not adding tracking category $msg to missing page!" );
206 if ( $contextPage->getNamespace() === NS_SPECIAL
) {
207 $this->logger
->debug( "Not adding tracking category $msg to special page!" );
211 // Important to parse with correct title (T33469)
212 // TODO replace uses of wfMessage with an injected service once that is available
213 $cat = wfMessage( $msg )
214 ->page( $contextPage )
215 ->inContentLanguage()
218 # Allow tracking categories to be disabled by setting them to "-"
219 if ( $cat === '-' ) {
223 $containerCategory = $this->titleParser
->makeTitleValueSafe( NS_CATEGORY
, $cat );
224 if ( $containerCategory === null ) {
225 $this->logger
->debug( "[[MediaWiki:$msg]] is not a valid title!" );
228 return $containerCategory;
232 * Add a tracking category to a ParserOutput.
233 * @param ParserOutput $parserOutput
234 * @param string $msg Message key
235 * @param ?PageReference $contextPage Context page title
236 * @return bool Whether the addition was successful
239 public function addTrackingCategory( ParserOutput
$parserOutput, string $msg, ?PageReference
$contextPage ): bool {
240 $categoryPage = $this->resolveTrackingCategory( $msg, $contextPage );
241 if ( $categoryPage === null ) {
244 $parserOutput->addCategory(
245 $categoryPage->getDBkey(),
246 $parserOutput->getPageProperty( 'defaultsort' ) ??
''