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