Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 165 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
PFMappingUtils | |
0.00% |
0 / 165 |
|
0.00% |
0 / 12 |
4692 | |
0.00% |
0 / 1 |
getMappingType | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
56 | |||
getMappedValuesForInput | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
isIndexedArray | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getMappedValues | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
110 | |||
getValuesWithMappingProperty | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
42 | |||
getValuesWithMappingTemplate | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
getValuesWithMappingCargoField | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
30 | |||
getValuesWithTranslateMapping | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getLabelsForTitles | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
132 | |||
getDisplayTitles | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
56 | |||
removeNSPrefixFromLabel | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
disambiguateLabels | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
72 |
1 | <?php |
2 | /** |
3 | * Methods for mapping values to labels |
4 | * @file |
5 | * @ingroup PF |
6 | */ |
7 | |
8 | use MediaWiki\MediaWikiServices; |
9 | |
10 | class PFMappingUtils { |
11 | |
12 | /** |
13 | * @param array $args |
14 | * @param bool $useDisplayTitle |
15 | * @return string|null |
16 | */ |
17 | public static function getMappingType( array $args, bool $useDisplayTitle = false ) { |
18 | $mappingType = null; |
19 | if ( array_key_exists( 'mapping property', $args ) ) { |
20 | $mappingType = 'mapping property'; |
21 | } elseif ( array_key_exists( 'mapping template', $args ) ) { |
22 | $mappingType = 'mapping template'; |
23 | } elseif ( array_key_exists( 'mapping cargo table', $args ) && |
24 | array_key_exists( 'mapping cargo field', $args ) ) { |
25 | // @todo: or use 'cargo field'? |
26 | $mappingType = 'mapping cargo field'; |
27 | } elseif ( array_key_exists( 'mapping using translate', $args ) ) { |
28 | $mappingType = 'mapping using translate'; |
29 | } elseif ( $useDisplayTitle ) { |
30 | $mappingType = 'displaytitle'; |
31 | } |
32 | return $mappingType; |
33 | } |
34 | |
35 | /** |
36 | * Map values if possible and return a named (associative) array |
37 | * @param array $values |
38 | * @param array $args |
39 | * @return array |
40 | */ |
41 | public static function getMappedValuesForInput( array $values, array $args = [] ) { |
42 | global $wgPageFormsUseDisplayTitle; |
43 | $mappingType = self::getMappingType( $args, $wgPageFormsUseDisplayTitle ); |
44 | if ( self::isIndexedArray( $values ) == false ) { |
45 | // already named associative |
46 | $pages = array_keys( $values ); |
47 | $values = self::getMappedValues( $pages, $mappingType, $args, $wgPageFormsUseDisplayTitle ); |
48 | $res = $values; |
49 | } elseif ( $mappingType !== null ) { |
50 | $res = self::getMappedValues( $values, $mappingType, $args, $wgPageFormsUseDisplayTitle ); |
51 | } else { |
52 | $res = []; |
53 | foreach ( $values as $key => $value ) { |
54 | $res[$value] = $value; |
55 | } |
56 | } |
57 | return $res; |
58 | } |
59 | |
60 | /** |
61 | * Check if array is indexed/sequential (true), else named/associative (false) |
62 | * @param array $arr |
63 | * @return string |
64 | */ |
65 | private static function isIndexedArray( $arr ) { |
66 | if ( array_keys( $arr ) == range( 0, count( $arr ) - 1 ) ) { |
67 | return true; |
68 | } else { |
69 | return false; |
70 | } |
71 | } |
72 | |
73 | /** |
74 | * Return named array of mapped values |
75 | * Static version of PF_FormField::setMappedValues |
76 | * @param array $values |
77 | * @param string|null $mappingType |
78 | * @param array $args |
79 | * @param bool $useDisplayTitle |
80 | * @return array |
81 | */ |
82 | public static function getMappedValues( |
83 | array $values, |
84 | ?string $mappingType, |
85 | array $args, |
86 | bool $useDisplayTitle |
87 | ) { |
88 | $mappedValues = null; |
89 | switch ( $mappingType ) { |
90 | case 'mapping property': |
91 | $mappingProperty = $args['mapping property']; |
92 | $mappedValues = self::getValuesWithMappingProperty( $values, $mappingProperty ); |
93 | break; |
94 | case 'mapping template': |
95 | $mappingTemplate = $args['mapping template']; |
96 | $mappedValues = self::getValuesWithMappingTemplate( $values, $mappingTemplate ); |
97 | break; |
98 | case 'mapping cargo field': |
99 | $mappingCargoField = isset( $args['mapping cargo field'] ) ? $args['mapping cargo field'] : null; |
100 | $mappingCargoValueField = isset( $args['mapping cargo value field'] ) ? $args['mapping cargo value field'] : null; |
101 | $mappingCargoTable = $args['mapping cargo table']; |
102 | $mappedValues = self::getValuesWithMappingCargoField( $values, $mappingCargoField, $mappingCargoValueField, $mappingCargoTable, $useDisplayTitle ); |
103 | break; |
104 | case 'mapping using translate': |
105 | $translateMapping = $args[ 'mapping using translate' ]; |
106 | $mappedValues = self::getValuesWithTranslateMapping( $values, $translateMapping ); |
107 | break; |
108 | case 'displaytitle': |
109 | $isReverseLookup = ( array_key_exists( 'reverselookup', $args ) && ( $args['reverselookup'] == 'true' ) ); |
110 | $mappedValues = self::getLabelsForTitles( $values, $isReverseLookup ); |
111 | // @todo - why just array_values ? |
112 | break; |
113 | } |
114 | $res = ( $mappedValues !== null ) ? self::disambiguateLabels( $mappedValues ) : $values; |
115 | return $res; |
116 | } |
117 | |
118 | /** |
119 | * Helper function to get a named array of labels from |
120 | * an indexed array of values given a mapping property. |
121 | * Originally in PF_FormField |
122 | * @param array $values |
123 | * @param string $propertyName |
124 | * @return array |
125 | */ |
126 | public static function getValuesWithMappingProperty( |
127 | array $values, |
128 | string $propertyName |
129 | ): array { |
130 | $store = PFUtils::getSMWStore(); |
131 | if ( $store == null || empty( $values ) ) { |
132 | return []; |
133 | } |
134 | $res = []; |
135 | foreach ( $values as $index => $value ) { |
136 | // @todo - does this make sense? |
137 | // if ( $useDisplayTitle ) { |
138 | // $value = $index; |
139 | // } |
140 | $subject = Title::newFromText( $value ); |
141 | if ( $subject != null ) { |
142 | $vals = PFValuesUtils::getSMWPropertyValues( $store, $subject, $propertyName ); |
143 | if ( count( $vals ) > 0 ) { |
144 | $res[$value] = trim( $vals[0] ); |
145 | } else { |
146 | // @todo - make this optional |
147 | $label = self::removeNSPrefixFromLabel( trim( $value ) ); |
148 | $res[$value] = $label; |
149 | } |
150 | } else { |
151 | $res[$value] = $value; |
152 | } |
153 | } |
154 | return $res; |
155 | } |
156 | |
157 | /** |
158 | * Helper function to get an array of labels from an array of values |
159 | * given a mapping template. |
160 | * @todo remove $useDisplayTitle? |
161 | * @param array $values |
162 | * @param string $mappingTemplate |
163 | * @param bool $useDisplayTitle |
164 | * @return array |
165 | */ |
166 | public static function getValuesWithMappingTemplate( |
167 | array $values, |
168 | string $mappingTemplate, |
169 | bool $useDisplayTitle = false |
170 | ): array { |
171 | $title = Title::makeTitleSafe( NS_TEMPLATE, $mappingTemplate ); |
172 | $templateExists = $title->exists(); |
173 | $res = []; |
174 | foreach ( $values as $index => $value ) { |
175 | // if ( $useDisplayTitle ) { |
176 | // $value = $index; |
177 | // } |
178 | if ( $templateExists ) { |
179 | $label = trim( PFUtils::getParser()->recursiveTagParse( '{{' . $mappingTemplate . |
180 | '|' . $value . '}}' ) ); |
181 | if ( $label == '' ) { |
182 | $res[$value] = $value; |
183 | } else { |
184 | $res[$value] = $label; |
185 | } |
186 | } else { |
187 | $res[$value] = $value; |
188 | } |
189 | } |
190 | return $res; |
191 | } |
192 | |
193 | /** |
194 | * Helper function to get an array of labels from an array of values |
195 | * given a mapping Cargo table/field. |
196 | * Derived from PFFormField::setValuesWithMappingCargoField |
197 | * @todo does $useDisplayTitle make sense here? |
198 | * @todo see if check for $mappingCargoValueField works |
199 | * @param array $values |
200 | * @param string|null $mappingCargoField |
201 | * @param string|null $mappingCargoValueField |
202 | * @param string|null $mappingCargoTable |
203 | * @param bool $useDisplayTitle |
204 | * @return array |
205 | */ |
206 | public static function getValuesWithMappingCargoField( |
207 | $values, |
208 | $mappingCargoField, |
209 | $mappingCargoValueField, |
210 | $mappingCargoTable, |
211 | bool $useDisplayTitle = false |
212 | ) { |
213 | $labels = []; |
214 | foreach ( $values as $index => $value ) { |
215 | if ( $useDisplayTitle ) { |
216 | $value = $index; |
217 | } |
218 | $labels[$value] = $value; |
219 | // Check if this works |
220 | if ( $mappingCargoValueField !== null ) { |
221 | $valueField = $mappingCargoValueField; |
222 | } else { |
223 | $valueField = '_pageName'; |
224 | } |
225 | $vals = PFValuesUtils::getValuesForCargoField( |
226 | $mappingCargoTable, |
227 | $mappingCargoField, |
228 | $valueField . '="' . $value . '"' |
229 | ); |
230 | if ( count( $vals ) > 0 ) { |
231 | $labels[$value] = html_entity_decode( trim( $vals[0] ) ); |
232 | } |
233 | } |
234 | return $labels; |
235 | } |
236 | |
237 | /** |
238 | * Mapping with the Translate extension |
239 | * @param array $values |
240 | * @param string $translateMapping |
241 | * @return array |
242 | */ |
243 | public static function getValuesWithTranslateMapping( |
244 | array $values, |
245 | string $translateMapping |
246 | ) { |
247 | $res = []; |
248 | foreach ( $values as $key ) { |
249 | $res[$key] = PFUtils::getParser()->recursiveTagParse( '{{int:' . $translateMapping . $key . '}}' ); |
250 | } |
251 | return $res; |
252 | } |
253 | |
254 | /** |
255 | * Get a named array of display titles |
256 | * |
257 | * @param array $values = pagenames |
258 | * @param bool $doReverseLookup |
259 | * @return array |
260 | */ |
261 | public static function getLabelsForTitles( |
262 | array $values, |
263 | bool $doReverseLookup = false |
264 | ) { |
265 | $labels = []; |
266 | $pageNamesForValues = []; |
267 | $allTitles = []; |
268 | foreach ( $values as $value ) { |
269 | if ( trim( $value ) === "" ) { |
270 | continue; |
271 | } |
272 | if ( $doReverseLookup ) { |
273 | // The regex matches every 'real' page inside the last brackets; for example |
274 | // 'Privacy (doel) (Privacy (doel)concept)', |
275 | // 'Pagina (doel) (Pagina)', |
276 | // will match on (Privacy (doel)concept), (Pagina), ect |
277 | if ( !preg_match_all( '/\((?:[^)(]*(?R)?)*+\)/', $value, $matches ) ) { |
278 | $title = Title::newFromText( $value ); |
279 | // @todo : maybe $title instanceof Title && ...? |
280 | if ( $title && $title->exists() ) { |
281 | $labels[ $value ] = $value; |
282 | } |
283 | // If no matches where found, just leave the value as is |
284 | continue; |
285 | } else { |
286 | $firstMatch = reset( $matches ); |
287 | // The actual match is always in the last group |
288 | $realPage = end( $firstMatch ); |
289 | // The match still contains the first ( and last ) character, remove them |
290 | $realPage = substr( $realPage, 1 ); |
291 | // Finally set the actual value |
292 | $value = substr( $realPage, 0, -1 ); |
293 | } |
294 | } |
295 | $titleInstance = Title::newFromText( $value ); |
296 | // If the title is invalid, just leave the value as is |
297 | if ( $titleInstance === null ) { |
298 | continue; |
299 | } |
300 | $pageNamesForValues[$value] = $titleInstance->getPrefixedText(); |
301 | $allTitles[] = $titleInstance; |
302 | } |
303 | |
304 | $allDisplayTitles = self::getDisplayTitles( $allTitles ); |
305 | foreach ( $pageNamesForValues as $value => $pageName ) { |
306 | if ( isset( $allDisplayTitles[ $pageName ] ) |
307 | && strtolower( $allDisplayTitles[ $pageName ] ) !== strtolower( $value ) |
308 | ) { |
309 | $displayValue = sprintf( '%s (%s)', $allDisplayTitles[ $pageName ], $value ); |
310 | } else { |
311 | $displayValue = $value; |
312 | } |
313 | $labels[$value] = $displayValue; |
314 | } |
315 | |
316 | return $labels; |
317 | } |
318 | |
319 | /** |
320 | * Returns pages each with their display title as the value. |
321 | * @param array $titlesUnfiltered |
322 | * @return array |
323 | */ |
324 | public static function getDisplayTitles( |
325 | array $titlesUnfiltered |
326 | ) { |
327 | $pages = $titles = []; |
328 | foreach ( $titlesUnfiltered as $k => $title ) { |
329 | if ( $title instanceof Title ) { |
330 | $titles[ $k ] = $title; |
331 | } |
332 | } |
333 | $properties = MediaWikiServices::getInstance()->getPageProps() |
334 | ->getProperties( $titles, [ 'displaytitle', 'defaultsort' ] ); |
335 | foreach ( $titles as $title ) { |
336 | if ( array_key_exists( $title->getArticleID(), $properties ) ) { |
337 | $titleprops = $properties[$title->getArticleID()]; |
338 | } else { |
339 | $titleprops = []; |
340 | } |
341 | $titleText = $title->getPrefixedText(); |
342 | if ( array_key_exists( 'displaytitle', $titleprops ) && |
343 | trim( str_replace( ' ', '', strip_tags( $titleprops['displaytitle'] ) ) ) !== '' ) { |
344 | $pages[$titleText] = htmlspecialchars_decode( $titleprops['displaytitle'] ); |
345 | } else { |
346 | $pages[$titleText] = $titleText; |
347 | } |
348 | } |
349 | return $pages; |
350 | } |
351 | |
352 | /** |
353 | * Remove namespace prefix (if any) from label |
354 | * @param string $label |
355 | * @return string |
356 | */ |
357 | private static function removeNSPrefixFromLabel( string $label ) { |
358 | $labelArr = explode( ':', trim( $label ) ); |
359 | if ( count( $labelArr ) > 1 ) { |
360 | $prefix = array_shift( $labelArr ); |
361 | $res = implode( ':', $labelArr ); |
362 | } else { |
363 | $res = $label; |
364 | } |
365 | return $res; |
366 | } |
367 | |
368 | /** |
369 | * Doing "mapping" on values can potentially lead to more than one |
370 | * value having the same "label". To avoid this, we find duplicate |
371 | * labels, if there are any, add on the real value, in parentheses, |
372 | * to all of them. |
373 | * |
374 | * @param array $labels |
375 | * @return array |
376 | */ |
377 | public static function disambiguateLabels( array $labels ) { |
378 | if ( count( $labels ) == count( array_unique( $labels ) ) ) { |
379 | return $labels; |
380 | } |
381 | $fixed_labels = []; |
382 | foreach ( $labels as $value => $label ) { |
383 | $fixed_labels[$value] = $labels[$value]; |
384 | } |
385 | $counts = array_count_values( $fixed_labels ); |
386 | foreach ( $counts as $current_label => $count ) { |
387 | if ( $count > 1 ) { |
388 | $matching_keys = array_keys( $labels, $current_label ); |
389 | foreach ( $matching_keys as $key ) { |
390 | $fixed_labels[$key] .= ' (' . $key . ')'; |
391 | } |
392 | } |
393 | } |
394 | if ( count( $fixed_labels ) == count( array_unique( $fixed_labels ) ) ) { |
395 | return $fixed_labels; |
396 | } |
397 | // If that didn't work, just add on " (value)" to *all* the |
398 | // labels. @TODO - is this necessary? |
399 | foreach ( $labels as $value => $label ) { |
400 | $labels[$value] .= ' (' . $value . ')'; |
401 | } |
402 | return $labels; |
403 | } |
404 | } |