Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 493
0.00% covered (danger)
0.00%
0 / 35
CRAP
0.00% covered (danger)
0.00%
0 / 1
PFFormField
0.00% covered (danger)
0.00%
0 / 493
0.00% covered (danger)
0.00%
0 / 35
61256
0.00% covered (danger)
0.00%
0 / 1
 create
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 getTemplateField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setTemplateField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInputType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setInputType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasFieldArg
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFieldArgs
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFieldArg
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setFieldArg
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDefaultValue
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isMandatory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setIsMandatory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isHidden
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setIsHidden
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isRestricted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setIsRestricted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 holdsTemplate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isList
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPossibleValues
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getUseDisplayTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInputName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLabel
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLabelMsg
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isDisabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setDescriptionArg
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 newFromFormFieldTag
0.00% covered (danger)
0.00%
0 / 175
0.00% covered (danger)
0.00%
0 / 1
4970
 setPossibleValues
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
240
 cleanupTranslateTags
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
 getCurrentValue
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 1
1640
 valueStringToLabels
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
110
 additionalHTMLForInput
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
342
 createMarkup
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 1
992
 getArgumentsForInputCallSMW
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
56
 getArgumentsForInputCallCargo
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
156
 getArgumentsForInputCall
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
132
1<?php
2/**
3 *
4 * @file
5 * @ingroup PF
6 */
7
8use MediaWiki\MediaWikiServices;
9
10/**
11 * This class is distinct from PFTemplateField in that it represents a template
12 * field defined in a form definition - it contains an PFTemplateField object
13 * within it (the $template_field variable), along with the other properties
14 * for that field that are set within the form.
15 * @ingroup PF
16 */
17class PFFormField {
18
19    /**
20     * @var PFTemplateField
21     */
22    public $template_field;
23    /**
24     * @var array
25     */
26    public static $mappedValuesCache = [];
27    private $mInputType;
28    private $mIsMandatory;
29    private $mIsHidden;
30    private $mIsRestricted;
31    private $mPossibleValues;
32    private $mUseDisplayTitle;
33    private $mIsList;
34    /**
35     * The following fields are not set by the form-creation page
36     * (though they could be).
37     */
38    private $mDefaultValue;
39    private $mPreloadPage;
40    private $mHoldsTemplate;
41    private $mIsUploadable;
42    private $mFieldArgs;
43    private $mDescriptionArgs;
44    private $mLabel;
45    private $mLabelMsg;
46    /**
47     * somewhat of a hack - these two fields are for a field in a specific
48     * representation of a form, not the form definition; ideally these
49     * should be contained in a third 'field' class, called something like
50     * PFFormInstanceField, which holds these fields plus an instance of
51     * PFFormField. Too much work?
52     */
53    private $mInputName;
54    private $mIsDisabled;
55
56    /**
57     * @param PFTemplateField $template_field
58     *
59     * @return self
60     */
61    static function create( PFTemplateField $template_field ) {
62        $f = new PFFormField();
63        $f->template_field = $template_field;
64        $f->mInputType = null;
65        $f->mIsMandatory = false;
66        $f->mIsHidden = false;
67        $f->mIsRestricted = false;
68        $f->mIsUploadable = false;
69        $f->mPossibleValues = null;
70        $f->mUseDisplayTitle = false;
71        $f->mFieldArgs = [];
72        $f->mDescriptionArgs = [];
73        return $f;
74    }
75
76    /**
77     * @return PFTemplateField
78     */
79    public function getTemplateField() {
80        return $this->template_field;
81    }
82
83    /**
84     * @param PFTemplateField $templateField
85     */
86    public function setTemplateField( $templateField ) {
87        $this->template_field = $templateField;
88    }
89
90    public function getInputType() {
91        return $this->mInputType;
92    }
93
94    public function setInputType( $inputType ) {
95        $this->mInputType = $inputType;
96    }
97
98    public function hasFieldArg( $key ) {
99        return array_key_exists( $key, $this->mFieldArgs );
100    }
101
102    public function getFieldArgs() {
103        return $this->mFieldArgs;
104    }
105
106    public function getFieldArg( $key ) {
107        return $this->mFieldArgs[$key];
108    }
109
110    public function setFieldArg( $key, $value ) {
111        $this->mFieldArgs[$key] = $value;
112    }
113
114    public function getDefaultValue() {
115        return $this->mDefaultValue;
116    }
117
118    public function isMandatory() {
119        return $this->mIsMandatory;
120    }
121
122    public function setIsMandatory( $isMandatory ) {
123        $this->mIsMandatory = $isMandatory;
124    }
125
126    public function isHidden() {
127        return $this->mIsHidden;
128    }
129
130    public function setIsHidden( $isHidden ) {
131        $this->mIsHidden = $isHidden;
132    }
133
134    public function isRestricted() {
135        return $this->mIsRestricted;
136    }
137
138    public function setIsRestricted( $isRestricted ) {
139        $this->mIsRestricted = $isRestricted;
140    }
141
142    public function holdsTemplate() {
143        return $this->mHoldsTemplate;
144    }
145
146    public function isList() {
147        return $this->mIsList;
148    }
149
150    public function getPossibleValues() {
151        if ( $this->mPossibleValues != null ) {
152            return $this->mPossibleValues;
153        } else {
154            return $this->template_field->getPossibleValues();
155        }
156    }
157
158    public function getUseDisplayTitle() {
159        return $this->mUseDisplayTitle;
160    }
161
162    public function getInputName() {
163        return $this->mInputName;
164    }
165
166    public function getLabel() {
167        return $this->mLabel;
168    }
169
170    public function getLabelMsg() {
171        return $this->mLabelMsg;
172    }
173
174    public function isDisabled() {
175        return $this->mIsDisabled;
176    }
177
178    public function setDescriptionArg( $key, $value ) {
179        $this->mDescriptionArgs[$key] = $value;
180    }
181
182    static function newFromFormFieldTag(
183        $tag_components,
184        $template,
185        $template_in_form,
186        $form_is_disabled,
187        User $user
188    ) {
189        global $wgPageFormsEmbeddedTemplates;
190
191        $parser = PFUtils::getParser();
192
193        $f = new PFFormField();
194        $f->mFieldArgs = [];
195
196        $field_name = trim( $tag_components[1] );
197        $template_name = $template_in_form->getTemplateName();
198
199        // See if this field matches one of the fields defined for this
200        // template - if it does, use all available information about
201        // that field; if it doesn't, either include it in the form or
202        // not, depending on whether the template has a 'strict'
203        // setting in the form definition.
204        $template_field = $template->getFieldNamed( $field_name );
205
206        if ( $template_field != null ) {
207            $f->template_field = $template_field;
208        } else {
209            if ( $template_in_form->strictParsing() ) {
210                $f->template_field = new PFTemplateField();
211                $f->mIsList = false;
212                return $f;
213            }
214            $f->template_field = PFTemplateField::create( $field_name, null );
215        }
216
217        $embeddedTemplate = $f->template_field->getHoldsTemplate();
218        if ( $embeddedTemplate != '' ) {
219            $f->mIsHidden = true;
220            $f->mHoldsTemplate = true;
221            // Store this information so that the embedded/"held"
222            // template - which is hopefully after this one in the
223            // form definition - can be handled correctly. In forms,
224            // both the embedding field and the embedded template are
225            // specified as such, but in templates (i.e., with
226            // #template_params), it's only the embedding field.
227            $wgPageFormsEmbeddedTemplates[$embeddedTemplate] = [ $template_name, $field_name ];
228        }
229
230        $semantic_property = null;
231        $cargo_table = $cargo_field = $cargo_where = null;
232        $show_on_select = [];
233        $fullFieldName = $template_name . '[' . $field_name . ']';
234        $values = $valuesSourceType = $valuesSource = null;
235
236        // We set "values from ..." params if there are corresponding
237        // values set in #template_params - this is a bit of a @hack,
238        // since we should really just use these values directly, but
239        // there are various places in the code that check for "values
240        // from ...", so it's easier to just pretend that these params
241        // were set.
242        $categoryFromTemplate = $f->getTemplateField()->getCategory();
243        if ( $categoryFromTemplate !== null ) {
244            $f->mFieldArgs['values from category'] = $categoryFromTemplate;
245        }
246        $namespaceFromTemplate = $f->getTemplateField()->getNSText();
247        if ( $namespaceFromTemplate !== null ) {
248            $f->mFieldArgs['values from namespace'] = $namespaceFromTemplate;
249        }
250
251        // Cycle through the other components.
252        for ( $i = 2; $i < count( $tag_components ); $i++ ) {
253            $component = trim( $tag_components[$i] );
254
255            if ( $component == 'mandatory' ) {
256                $f->mIsMandatory = true;
257            } elseif ( $component == 'hidden' ) {
258                $f->mIsHidden = true;
259            } elseif ( $component == 'restricted' ) {
260                $f->mIsRestricted = ( !$user || !$user->isAllowed( 'editrestrictedfields' ) );
261            } elseif ( $component == 'list' ) {
262                $f->mIsList = true;
263            } elseif ( $component == 'unique' ) {
264                $f->mFieldArgs['unique'] = true;
265            } elseif ( $component == 'edittools' ) {
266                // free text only
267                $f->mFieldArgs['edittools'] = true;
268            }
269
270            $sub_components = array_map( 'trim', explode( '=', $component, 2 ) );
271
272            if ( count( $sub_components ) == 1 ) {
273                // add handling for single-value params, for custom input types
274                $f->mFieldArgs[$sub_components[0]] = true;
275
276                if ( $component == 'holds template' ) {
277                    $f->mIsHidden = true;
278                    $f->mHoldsTemplate = true;
279                }
280            } elseif ( count( $sub_components ) == 2 ) {
281                // First, set each value as its own entry in $this->mFieldArgs.
282                $f->mFieldArgs[$sub_components[0]] = $sub_components[1];
283
284                // Then, do all special handling.
285                if ( $sub_components[0] == 'input type' ) {
286                    $f->mInputType = $sub_components[1];
287                } elseif ( $sub_components[0] == 'default' ) {
288                    // We call recursivePreprocess() here,
289                    // and not the more standard
290                    // recursiveTagParse(), so that
291                    // wikitext in the value, and bare URLs,
292                    // will not get turned into HTML.
293                    $f->mDefaultValue = $parser->recursivePreprocess( $sub_components[1] );
294                } elseif ( $sub_components[0] == 'preload' ) {
295                    $f->mPreloadPage = $sub_components[1];
296                } elseif ( $sub_components[0] == 'label' ) {
297                    $f->mLabel = $sub_components[1];
298                } elseif ( $sub_components[0] == 'label msg' ) {
299                    $f->mLabelMsg = $sub_components[1];
300                } elseif ( $sub_components[0] == 'show on select' ) {
301                    // html_entity_decode() is needed to turn '&gt;' to '>'
302                    $vals = explode( ';', html_entity_decode( $sub_components[1] ) );
303                    foreach ( $vals as $val ) {
304                        $val = trim( $val );
305                        if ( empty( $val ) ) {
306                            continue;
307                        }
308                        $option_div_pair = explode( '=>', $val, 2 );
309                        if ( count( $option_div_pair ) > 1 ) {
310                            $option = PFFormPrinter::getParsedValue( $parser, trim( $option_div_pair[0] ) );
311                            $div_id = $option_div_pair[1];
312                            if ( array_key_exists( $div_id, $show_on_select ) ) {
313                                $show_on_select[$div_id][] = $option;
314                            } else {
315                                $show_on_select[$div_id] = [ $option ];
316                            }
317                        } else {
318                            $show_on_select[$val] = [];
319                        }
320                    }
321                } elseif ( $sub_components[0] == 'values' ) {
322                    // Handle this one only after
323                    // 'delimiter' has also been set.
324                    $values = PFFormPrinter::getParsedValue( $parser, $sub_components[1] );
325                } elseif ( $sub_components[0] == 'values from property' ) {
326                    $valuesSourceType = 'property';
327                    $valuesSource = $sub_components[1];
328                } elseif ( $sub_components[0] == 'values from wikidata' ) {
329                    $valuesSourceType = 'wikidata';
330                    $valuesSource = urlencode( $sub_components[1] );
331                } elseif ( $sub_components[0] == 'values from query' ) {
332                    $valuesSourceType = 'query';
333                    $valuesSource = $sub_components[1];
334                } elseif ( $sub_components[0] == 'values from category' ) {
335                    $valuesSource = PFFormPrinter::getParsedValue( $parser, $sub_components[1] );
336                    global $wgCapitalLinks;
337                    if ( $wgCapitalLinks ) {
338                        $valuesSource = ucfirst( $valuesSource );
339                    }
340                    $valuesSourceType = 'category';
341                } elseif ( $sub_components[0] == 'values from concept' ) {
342                    $valuesSourceType = 'concept';
343                    $valuesSource = PFFormPrinter::getParsedValue( $parser, $sub_components[1] );
344                } elseif ( $sub_components[0] == 'values from namespace' ) {
345                    $valuesSourceType = 'namespace';
346                    $valuesSource = PFFormPrinter::getParsedValue( $parser, $sub_components[1] );
347                } elseif ( $sub_components[0] == 'values dependent on' ) {
348                    global $wgPageFormsDependentFields;
349                    $wgPageFormsDependentFields[] = [ $sub_components[1], $fullFieldName ];
350                } elseif ( $sub_components[0] == 'unique for category' ) {
351                    $f->mFieldArgs['unique'] = true;
352                    $f->mFieldArgs['unique_for_category'] = PFFormPrinter::getParsedValue( $parser, $sub_components[1] );
353                } elseif ( $sub_components[0] == 'unique for namespace' ) {
354                    $f->mFieldArgs['unique'] = true;
355                    $f->mFieldArgs['unique_for_namespace'] = PFFormPrinter::getParsedValue( $parser, $sub_components[1] );
356                } elseif ( $sub_components[0] == 'unique for concept' ) {
357                    $f->mFieldArgs['unique'] = true;
358                    $f->mFieldArgs['unique_for_concept'] = PFFormPrinter::getParsedValue( $parser, $sub_components[1] );
359                } elseif ( $sub_components[0] == 'property' ) {
360                    $semantic_property = $sub_components[1];
361                } elseif ( $sub_components[0] == 'cargo table' ) {
362                    $cargo_table = $sub_components[1];
363                } elseif ( $sub_components[0] == 'cargo field' ) {
364                    $cargo_field = $sub_components[1];
365                } elseif ( $sub_components[0] == 'cargo where' ) {
366                    $cargo_where = PFFormPrinter::getParsedValue( $parser, $sub_components[1] );
367                } elseif ( $sub_components[0] == 'default filename' ) {
368                    global $wgTitle;
369                    $page_name = $wgTitle->getText();
370                    if ( $wgTitle->isSpecialPage() ) {
371                        // If it's of the form
372                        // Special:FormEdit/form/target,
373                        // get just the target.
374                        $pageNameComponents = explode( '/', $page_name, 3 );
375                        if ( count( $pageNameComponents ) == 3 ) {
376                            $page_name = $pageNameComponents[2];
377                        }
378                    }
379                    $default_filename = str_replace( '<page name>', $page_name, $sub_components[1] );
380                    // Parse value, so default filename can
381                    // include parser functions.
382                    $default_filename = PFFormPrinter::getParsedValue( $parser, $default_filename );
383                    $f->mFieldArgs['default filename'] = $default_filename;
384                } elseif ( $sub_components[0] == 'restricted' ) {
385                    $effectiveGroups = MediaWikiServices::getInstance()->getUserGroupManager()->getUserEffectiveGroups( $user );
386                    $f->mIsRestricted = !array_intersect(
387                        $effectiveGroups, array_map( 'trim', explode( ',', $sub_components[1] ) )
388                    );
389                }
390            }
391        }
392        // end for
393
394        if ( in_array( $valuesSourceType, [ 'category', 'namespace', 'concept' ] ) ) {
395            global $wgPageFormsUseDisplayTitle;
396            $f->mUseDisplayTitle = $wgPageFormsUseDisplayTitle;
397        } else {
398            $f->mUseDisplayTitle = false;
399        }
400
401        if ( !array_key_exists( 'delimiter', $f->mFieldArgs ) ) {
402            $delimiterFromTemplate = $f->getTemplateField()->getDelimiter();
403            if ( $delimiterFromTemplate == '' ) {
404                $f->mFieldArgs['delimiter'] = ',';
405            } else {
406                $f->mFieldArgs['delimiter'] = $delimiterFromTemplate;
407                $f->mIsList = true;
408            }
409        }
410
411        // Do some data storage specific to the Semantic MediaWiki and
412        // Cargo extensions.
413        if ( defined( 'SMW_VERSION' ) ) {
414            // If a property was set in the form definition,
415            // overwrite whatever is set in the template field -
416            // this is somewhat of a hack, since parameters set in
417            // the form definition are meant to go into the
418            // PFFormField object, not the PFTemplateField object
419            // it contains;
420            // it seemed like too much work, though, to create an
421            // PFFormField::setSemanticProperty() function just for
422            // this call.
423            if ( $semantic_property !== null ) {
424                $f->template_field->setSemanticProperty( $semantic_property );
425            } else {
426                $semantic_property = $f->template_field->getSemanticProperty();
427            }
428            if ( $semantic_property !== null ) {
429                global $wgPageFormsFieldProperties;
430                $wgPageFormsFieldProperties[$fullFieldName] = $semantic_property;
431            }
432        }
433        if ( defined( 'CARGO_VERSION' ) ) {
434            if ( $cargo_table != null && $cargo_field != null ) {
435                $f->template_field->setCargoFieldData( $cargo_table, $cargo_field );
436            }
437            $fullCargoField = $f->template_field->getFullCargoField();
438            if ( $fullCargoField !== null ) {
439                global $wgPageFormsCargoFields;
440                $wgPageFormsCargoFields[$fullFieldName] = $fullCargoField;
441            }
442        }
443
444        $f->setPossibleValues( $valuesSourceType, $valuesSource, $values, $cargo_table, $cargo_field, $cargo_where );
445
446        $mappingType = PFMappingUtils::getMappingType( $f->mFieldArgs, $f->mUseDisplayTitle );
447        if ( $mappingType !== null && !empty( $f->mPossibleValues ) ) {
448            // If we're going to be mapping values, we need to have
449            // the exact page name - and if these values come from
450            // "values from namespace", the namespace prefix was
451            // not included, so we need to add it now.
452            if ( $valuesSourceType == 'namespace' && $valuesSource != '' && $valuesSource != 'Main' ) {
453                foreach ( $f->mPossibleValues as $index => &$value ) {
454                    $value = $valuesSource . ':' . $value;
455                }
456                // Has to be set to false to not mess up the
457                // handling.
458                $f->mUseDisplayTitle = false;
459            }
460
461            $mappedValuesKey = json_encode( $f->mFieldArgs ) . $mappingType;
462            if ( array_key_exists( $mappedValuesKey, self::$mappedValuesCache ) ) {
463                $f->mPossibleValues = self::$mappedValuesCache[$mappedValuesKey];
464            } else {
465                $f->mPossibleValues = PFMappingUtils::getMappedValuesForInput( $f->mPossibleValues, $f->mFieldArgs );
466                self::$mappedValuesCache[$mappedValuesKey] = $f->mPossibleValues;
467            }
468        }
469
470        if ( $template_in_form->allowsMultiple() ) {
471            $f->mFieldArgs['part_of_multiple'] = true;
472        }
473        if ( count( $show_on_select ) > 0 ) {
474            $f->mFieldArgs['show on select'] = $show_on_select;
475        }
476
477        // Disable this field if either the whole form is disabled, or
478        // it's a restricted field and user doesn't have sysop privileges.
479        $f->mIsDisabled = ( $form_is_disabled || $f->mIsRestricted );
480
481        if ( $template_name === null || $template_name === '' ) {
482            $f->mInputName = $field_name;
483        } elseif ( $template_in_form->allowsMultiple() ) {
484            // 'num' will get replaced by an actual index, either in PHP
485            // or in Javascript, later on
486            $f->mInputName = $template_name . '[num][' . $field_name . ']';
487            $f->setFieldArg( 'origName', $fullFieldName );
488        } else {
489            $f->mInputName = $fullFieldName;
490        }
491
492        return $f;
493    }
494
495    private function setPossibleValues( $valuesSourceType, $valuesSource, $values, $cargo_table, $cargo_field, $cargo_where ) {
496        // Set the $mPossibleValues field, using the following logic:
497        // - If "values" was set in the form definition, use that.
498        // - If any "values from ..." parameter was set, use that.
499        // - If "cargo where" was set, use it, if a Cargo table and field have also been defined.
500        // - If "cargo table" and "cargo field" were set, then:
501        //     - If there are "allowed values" for that field use those.
502        //     - Otherwise, use that field's existing values.
503        // - Otherwise, use the possible values defined within the corresponding template field, if any.
504
505        if ( $values != null ) {
506            $delimiter = $this->mFieldArgs['delimiter'];
507            // Remove whitespaces, and un-escape characters
508            $valuesArray = array_map( 'trim', explode( $delimiter, $values ) );
509            $this->mPossibleValues = array_map( 'htmlspecialchars_decode', $valuesArray );
510            return;
511        }
512
513        if ( $valuesSourceType !== null && ( $valuesSourceType !== 'wikidata' || ( $this->mInputType !== 'combobox' &&
514        $this->mInputType !== 'tokens' ) ) ) {
515            $this->mPossibleValues = PFValuesUtils::getAutocompleteValues( $valuesSource, $valuesSourceType );
516            return;
517        }
518
519        if ( defined( 'CARGO_VERSION' ) && $cargo_where != null ) {
520            if ( $cargo_table == null || $cargo_field == null ) {
521                $fullCargoField = $this->template_field->getFullCargoField();
522                $table_and_field = explode( '|', $fullCargoField );
523                $cargo_table = $table_and_field[0];
524                $cargo_field = $table_and_field[1];
525            }
526            $cargoValues = PFValuesUtils::getValuesForCargoField( $cargo_table, $cargo_field, $cargo_where );
527            $this->mPossibleValues = array_filter( $cargoValues, 'strlen' );
528            return;
529        }
530
531        // If we're using Cargo, there's no equivalent for "values from
532        // property" - instead, we just always get the values if a
533        // field and table have been specified.
534        if ( defined( 'CARGO_VERSION' ) && $cargo_table != null && $cargo_field != null ) {
535            // If there are "allowed values" defined, use those.
536            $fieldDesc = PFUtils::getCargoFieldDescription( $cargo_table, $cargo_field );
537            if ( $fieldDesc !== null && $fieldDesc->mAllowedValues !== null ) {
538                $this->mPossibleValues = $fieldDesc->mAllowedValues;
539                return;
540            }
541            // We only want the non-null values. Ideally this could
542            // be done by calling getValuesForCargoField() with
543            // an "IS NOT NULL" clause, but unfortunately that fails
544            // for array/list fields.
545            // Instead of getting involved with all that, we'll just
546            // remove the null/blank values afterward.
547            $cargoValues = PFValuesUtils::getAllValuesForCargoField( $cargo_table, $cargo_field );
548            $this->mPossibleValues = array_filter( $cargoValues, 'strlen' );
549            return;
550        }
551
552        $this->mPossibleValues = $this->template_field->getPossibleValues();
553    }
554
555    function cleanupTranslateTags( &$value ) {
556        $i = 0;
557        // If there are two tags ("<!--T:X-->") with no content between them, remove the first one.
558        while ( preg_match( '/(<!--T:[0-9]+-->\s*)(<!--T:[0-9]+-->)/', $value, $matches ) ) {
559            $value = str_replace( $matches[1], '', $value );
560            if ( $i++ > 200 ) {
561                // Is this necessary?
562                break;
563            }
564        }
565
566        $i = 0;
567        // If there is a tag ("<!--T:X-->") at the end, with nothing after, remove it.
568        while ( preg_match( '#(<!--T:[0-9]+-->\s*)(</translate>)#', $value, $matches ) ) {
569            $value = str_replace( $matches[1], '', $value );
570            if ( $i++ > 200 ) {
571                // Is this necessary?
572                break;
573            }
574        }
575
576        $i = 0;
577        // If there is a tag ("<!--T:X-->") not separated from a template call ("{{ ..."),
578        // add a new line between them.
579        while ( preg_match( '/(<!--T:[0-9]+-->)({{[^}]+}}\s*)/', $value, $matches ) ) {
580            $value = str_replace( $matches[1], $matches[1] . "\n", $value );
581            if ( $i++ > 200 ) {
582                // Is this necessary?
583                break;
584            }
585        }
586    }
587
588    function getCurrentValue( $template_instance_query_values, $form_submitted, $source_is_page, $all_instances_printed, &$val_modifier = null ) {
589        // Get the value from the request, if
590        // it's there, and if it's not an array.
591        $field_name = $this->template_field->getFieldName();
592        $delimiter = $this->mFieldArgs['delimiter'];
593        $escaped_field_name = str_replace( "'", "\'", $field_name );
594
595        if ( PFUtils::isTranslateEnabled() && $this->hasFieldArg( 'translatable' ) && $this->getFieldArg( 'translatable' ) ) {
596            // If this is a translatable field, and both it and its corresponding translate ID tag are passed in, we add it.
597            $fieldName = $this->getTemplateField()->getFieldName();
598            $fieldNameTag = $fieldName . '_translate_number_tag';
599            if ( isset( $template_instance_query_values[$fieldName] ) && isset( $template_instance_query_values[$fieldNameTag] ) ) {
600                $tag = $template_instance_query_values[$fieldNameTag];
601                if ( !preg_match( '/( |\n)$/', $tag ) ) {
602                    $tag .= "\n";
603                }
604                if ( trim( $template_instance_query_values[$fieldName] ) ) {
605                    // Don't add the tag if field content has been removed.
606                    $template_instance_query_values[$fieldName] = $tag . $template_instance_query_values[$fieldName];
607                }
608            }
609            // If user has deleted some content, and there is some translate tag ("<!--T:X-->") with no content, remove the tag.
610            if ( isset( $template_instance_query_values[$fieldName] ) ) {
611                $this->cleanupTranslateTags( $template_instance_query_values[$fieldName] );
612            }
613        }
614
615        if ( isset( $template_instance_query_values ) &&
616            $template_instance_query_values != null &&
617            is_array( $template_instance_query_values )
618        ) {
619            // If the field name contains an apostrophe, the array
620            // sometimes has the apostrophe escaped, and sometimes
621            // not. For now, just check for both versions.
622            // @TODO - figure this out.
623            $field_query_val = null;
624            if ( array_key_exists( $escaped_field_name, $template_instance_query_values ) ) {
625                $field_query_val = $template_instance_query_values[$escaped_field_name];
626            } elseif ( array_key_exists( $field_name, $template_instance_query_values ) ) {
627                $field_query_val = $template_instance_query_values[$field_name];
628            } else {
629                // The next checks are to allow for support for appending/prepending with autoedit.
630                if ( array_key_exists( "$field_name+", $template_instance_query_values ) ) {
631                    $field_query_val = $template_instance_query_values["$field_name+"];
632                    $val_modifier = '+';
633                } elseif ( array_key_exists( "$field_name-", $template_instance_query_values ) ) {
634                    $field_query_val = $template_instance_query_values["$field_name-"];
635                    $val_modifier = '-';
636                }
637            }
638
639            if ( $form_submitted && $field_query_val != '' ) {
640                $map_field = false;
641                if ( array_key_exists( 'map_field', $template_instance_query_values ) &&
642                    array_key_exists( $field_name, $template_instance_query_values['map_field'] ) ) {
643                    $map_field = true;
644                }
645                if ( is_array( $field_query_val ) ) {
646                    $cur_values = [];
647                    if ( $map_field && $this->mPossibleValues !== null ) {
648                        foreach ( $field_query_val as $key => $val ) {
649                            $val = trim( $val );
650                            if ( $key === 'is_list' ) {
651                                $cur_values[$key] = $val;
652                            } else {
653                                $cur_values[] = PFValuesUtils::labelToValue( $val, $this->mPossibleValues );
654                            }
655                        }
656                    } else {
657                        foreach ( $field_query_val as $key => $val ) {
658                            $cur_values[$key] = $val;
659                        }
660                    }
661                    return PFFormPrinter::getStringFromPassedInArray( $cur_values, $delimiter );
662                } else {
663                    $field_query_val = trim( $field_query_val );
664                    if ( $map_field && $this->mPossibleValues !== null ) {
665                        // this should be replaced with an input type neutral way of
666                        // figuring out if this scalar input type is a list
667                        if ( $this->mInputType == "tokens" ) {
668                            $this->mIsList = true;
669                        }
670                        if ( $this->mIsList ) {
671                            $cur_values = array_map( 'trim', explode( $delimiter, $field_query_val ) );
672                            foreach ( $cur_values as $key => $val ) {
673                                $cur_values[$key] = PFValuesUtils::labelToValue( $val, $this->mPossibleValues );
674                            }
675                            return implode( $delimiter, $cur_values );
676                        }
677                        return PFValuesUtils::labelToValue( $field_query_val, $this->mPossibleValues );
678                    }
679                    return $field_query_val;
680                }
681            }
682            if ( !$form_submitted && $field_query_val != '' ) {
683                if ( is_array( $field_query_val ) ) {
684                    $str = PFFormPrinter::getStringFromPassedInArray( $field_query_val, $delimiter );
685                } else {
686                    $str = $field_query_val;
687                }
688                return str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], $str );
689
690            }
691        }
692
693        // Default values in new instances of multiple-instance
694        // templates should always be set, even for existing pages.
695        $part_of_multiple = array_key_exists( 'part_of_multiple', $this->mFieldArgs );
696        $printing_starter_instance = $part_of_multiple && $all_instances_printed;
697        if ( ( !$source_is_page || $printing_starter_instance ) && !$form_submitted ) {
698            if ( $this->mDefaultValue !== null ) {
699                // Set to the default value specified in the form, if it's there.
700                return $this->mDefaultValue;
701            } elseif ( $this->mPreloadPage ) {
702                return PFFormUtils::getPreloadedText( $this->mPreloadPage );
703            }
704        }
705
706        // We're still here...
707        return null;
708    }
709
710    /**
711     * Map a template field value into labels.
712     * @param string $valueString
713     * @param string $delimiter
714     * @param bool $formSubmitted
715     * @return string|string[]
716     */
717    public function valueStringToLabels( $valueString, $delimiter, $formSubmitted ) {
718        if ( $valueString === null || trim( $valueString ) === '' ||
719            $this->mPossibleValues === null ) {
720            return $valueString;
721        }
722        if ( $delimiter !== null ) {
723            $values = array_map( 'trim', explode( $delimiter, $valueString ) );
724        } else {
725            $values = [ $valueString ];
726        }
727
728        $maxValues = PFValuesUtils::getMaxValuesToRetrieve();
729        if ( $formSubmitted && ( count( $this->mPossibleValues ) >= $maxValues ) ) {
730            // Remote autocompletion.
731            $mappedValues = PFMappingUtils::getMappedValuesForInput( $values, $this->getFieldArgs() );
732            return array_values( $mappedValues );
733        }
734
735        $labels = [];
736        foreach ( $values as $value ) {
737            if ( $value != '' ) {
738                if ( array_key_exists( $value, $this->mPossibleValues ) ) {
739                    $labels[] = $this->mPossibleValues[$value];
740                } else {
741                    $labels[] = $value;
742                }
743            }
744        }
745        return $labels;
746    }
747
748    public function additionalHTMLForInput( $cur_value, $field_name, $template_name ) {
749        $text = '';
750
751        // Add a field just after the hidden field, within the HTML, to
752        // locate where the multiple-templates HTML, stored in
753        // $multipleTemplateString, should be inserted.
754        if ( $this->mHoldsTemplate ) {
755            $text .= PFFormPrinter::makePlaceholderInFormHTML( PFFormPrinter::placeholderFormat( $template_name, $field_name ) );
756        }
757
758        // If this field is disabled, add a hidden field holding
759        // the value of this field, because disabled inputs for some
760        // reason don't submit their value.
761        if ( $this->mIsDisabled ) {
762            if ( $field_name == 'free text' || $field_name == '#freetext#' ) {
763                $text .= Html::hidden( 'pf_free_text', '!free_text!' );
764            } else {
765                if ( is_array( $cur_value ) ) {
766                    $delimiter = $this->mFieldArgs['delimiter'];
767                    $text .= Html::hidden( $this->mInputName, implode( $delimiter, $cur_value ) );
768                } else {
769                    $text .= Html::hidden( $this->mInputName, $cur_value );
770                }
771            }
772        }
773
774        if ( $this->hasFieldArg( 'mapping template' ) ||
775            $this->hasFieldArg( 'mapping property' ) ||
776            ( $this->hasFieldArg( 'mapping cargo table' ) &&
777            $this->hasFieldArg( 'mapping cargo field' ) ) ||
778            $this->mUseDisplayTitle ) {
779            if ( $this->hasFieldArg( 'part_of_multiple' ) ) {
780                $text .= Html::hidden( $template_name . '[num][map_field][' . $field_name . ']', 'true' );
781            } else {
782                $text .= Html::hidden( $template_name . '[map_field][' . $field_name . ']', 'true' );
783            }
784        }
785
786        if ( $this->hasFieldArg( 'unique' ) ) {
787            global $wgPageFormsFieldNum;
788
789            $semantic_property = $this->template_field->getSemanticProperty();
790            if ( $semantic_property != null ) {
791                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_property', $semantic_property );
792            }
793            $fullCargoField = $this->template_field->getFullCargoField();
794            if ( $fullCargoField != null ) {
795                // It's inefficient to get these values via
796                // text parsing, but oh well.
797                list( $cargo_table, $cargo_field ) = explode( '|', $fullCargoField, 2 );
798                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_cargo_table', $cargo_table );
799                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_cargo_field', $cargo_field );
800            }
801            if ( $this->hasFieldArg( 'unique_for_category' ) ) {
802                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_for_category', $this->getFieldArg( 'unique_for_category' ) );
803            }
804            if ( $this->hasFieldArg( 'unique_for_namespace' ) ) {
805                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_for_namespace', $this->getFieldArg( 'unique_for_namespace' ) );
806            }
807            if ( $this->hasFieldArg( 'unique_for_concept' ) ) {
808                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_for_concept', $this->getFieldArg( 'unique_for_concept' ) );
809            }
810        }
811        return $text;
812    }
813
814    /**
815     * For now, HTML of an individual field depends on whether or not it's
816     * part of multiple-instance template; this may change if handling of
817     * such templates in form definitions gets more sophisticated.
818     *
819     * @param bool $part_of_multiple
820     * @param bool $is_last_field_in_template
821     * @return string
822     */
823    function createMarkup( $part_of_multiple, $is_last_field_in_template ) {
824        $text = "";
825        $descPlaceholder = "";
826        $textBeforeField = "";
827
828        if ( array_key_exists( "Description", $this->mDescriptionArgs ) ) {
829            $fieldDesc = $this->mDescriptionArgs['Description'];
830            if ( $fieldDesc != '' ) {
831                if ( isset( $this->mDescriptionArgs['DescriptionTooltipMode'] ) ) {
832                    // The wikitext we use for tooltips
833                    // depends on which other extensions
834                    // are installed.
835                    if ( class_exists( 'RegularTooltipsParser' ) ) {
836                        // RegularTooltips
837                        $descPlaceholder = " {{#info-tooltip:$fieldDesc}}";
838                    } elseif ( defined( 'SMW_VERSION' ) ) {
839                        // Semantic MediaWiki
840                        $descPlaceholder = " {{#info:$fieldDesc}}";
841                    } elseif ( class_exists( 'SimpleTooltipParserFunction' ) ) {
842                        // SimpleTooltip
843                        $descPlaceholder = " {{#tip-info:$fieldDesc}}";
844                    } else {
845                        // Don't make it a tooltip.
846                        $descPlaceholder = '<br><p class="pfFieldDescription" style="font-size:0.7em; color:gray;">' . $fieldDesc . '</p>';
847                    }
848                } else {
849                    $descPlaceholder = '<br><p class="pfFieldDescription" style="font-size:0.7em; color:gray;">' . $fieldDesc . '</p>';
850                }
851            }
852        }
853
854        if ( array_key_exists( "TextBeforeField", $this->mDescriptionArgs ) ) {
855            $textBeforeField = $this->mDescriptionArgs['TextBeforeField'];
856        }
857
858        $fieldLabel = $this->template_field->getLabel();
859        if ( $fieldLabel == '' ) {
860            $fieldLabel = $this->template_field->getFieldName();
861        }
862        if ( $textBeforeField != '' ) {
863            $fieldLabel = $textBeforeField . ' ' . $fieldLabel;
864        }
865
866        if ( $part_of_multiple ) {
867            $text .= "'''$fieldLabel:''' $descPlaceholder";
868        } else {
869            $text .= "$fieldLabel$descPlaceholder\n";
870        }
871
872        if ( !$part_of_multiple ) {
873            $text .= "| ";
874        }
875        $text .= "{{{field|" . $this->template_field->getFieldName();
876        if ( $this->mIsHidden ) {
877            $text .= "|hidden";
878        } elseif ( $this->getInputType() !== null && $this->getInputType() !== '' ) {
879            $text .= "|input type=" . $this->getInputType();
880        }
881        foreach ( $this->mFieldArgs as $arg => $value ) {
882            if ( $value === true ) {
883                $text .= "|$arg";
884            } elseif ( $arg === 'uploadable' ) {
885                // Are there similar value-less arguments
886                // that need to be handled here?
887                $text .= "|$arg";
888            } else {
889                $text .= "|$arg=$value";
890            }
891        }
892
893        // Special handling if neither SMW nor Cargo are installed - the
894        // form has to handle stuff that otherwise would go in the
895        // template.
896        if (
897            !defined( 'SMW_VERSION' ) &&
898            !defined( 'CARGO_VERSION' ) &&
899            !array_key_exists( 'values', $this->mFieldArgs ) &&
900            is_array( $this->template_field->getPossibleValues() ) &&
901            count( $this->template_field->getPossibleValues() ) > 0
902        ) {
903            if ( $this->getInputType() == null ) {
904                if ( $this->template_field->isList() ) {
905                    $text .= '|input type=checkboxes';
906                } else {
907                    $text .= '|input type=dropdown';
908                }
909            }
910            $delimiter = ',';
911            if ( $this->template_field->isList() ) {
912                $delimiter = $this->template_field->getDelimiter();
913                if ( $delimiter == '' ) {
914                    $delimiter = ',';
915                }
916                // @todo - we need to add a "|delimiter=" param
917                // here too, if #template_params is not being
918                // called in the template.
919            }
920            $text .= '|values=' . implode( $delimiter, $this->template_field->getPossibleValues() );
921        }
922
923        if ( $this->mIsMandatory ) {
924            $text .= "|mandatory";
925        } elseif ( $this->mIsRestricted ) {
926            $text .= "|restricted";
927        }
928        $text .= "}}}\n";
929        if ( $part_of_multiple ) {
930            $text .= "\n";
931        } elseif ( !$is_last_field_in_template ) {
932            $text .= "|-\n";
933        }
934        return $text;
935    }
936
937    function getArgumentsForInputCallSMW( array &$other_args ) {
938        if ( $this->template_field->getSemanticProperty() !== '' &&
939            !array_key_exists( 'semantic_property', $other_args ) ) {
940            $other_args['semantic_property'] = $this->template_field->getSemanticProperty();
941            $other_args['property_type'] = $this->template_field->getPropertyType();
942        }
943        // If autocompletion hasn't already been hardcoded in the form,
944        // and it's a property of type page, or a property of another
945        // type with 'autocomplete' specified, set the necessary
946        // parameters.
947        if ( !array_key_exists( 'autocompletion source', $other_args ) ) {
948            if ( $this->template_field->getPropertyType() == '_wpg' ) {
949                $other_args['autocompletion source'] = $this->template_field->getSemanticProperty();
950                $other_args['autocomplete field type'] = 'property';
951            } elseif ( array_key_exists( 'autocomplete', $other_args ) || array_key_exists( 'remote autocompletion', $other_args ) ) {
952                $other_args['autocompletion source'] = $this->template_field->getSemanticProperty();
953                $other_args['autocomplete field type'] = 'property';
954            }
955        }
956    }
957
958    function getArgumentsForInputCallCargo( array &$other_args ) {
959        $fullCargoField = $this->template_field->getFullCargoField();
960        if ( $fullCargoField !== null &&
961            array_key_exists( 'cargo where', $other_args ) ) {
962            $fullCargoField .= '|' . $other_args['cargo where'];
963        }
964        if ( $fullCargoField !== null &&
965            !array_key_exists( 'full_cargo_field', $other_args ) ) {
966            $other_args['full_cargo_field'] = $fullCargoField;
967        }
968
969        if ( $this->template_field->getFieldType() == 'Hierarchy' ) {
970            $other_args['structure'] = $this->template_field->getHierarchyStructure();
971        }
972
973        if ( !array_key_exists( 'autocompletion source', $other_args ) ) {
974            if (
975                $this->template_field->getFieldType() == 'Page' ||
976                array_key_exists( 'autocomplete', $other_args ) ||
977                array_key_exists( 'remote autocompletion', $other_args )
978            ) {
979                if ( array_key_exists( 'mapping cargo table', $other_args ) &&
980                array_key_exists( 'mapping cargo field', $other_args ) ) {
981                    $mapping_cargo_field = $other_args[ 'mapping cargo field' ];
982                    $mapping_cargo_table = $other_args[ 'mapping cargo table' ];
983                    $other_args['autocompletion source'] = $mapping_cargo_table . '|' . $mapping_cargo_field;
984                } else {
985                    $other_args['autocompletion source'] = $fullCargoField;
986                }
987                $other_args['autocomplete field type'] = 'cargo field';
988            }
989        }
990    }
991
992    /**
993     * Since Page Forms uses a hook system for the functions that
994     * create HTML inputs, most arguments are contained in the "$other_args"
995     * array - create this array, using the attributes of this form
996     * field and the template field it corresponds to, if any.
997     * @param array|null $default_args
998     * @return array
999     */
1000    function getArgumentsForInputCall( array $default_args = null ) {
1001        $parser = PFUtils::getParser();
1002
1003        // start with the arguments array already defined
1004        $other_args = $this->mFieldArgs;
1005        // a value defined for the form field should always supersede
1006        // the coresponding value for the template field
1007        if ( $this->mPossibleValues != null ) {
1008            $other_args['possible_values'] = $this->mPossibleValues;
1009        } else {
1010            $other_args['possible_values'] = $this->template_field->getPossibleValues();
1011            if ( $this->hasFieldArg( 'mapping using translate' ) ) {
1012                $mappedValues = PFMappingUtils::getValuesWithTranslateMapping( $other_args['possible_values'], $other_args['mapping using translate'] );
1013                $other_args['value_labels'] = array_values( $mappedValues );
1014            } else {
1015                $other_args['value_labels'] = $this->template_field->getValueLabels();
1016            }
1017        }
1018        $other_args['is_list'] = ( $this->mIsList || $this->template_field->isList() );
1019        if ( $this->template_field->isMandatory() ) {
1020            $other_args['mandatory'] = true;
1021        }
1022        if ( $this->template_field->isUnique() ) {
1023            $other_args['unique'] = true;
1024        }
1025
1026        // Now add some extension-specific arguments to the input call.
1027        if ( defined( 'CARGO_VERSION' ) ) {
1028            $this->getArgumentsForInputCallCargo( $other_args );
1029        }
1030        if ( defined( 'SMW_VERSION' ) ) {
1031            $this->getArgumentsForInputCallSMW( $other_args );
1032        }
1033
1034        // Now merge in the default values set by PFFormPrinter, if
1035        // there were any - put the default values first, so that if
1036        // there's a conflict they'll be overridden.
1037        if ( $default_args != null ) {
1038            $other_args = array_merge( $default_args, $other_args );
1039        }
1040
1041        foreach ( $other_args as $argname => $argvalue ) {
1042            if ( is_string( $argvalue ) ) {
1043                $other_args[$argname] =
1044                    PFFormPrinter::getParsedValue( $parser, $argvalue );
1045            }
1046        }
1047
1048        return $other_args;
1049    }
1050}