Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 125 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
OptionManager | |
0.00% |
0 / 125 |
|
0.00% |
0 / 12 |
6320 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
56 | |||
getOptions | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getOption | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isInverse | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
decodeNamespaces | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
182 | |||
decodeMode | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
156 | |||
decodeBoolean | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
110 | |||
decodeHidePrefix | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
462 | |||
encodeOptions | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
getOptionsAsCacheKey | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
getOptionsAsJsStructure | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
capDepth | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * © 2006-2007 Daniel Kinzler |
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 InvalidArgumentException; |
28 | use MediaWiki\Config\Config; |
29 | use MediaWiki\Json\FormatJson; |
30 | use MediaWiki\MediaWikiServices; |
31 | |
32 | /** |
33 | * Core functions to handle the options |
34 | */ |
35 | class OptionManager { |
36 | private array $mOptions = []; |
37 | private Config $config; |
38 | |
39 | public function __construct( array $options, Config $config ) { |
40 | $this->config = $config; |
41 | |
42 | // ensure default values and order of options. |
43 | // Order may become important, it may influence the cache key! |
44 | foreach ( $config->get( 'CategoryTreeDefaultOptions' ) as $option => $default ) { |
45 | $this->mOptions[$option] = $options[$option] ?? $default; |
46 | } |
47 | |
48 | $this->mOptions['mode'] = $this->decodeMode( $this->mOptions['mode'] ); |
49 | |
50 | if ( $this->mOptions['mode'] === CategoryTreeMode::PARENTS ) { |
51 | // namespace filter makes no sense with CategoryTreeMode::PARENTS |
52 | $this->mOptions['namespaces'] = false; |
53 | } |
54 | |
55 | $this->mOptions['hideprefix'] = $this->decodeHidePrefix( $this->mOptions['hideprefix'] ); |
56 | $this->mOptions['showcount'] = self::decodeBoolean( $this->mOptions['showcount'] ); |
57 | $this->mOptions['namespaces'] = self::decodeNamespaces( $this->mOptions['namespaces'] ); |
58 | |
59 | if ( $this->mOptions['namespaces'] ) { |
60 | # automatically adjust mode to match namespace filter |
61 | if ( count( $this->mOptions['namespaces'] ) === 1 |
62 | && $this->mOptions['namespaces'][0] === NS_CATEGORY ) { |
63 | $this->mOptions['mode'] = CategoryTreeMode::CATEGORIES; |
64 | } elseif ( !in_array( NS_FILE, $this->mOptions['namespaces'] ) ) { |
65 | $this->mOptions['mode'] = CategoryTreeMode::PAGES; |
66 | } else { |
67 | $this->mOptions['mode'] = CategoryTreeMode::ALL; |
68 | } |
69 | } |
70 | } |
71 | |
72 | public function getOptions(): array { |
73 | return $this->mOptions; |
74 | } |
75 | |
76 | /** |
77 | * @param string $name |
78 | * @return mixed |
79 | */ |
80 | public function getOption( string $name ) { |
81 | return $this->mOptions[$name]; |
82 | } |
83 | |
84 | public function isInverse(): bool { |
85 | return $this->getOption( 'mode' ) === CategoryTreeMode::PARENTS; |
86 | } |
87 | |
88 | /** |
89 | * @param mixed $nn |
90 | * @return array|bool |
91 | */ |
92 | private static function decodeNamespaces( $nn ) { |
93 | if ( $nn === false || $nn === null ) { |
94 | return false; |
95 | } |
96 | |
97 | if ( !is_array( $nn ) ) { |
98 | $nn = preg_split( '![\s#:|]+!', $nn ); |
99 | } |
100 | |
101 | $namespaces = []; |
102 | $contLang = MediaWikiServices::getInstance()->getContentLanguage(); |
103 | foreach ( $nn as $n ) { |
104 | if ( is_int( $n ) ) { |
105 | $ns = $n; |
106 | } else { |
107 | $n = trim( $n ); |
108 | if ( $n === '' ) { |
109 | continue; |
110 | } |
111 | |
112 | $lower = strtolower( $n ); |
113 | |
114 | if ( is_numeric( $n ) ) { |
115 | $ns = (int)$n; |
116 | } elseif ( $n === '-' || $n === '_' || $n === '*' || $lower === 'main' ) { |
117 | $ns = NS_MAIN; |
118 | } else { |
119 | $ns = $contLang->getNsIndex( $n ); |
120 | } |
121 | } |
122 | |
123 | if ( is_int( $ns ) ) { |
124 | $namespaces[] = $ns; |
125 | } |
126 | } |
127 | |
128 | # get elements into canonical order |
129 | sort( $namespaces ); |
130 | return $namespaces; |
131 | } |
132 | |
133 | /** |
134 | * @param mixed $mode |
135 | * @return int|string |
136 | */ |
137 | private function decodeMode( $mode ) { |
138 | $defaultOptions = $this->config->get( 'CategoryTreeDefaultOptions' ); |
139 | |
140 | if ( $mode === null ) { |
141 | return $defaultOptions['mode']; |
142 | } |
143 | if ( is_int( $mode ) ) { |
144 | return $mode; |
145 | } |
146 | |
147 | $mode = trim( strtolower( $mode ) ); |
148 | |
149 | if ( is_numeric( $mode ) ) { |
150 | return (int)$mode; |
151 | } |
152 | |
153 | if ( $mode === 'all' ) { |
154 | $mode = CategoryTreeMode::ALL; |
155 | } elseif ( $mode === 'pages' ) { |
156 | $mode = CategoryTreeMode::PAGES; |
157 | } elseif ( $mode === 'categories' || $mode === 'sub' ) { |
158 | $mode = CategoryTreeMode::CATEGORIES; |
159 | } elseif ( $mode === 'parents' || $mode === 'super' || $mode === 'inverse' ) { |
160 | $mode = CategoryTreeMode::PARENTS; |
161 | } elseif ( $mode === 'default' ) { |
162 | $mode = $defaultOptions['mode']; |
163 | } |
164 | |
165 | return (int)$mode; |
166 | } |
167 | |
168 | /** |
169 | * Helper function to convert a string to a boolean value. |
170 | * Perhaps make this a global function in MediaWiki proper |
171 | * @param mixed $value |
172 | * @return bool |
173 | */ |
174 | public static function decodeBoolean( $value ): bool { |
175 | if ( $value === null ) { |
176 | return false; |
177 | } |
178 | if ( is_bool( $value ) ) { |
179 | return $value; |
180 | } |
181 | if ( is_int( $value ) ) { |
182 | return ( $value > 0 ); |
183 | } |
184 | |
185 | $value = trim( strtolower( $value ) ); |
186 | if ( is_numeric( $value ) ) { |
187 | return ( (int)$value > 0 ); |
188 | } |
189 | |
190 | if ( $value === 'yes' || $value === 'y' |
191 | || $value === 'true' || $value === 't' || $value === 'on' |
192 | ) { |
193 | return true; |
194 | } else { |
195 | return false; |
196 | } |
197 | } |
198 | |
199 | /** |
200 | * @param mixed $value |
201 | * @return int|string |
202 | */ |
203 | private function decodeHidePrefix( $value ) { |
204 | $defaultOptions = $this->config->get( 'CategoryTreeDefaultOptions' ); |
205 | |
206 | if ( $value === null ) { |
207 | return $defaultOptions['hideprefix']; |
208 | } |
209 | if ( is_int( $value ) ) { |
210 | return $value; |
211 | } |
212 | if ( $value === true ) { |
213 | return CategoryTreeHidePrefix::ALWAYS; |
214 | } |
215 | if ( $value === false ) { |
216 | return CategoryTreeHidePrefix::NEVER; |
217 | } |
218 | |
219 | $value = trim( strtolower( $value ) ); |
220 | |
221 | if ( $value === 'yes' || $value === 'y' |
222 | || $value === 'true' || $value === 't' || $value === 'on' |
223 | ) { |
224 | return CategoryTreeHidePrefix::ALWAYS; |
225 | } elseif ( $value === 'no' || $value === 'n' |
226 | || $value === 'false' || $value === 'f' || $value === 'off' |
227 | ) { |
228 | return CategoryTreeHidePrefix::NEVER; |
229 | } elseif ( $value === 'always' ) { |
230 | return CategoryTreeHidePrefix::ALWAYS; |
231 | } elseif ( $value === 'never' ) { |
232 | return CategoryTreeHidePrefix::NEVER; |
233 | } elseif ( $value === 'auto' ) { |
234 | return CategoryTreeHidePrefix::AUTO; |
235 | } elseif ( $value === 'categories' || $value === 'category' || $value === 'smart' ) { |
236 | return CategoryTreeHidePrefix::CATEGORIES; |
237 | } else { |
238 | return $defaultOptions['hideprefix']; |
239 | } |
240 | } |
241 | |
242 | /** |
243 | * @param array $options |
244 | * @param string $enc |
245 | * @return mixed |
246 | */ |
247 | private static function encodeOptions( array $options, string $enc ) { |
248 | if ( $enc === 'mode' || $enc === '' ) { |
249 | $opt = $options['mode']; |
250 | } elseif ( $enc === 'json' ) { |
251 | $opt = FormatJson::encode( $options ); |
252 | } else { |
253 | throw new InvalidArgumentException( 'Unknown encoding for CategoryTree options: ' . $enc ); |
254 | } |
255 | |
256 | return $opt; |
257 | } |
258 | |
259 | /** |
260 | * @param int|null $depth |
261 | * @return string |
262 | */ |
263 | public function getOptionsAsCacheKey( ?int $depth = null ): string { |
264 | $key = ''; |
265 | |
266 | foreach ( $this->mOptions as $k => $v ) { |
267 | if ( is_array( $v ) ) { |
268 | $v = implode( '|', $v ); |
269 | } |
270 | $key .= $k . ':' . $v . ';'; |
271 | } |
272 | |
273 | if ( $depth !== null ) { |
274 | $key .= ';depth=' . $depth; |
275 | } |
276 | return $key; |
277 | } |
278 | |
279 | /** |
280 | * @param int|null $depth |
281 | * @return mixed |
282 | */ |
283 | public function getOptionsAsJsStructure( ?int $depth = null ) { |
284 | $opt = $this->mOptions; |
285 | if ( $depth !== null ) { |
286 | $opt['depth'] = $depth; |
287 | } |
288 | |
289 | return self::encodeOptions( $opt, 'json' ); |
290 | } |
291 | |
292 | /** |
293 | * Internal function to cap depth |
294 | * @param int $depth |
295 | * @return int |
296 | */ |
297 | public function capDepth( int $depth ): int { |
298 | $mode = $this->getOption( 'mode' ); |
299 | $maxDepth = $this->config->get( 'CategoryTreeMaxDepth' ); |
300 | |
301 | if ( is_array( $maxDepth ) ) { |
302 | $max = $maxDepth[$mode] ?? 1; |
303 | } elseif ( is_numeric( $maxDepth ) ) { |
304 | $max = $maxDepth; |
305 | } else { |
306 | wfDebug( __METHOD__ . ': $wgCategoryTreeMaxDepth is invalid.' ); |
307 | $max = 1; |
308 | } |
309 | |
310 | return min( $depth, $max ); |
311 | } |
312 | } |