Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 86 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
0.00% |
0 / 86 |
|
0.00% |
0 / 13 |
1406 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
onParserFirstCallInit | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
parserFunction | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
30 | |||
getCategorySidebarBox | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
onSkinBuildSidebar | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
onSkinAfterPortlet | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
parserHook | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
42 | |||
onOutputPageMakeCategoryLinks | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
getDataForJs | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
onSpecialTrackingCategories__preprocess | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
onSpecialTrackingCategories__generateCatLink | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
onCategoryViewer__doCategoryQuery | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
onCategoryViewer__generateLink | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | /** |
3 | * © 2006-2008 Daniel Kinzler and others |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @ingroup Extensions |
22 | * @author Daniel Kinzler, brightbyte.de |
23 | */ |
24 | |
25 | namespace MediaWiki\Extension\CategoryTree; |
26 | |
27 | use MediaWiki\Config\Config; |
28 | use MediaWiki\Hook\CategoryViewer__doCategoryQueryHook; |
29 | use MediaWiki\Hook\CategoryViewer__generateLinkHook; |
30 | use MediaWiki\Hook\OutputPageMakeCategoryLinksHook; |
31 | use MediaWiki\Hook\ParserFirstCallInitHook; |
32 | use MediaWiki\Hook\SkinBuildSidebarHook; |
33 | use MediaWiki\Hook\SpecialTrackingCategories__generateCatLinkHook; |
34 | use MediaWiki\Hook\SpecialTrackingCategories__preprocessHook; |
35 | use MediaWiki\Html\Html; |
36 | use MediaWiki\Linker\LinkRenderer; |
37 | use MediaWiki\Linker\LinkTarget; |
38 | use MediaWiki\Output\OutputPage; |
39 | use MediaWiki\Parser\Sanitizer; |
40 | use MediaWiki\ResourceLoader as RL; |
41 | use MediaWiki\SpecialPage\SpecialPage; |
42 | use MediaWiki\Title\Title; |
43 | use Parser; |
44 | use PPFrame; |
45 | use RequestContext; |
46 | use Skin; |
47 | use Wikimedia\Rdbms\IConnectionProvider; |
48 | use Wikimedia\Rdbms\IResultWrapper; |
49 | |
50 | /** |
51 | * Hooks for the CategoryTree extension, an AJAX based gadget |
52 | * to display the category structure of a wiki |
53 | * |
54 | * @phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName |
55 | */ |
56 | class Hooks implements |
57 | SpecialTrackingCategories__preprocessHook, |
58 | SpecialTrackingCategories__generateCatLinkHook, |
59 | SkinBuildSidebarHook, |
60 | ParserFirstCallInitHook, |
61 | OutputPageMakeCategoryLinksHook, |
62 | CategoryViewer__doCategoryQueryHook, |
63 | CategoryViewer__generateLinkHook |
64 | { |
65 | |
66 | /** @var CategoryCache */ |
67 | private $categoryCache; |
68 | |
69 | /** @var Config */ |
70 | private $config; |
71 | |
72 | /** @var IConnectionProvider */ |
73 | private IConnectionProvider $dbProvider; |
74 | |
75 | /** @var LinkRenderer */ |
76 | private $linkRenderer; |
77 | |
78 | /** |
79 | * @param CategoryCache $categoryCache |
80 | * @param Config $config |
81 | * @param IConnectionProvider $dbProvider |
82 | * @param LinkRenderer $linkRenderer |
83 | */ |
84 | public function __construct( |
85 | CategoryCache $categoryCache, |
86 | Config $config, |
87 | IConnectionProvider $dbProvider, |
88 | LinkRenderer $linkRenderer |
89 | ) { |
90 | $this->categoryCache = $categoryCache; |
91 | $this->config = $config; |
92 | $this->dbProvider = $dbProvider; |
93 | $this->linkRenderer = $linkRenderer; |
94 | } |
95 | |
96 | /** |
97 | * @param Parser $parser |
98 | */ |
99 | public function onParserFirstCallInit( $parser ) { |
100 | if ( !$this->config->get( 'CategoryTreeAllowTag' ) ) { |
101 | return; |
102 | } |
103 | $parser->setHook( 'categorytree', [ $this, 'parserHook' ] ); |
104 | $parser->setFunctionHook( 'categorytree', [ $this, 'parserFunction' ] ); |
105 | } |
106 | |
107 | /** |
108 | * Entry point for the {{#categorytree}} tag parser function. |
109 | * This is a wrapper around Hooks::parserHook |
110 | * @param Parser $parser |
111 | * @param string ...$params |
112 | * @return array|string |
113 | */ |
114 | public function parserFunction( Parser $parser, ...$params ) { |
115 | // first user-supplied parameter must be category name |
116 | if ( !$params ) { |
117 | // no category specified, return nothing |
118 | return ''; |
119 | } |
120 | $cat = array_shift( $params ); |
121 | |
122 | // build associative arguments from flat parameter list |
123 | $argv = []; |
124 | foreach ( $params as $p ) { |
125 | if ( preg_match( '/^\s*(\S.*?)\s*=\s*(.*?)\s*$/', $p, $m ) ) { |
126 | $k = $m[1]; |
127 | // strip any quotes enclosing the value |
128 | $v = preg_replace( '/^"\s*(.*?)\s*"$/', '$1', $m[2] ); |
129 | } else { |
130 | $k = trim( $p ); |
131 | $v = true; |
132 | } |
133 | |
134 | $argv[$k] = $v; |
135 | } |
136 | |
137 | if ( $parser->getOutputType() === Parser::OT_PREPROCESS ) { |
138 | return Html::rawElement( 'categorytree', $argv, $cat ); |
139 | } else { |
140 | // now handle just like a <categorytree> tag |
141 | $html = $this->parserHook( $cat, $argv, $parser ); |
142 | return [ $html, 'noparse' => true, 'isHTML' => true ]; |
143 | } |
144 | } |
145 | |
146 | /** |
147 | * Obtain a category sidebar link based on config |
148 | * @return bool|string of link |
149 | */ |
150 | private function getCategorySidebarBox() { |
151 | if ( !$this->config->get( 'CategoryTreeSidebarRoot' ) ) { |
152 | return false; |
153 | } |
154 | return $this->parserHook( |
155 | $this->config->get( 'CategoryTreeSidebarRoot' ), |
156 | $this->config->get( 'CategoryTreeSidebarOptions' ) |
157 | ); |
158 | } |
159 | |
160 | /** |
161 | * Hook implementation for injecting a category tree into the sidebar. |
162 | * Only does anything if $wgCategoryTreeSidebarRoot is set to a category name. |
163 | * @param Skin $skin |
164 | * @param array &$sidebar |
165 | */ |
166 | public function onSkinBuildSidebar( $skin, &$sidebar ) { |
167 | $html = $this->getCategorySidebarBox(); |
168 | if ( $html ) { |
169 | $sidebar['categorytree-portlet'] = []; |
170 | CategoryTree::setHeaders( $skin->getOutput() ); |
171 | } |
172 | } |
173 | |
174 | /** |
175 | * Hook implementation for injecting a category tree link into the sidebar. |
176 | * Only does anything if $wgCategoryTreeSidebarRoot is set to a category name. |
177 | * @param Skin $skin |
178 | * @param string $portlet |
179 | * @param string &$html |
180 | */ |
181 | public function onSkinAfterPortlet( $skin, $portlet, &$html ) { |
182 | if ( $portlet === 'categorytree-portlet' ) { |
183 | $box = $this->getCategorySidebarBox(); |
184 | if ( $box ) { |
185 | $html .= $box; |
186 | } |
187 | } |
188 | } |
189 | |
190 | /** |
191 | * Entry point for the <categorytree> tag parser hook. |
192 | * This loads CategoryTreeFunctions.php and calls CategoryTree::getTag() |
193 | * @param string $cat |
194 | * @param array $argv |
195 | * @param Parser|null $parser |
196 | * @param PPFrame|null $frame |
197 | * @param bool $allowMissing |
198 | * @return bool|string |
199 | */ |
200 | public function parserHook( |
201 | $cat, |
202 | array $argv, |
203 | Parser $parser = null, |
204 | PPFrame $frame = null, |
205 | $allowMissing = false |
206 | ) { |
207 | if ( $parser ) { |
208 | $parserOutput = $parser->getOutput(); |
209 | $parserOutput->addModuleStyles( [ 'ext.categoryTree.styles' ] ); |
210 | $parserOutput->addModules( [ 'ext.categoryTree' ] ); |
211 | } |
212 | |
213 | $ct = new CategoryTree( $argv, $this->config, $this->dbProvider, $this->linkRenderer ); |
214 | |
215 | $attr = Sanitizer::validateTagAttributes( $argv, 'div' ); |
216 | |
217 | $hideroot = isset( $argv['hideroot'] ) |
218 | ? OptionManager::decodeBoolean( $argv['hideroot'] ) : null; |
219 | $onlyroot = isset( $argv['onlyroot'] ) |
220 | ? OptionManager::decodeBoolean( $argv['onlyroot'] ) : null; |
221 | $depthArg = isset( $argv['depth'] ) ? (int)$argv['depth'] : null; |
222 | |
223 | $depth = $ct->optionManager->capDepth( $depthArg ); |
224 | if ( $onlyroot ) { |
225 | $depth = 0; |
226 | } |
227 | |
228 | return $ct->getTag( $parser, $cat, $hideroot, $attr, $depth, $allowMissing ); |
229 | } |
230 | |
231 | /** |
232 | * OutputPageMakeCategoryLinks hook, override category links |
233 | * @param OutputPage $out |
234 | * @param array $categories |
235 | * @param array &$links |
236 | * @return bool |
237 | */ |
238 | public function onOutputPageMakeCategoryLinks( $out, $categories, &$links ) { |
239 | if ( !$this->config->get( 'CategoryTreeHijackPageCategories' ) ) { |
240 | // Not enabled, don't do anything |
241 | return true; |
242 | } |
243 | |
244 | $options = $this->config->get( 'CategoryTreePageCategoryOptions' ); |
245 | foreach ( $categories as $category => $type ) { |
246 | $links[$type][] = $this->parserHook( $category, $options, null, null, true ); |
247 | } |
248 | CategoryTree::setHeaders( $out ); |
249 | |
250 | return false; |
251 | } |
252 | |
253 | /** |
254 | * Get exported data for the "ext.categoryTree" ResourceLoader module. |
255 | * |
256 | * @internal For use in extension.json only. |
257 | * @param RL\Context $context |
258 | * @param Config $config |
259 | * @return array Data to be serialised as data.json |
260 | */ |
261 | public static function getDataForJs( RL\Context $context, Config $config ) { |
262 | // Look, this is pretty bad but CategoryTree is just whacky, it needs to be rewritten |
263 | $optionManager = new OptionManager( $config->get( 'CategoryTreeCategoryPageOptions' ), $config ); |
264 | |
265 | return [ |
266 | 'defaultCtOptions' => $optionManager->getOptionsAsJsStructure(), |
267 | ]; |
268 | } |
269 | |
270 | /** |
271 | * Hook handler for the SpecialTrackingCategories::preprocess hook |
272 | * @param SpecialPage $specialPage SpecialTrackingCategories object |
273 | * @param array $trackingCategories [ 'msg' => LinkTarget, 'cats' => LinkTarget[] ] |
274 | * @phan-param array<string,array{msg:LinkTarget,cats:LinkTarget[]}> $trackingCategories |
275 | */ |
276 | public function onSpecialTrackingCategories__preprocess( |
277 | $specialPage, |
278 | $trackingCategories |
279 | ) { |
280 | $categoryTargets = []; |
281 | foreach ( $trackingCategories as $data ) { |
282 | foreach ( $data['cats'] as $catTitle ) { |
283 | $categoryTargets[] = $catTitle; |
284 | } |
285 | } |
286 | $this->categoryCache->doQuery( $categoryTargets ); |
287 | } |
288 | |
289 | /** |
290 | * Hook handler for the SpecialTrackingCategories::generateCatLink hook |
291 | * @param SpecialPage $specialPage SpecialTrackingCategories object |
292 | * @param LinkTarget $catTitle LinkTarget object of the linked category |
293 | * @param string &$html Result html |
294 | */ |
295 | public function onSpecialTrackingCategories__generateCatLink( $specialPage, |
296 | $catTitle, &$html |
297 | ) { |
298 | $cat = $this->categoryCache->getCategory( $catTitle ); |
299 | |
300 | $html .= CategoryTree::createCountString( $specialPage->getContext(), $cat, 0 ); |
301 | } |
302 | |
303 | /** |
304 | * @param string $type |
305 | * @param IResultWrapper $res |
306 | */ |
307 | public function onCategoryViewer__doCategoryQuery( $type, $res ) { |
308 | if ( $type === 'subcat' && $res ) { |
309 | $this->categoryCache->fillFromQuery( $res ); |
310 | CategoryTree::setHeaders( RequestContext::getMain()->getOutput() ); |
311 | } |
312 | } |
313 | |
314 | /** |
315 | * @param string $type |
316 | * @param Title $title |
317 | * @param string $html |
318 | * @param string &$link |
319 | * @return bool |
320 | */ |
321 | public function onCategoryViewer__generateLink( $type, $title, $html, &$link ) { |
322 | if ( $type !== 'subcat' || $link !== null ) { |
323 | return true; |
324 | } |
325 | |
326 | $request = RequestContext::getMain()->getRequest(); |
327 | if ( $request->getCheck( 'notree' ) ) { |
328 | return true; |
329 | } |
330 | |
331 | $options = $this->config->get( 'CategoryTreeCategoryPageOptions' ); |
332 | $mode = $request->getRawVal( 'mode' ); |
333 | if ( $mode !== null ) { |
334 | $options['mode'] = $mode; |
335 | } |
336 | $tree = new CategoryTree( $options, $this->config, $this->dbProvider, $this->linkRenderer ); |
337 | |
338 | $cat = $this->categoryCache->getCategory( $title ); |
339 | |
340 | $link = $tree->renderNodeInfo( $title, $cat ); |
341 | return false; |
342 | } |
343 | } |