Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 165
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
PFMappingUtils
0.00% covered (danger)
0.00%
0 / 165
0.00% covered (danger)
0.00%
0 / 12
4692
0.00% covered (danger)
0.00%
0 / 1
 getMappingType
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
56
 getMappedValuesForInput
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 isIndexedArray
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getMappedValues
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
110
 getValuesWithMappingProperty
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 getValuesWithMappingTemplate
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 getValuesWithMappingCargoField
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 getValuesWithTranslateMapping
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getLabelsForTitles
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
132
 getDisplayTitles
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
 removeNSPrefixFromLabel
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 disambiguateLabels
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
72
1<?php
2/**
3 * Methods for mapping values to labels
4 * @file
5 * @ingroup PF
6 */
7
8use MediaWiki\MediaWikiServices;
9
10class 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( '&#160;', '', 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}