Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 505
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 / 505
0.00% covered (danger)
0.00%
0 / 35
63252
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 / 177
0.00% covered (danger)
0.00%
0 / 1
5112
 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 / 76
0.00% covered (danger)
0.00%
0 / 1
1806
 valueStringToLabels
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
132
 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\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                            // The key containing the actual title of the page
674                            $cur_values = array_keys( PFMappingUtils::getLabelsForTitles( $cur_values, true ) );
675                            if ( $hasList ) {
676                                $cur_values['is_list'] = $hasList;
677                            }
678                        }
679
680                    } else {
681                        foreach ( $field_query_val as $key => $val ) {
682                            $cur_values[$key] = $val;
683                        }
684                    }
685                    return PFFormPrinter::getStringFromPassedInArray( $cur_values, $delimiter, $is_autoedit );
686                } else {
687                    $field_query_val = trim( $field_query_val );
688                    if ( $map_field && $this->mPossibleValues !== null ) {
689                        // this should be replaced with an input type neutral way of
690                        // figuring out if this scalar input type is a list
691                        if ( $this->mInputType == "tokens" ) {
692                            $this->mIsList = true;
693                        }
694                        if ( $this->mIsList ) {
695                            $cur_values = array_map( 'trim', explode( $delimiter, $field_query_val ) );
696                            foreach ( $cur_values as $key => $val ) {
697                                $cur_values[$key] = PFValuesUtils::labelToValue( $val, $this->mPossibleValues );
698                            }
699                            return implode( $delimiter, $cur_values );
700                        }
701                        return PFValuesUtils::labelToValue( $field_query_val, $this->mPossibleValues );
702                    }
703                    return $field_query_val;
704                }
705            }
706            if ( !$form_submitted && $field_query_val != '' ) {
707                if ( is_array( $field_query_val ) ) {
708                    return PFFormPrinter::getStringFromPassedInArray( $field_query_val, $delimiter );
709                }
710                return $field_query_val;
711            }
712        }
713
714        // Default values in new instances of multiple-instance
715        // templates should always be set, even for existing pages.
716        $part_of_multiple = array_key_exists( 'part_of_multiple', $this->mFieldArgs );
717        $printing_starter_instance = $part_of_multiple && $all_instances_printed;
718        if ( ( !$source_is_page || $printing_starter_instance ) && !$form_submitted ) {
719            if ( $this->mDefaultValue !== null ) {
720                // Set to the default value specified in the form, if it's there.
721                return $this->mDefaultValue;
722            } elseif ( $this->mPreloadPage ) {
723                return PFFormUtils::getPreloadedText( $this->mPreloadPage );
724            }
725        }
726
727        // We're still here...
728        return null;
729    }
730
731    /**
732     * Map a template field value into labels.
733     * @param string $valueString
734     * @param string $delimiter
735     * @param bool $formSubmitted
736     * @return string|string[]
737     */
738    public function valueStringToLabels( $valueString, $delimiter, $formSubmitted ) {
739        if ( $valueString === null || trim( $valueString ) === '' ||
740            $this->mPossibleValues === null ) {
741            return $valueString;
742        }
743        if ( $delimiter !== null ) {
744            $values = array_map( 'trim', explode( $delimiter, $valueString ) );
745        } else {
746            $values = [ $valueString ];
747        }
748
749        $maxValues = PFValuesUtils::getMaxValuesToRetrieve();
750        if ( $formSubmitted && ( count( $this->mPossibleValues ) >= $maxValues ) ) {
751            // Remote autocompletion.
752            $mappedValues = PFMappingUtils::getMappedValuesForInput( $values, $this->getFieldArgs() );
753            return array_values( $mappedValues );
754        }
755
756        $labels = [];
757        foreach ( $values as $value ) {
758            if ( $value != '' ) {
759                if ( array_key_exists( $value, $this->mPossibleValues ) ) {
760                    $labels[] = $this->mPossibleValues[$value];
761                } else {
762                    $labels[] = $value;
763                }
764            }
765        }
766
767        // Most form input types expect a string, and not an array.
768        if ( count( $labels ) == 1 ) {
769            return $labels[0];
770        }
771
772        return $labels;
773    }
774
775    public function additionalHTMLForInput( $cur_value, $field_name, $template_name ) {
776        $text = '';
777
778        // Add a field just after the hidden field, within the HTML, to
779        // locate where the multiple-templates HTML, stored in
780        // $multipleTemplateString, should be inserted.
781        if ( $this->mHoldsTemplate ) {
782            $text .= PFFormPrinter::makePlaceholderInFormHTML( PFFormPrinter::placeholderFormat( $template_name, $field_name ) );
783        }
784
785        // If this field is disabled, add a hidden field holding
786        // the value of this field, because disabled inputs for some
787        // reason don't submit their value.
788        if ( $this->mIsDisabled ) {
789            if ( $field_name == 'free text' || $field_name == '#freetext#' ) {
790                $text .= Html::hidden( 'pf_free_text', '!free_text!' );
791            } else {
792                if ( is_array( $cur_value ) ) {
793                    $delimiter = $this->mFieldArgs['delimiter'];
794                    $text .= Html::hidden( $this->mInputName, implode( $delimiter, $cur_value ) );
795                } else {
796                    $text .= Html::hidden( $this->mInputName, $cur_value );
797                }
798            }
799        }
800
801        if ( $this->hasFieldArg( 'mapping template' ) ||
802            $this->hasFieldArg( 'mapping property' ) ||
803            ( $this->hasFieldArg( 'mapping cargo table' ) &&
804            $this->hasFieldArg( 'mapping cargo field' ) ) ||
805            $this->mUseDisplayTitle ) {
806            if ( $this->hasFieldArg( 'part_of_multiple' ) ) {
807                $text .= Html::hidden( $template_name . '[num][map_field][' . $field_name . ']', 'true' );
808            } else {
809                $text .= Html::hidden( $template_name . '[map_field][' . $field_name . ']', 'true' );
810            }
811        }
812
813        if ( $this->hasFieldArg( 'unique' ) ) {
814            global $wgPageFormsFieldNum;
815
816            $semantic_property = $this->template_field->getSemanticProperty();
817            if ( $semantic_property != null ) {
818                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_property', $semantic_property );
819            }
820            $fullCargoField = $this->template_field->getFullCargoField();
821            if ( $fullCargoField != null ) {
822                // It's inefficient to get these values via
823                // text parsing, but oh well.
824                [ $cargo_table, $cargo_field ] = explode( '|', $fullCargoField, 2 );
825                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_cargo_table', $cargo_table );
826                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_cargo_field', $cargo_field );
827            }
828            if ( $this->hasFieldArg( 'unique_for_category' ) ) {
829                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_for_category', $this->getFieldArg( 'unique_for_category' ) );
830            }
831            if ( $this->hasFieldArg( 'unique_for_namespace' ) ) {
832                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_for_namespace', $this->getFieldArg( 'unique_for_namespace' ) );
833            }
834            if ( $this->hasFieldArg( 'unique_for_concept' ) ) {
835                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_for_concept', $this->getFieldArg( 'unique_for_concept' ) );
836            }
837        }
838        return $text;
839    }
840
841    /**
842     * For now, HTML of an individual field depends on whether or not it's
843     * part of multiple-instance template; this may change if handling of
844     * such templates in form definitions gets more sophisticated.
845     *
846     * @param bool $part_of_multiple
847     * @param bool $is_last_field_in_template
848     * @return string
849     */
850    function createMarkup( $part_of_multiple, $is_last_field_in_template ) {
851        $text = "";
852        $descPlaceholder = "";
853        $textBeforeField = "";
854
855        if ( array_key_exists( "Description", $this->mDescriptionArgs ) ) {
856            $fieldDesc = $this->mDescriptionArgs['Description'];
857            if ( $fieldDesc != '' ) {
858                if ( isset( $this->mDescriptionArgs['DescriptionTooltipMode'] ) ) {
859                    // The wikitext we use for tooltips
860                    // depends on which other extensions
861                    // are installed.
862                    if ( class_exists( 'RegularTooltipsParser' ) ) {
863                        // RegularTooltips
864                        $descPlaceholder = " {{#info-tooltip:$fieldDesc}}";
865                    } elseif ( defined( 'SMW_VERSION' ) ) {
866                        // Semantic MediaWiki
867                        $descPlaceholder = " {{#info:$fieldDesc}}";
868                    } elseif ( class_exists( 'SimpleTooltipParserFunction' ) ) {
869                        // SimpleTooltip
870                        $descPlaceholder = " {{#tip-info:$fieldDesc}}";
871                    } else {
872                        // Don't make it a tooltip.
873                        $descPlaceholder = '<br><p class="pfFieldDescription" style="font-size:0.7em; color:gray;">' . $fieldDesc . '</p>';
874                    }
875                } else {
876                    $descPlaceholder = '<br><p class="pfFieldDescription" style="font-size:0.7em; color:gray;">' . $fieldDesc . '</p>';
877                }
878            }
879        }
880
881        if ( array_key_exists( "TextBeforeField", $this->mDescriptionArgs ) ) {
882            $textBeforeField = $this->mDescriptionArgs['TextBeforeField'];
883        }
884
885        $fieldLabel = $this->template_field->getLabel();
886        if ( $fieldLabel == '' ) {
887            $fieldLabel = $this->template_field->getFieldName();
888        }
889        if ( $textBeforeField != '' ) {
890            $fieldLabel = $textBeforeField . ' ' . $fieldLabel;
891        }
892
893        if ( $part_of_multiple ) {
894            $text .= "'''$fieldLabel:''' $descPlaceholder";
895        } else {
896            $text .= "$fieldLabel$descPlaceholder\n";
897        }
898
899        if ( !$part_of_multiple ) {
900            $text .= "| ";
901        }
902        $text .= "{{{field|" . $this->template_field->getFieldName();
903        if ( $this->mIsHidden ) {
904            $text .= "|hidden";
905        } elseif ( $this->getInputType() !== null && $this->getInputType() !== '' ) {
906            $text .= "|input type=" . $this->getInputType();
907        }
908        foreach ( $this->mFieldArgs as $arg => $value ) {
909            if ( $value === true ) {
910                $text .= "|$arg";
911            } elseif ( $arg === 'uploadable' ) {
912                // Are there similar value-less arguments
913                // that need to be handled here?
914                $text .= "|$arg";
915            } else {
916                $text .= "|$arg=$value";
917            }
918        }
919
920        // Special handling if neither SMW nor Cargo are installed - the
921        // form has to handle stuff that otherwise would go in the
922        // template.
923        if (
924            !defined( 'SMW_VERSION' ) &&
925            !defined( 'CARGO_VERSION' ) &&
926            !array_key_exists( 'values', $this->mFieldArgs ) &&
927            is_array( $this->template_field->getPossibleValues() ) &&
928            count( $this->template_field->getPossibleValues() ) > 0
929        ) {
930            if ( $this->getInputType() == null ) {
931                if ( $this->template_field->isList() ) {
932                    $text .= '|input type=checkboxes';
933                } else {
934                    $text .= '|input type=dropdown';
935                }
936            }
937            $delimiter = ',';
938            if ( $this->template_field->isList() ) {
939                $delimiter = $this->template_field->getDelimiter();
940                if ( $delimiter == '' ) {
941                    $delimiter = ',';
942                }
943                // @todo - we need to add a "|delimiter=" param
944                // here too, if #template_params is not being
945                // called in the template.
946            }
947            $text .= '|values=' . implode( $delimiter, $this->template_field->getPossibleValues() );
948        }
949
950        if ( $this->mIsMandatory ) {
951            $text .= "|mandatory";
952        } elseif ( $this->mIsRestricted ) {
953            $text .= "|restricted";
954        }
955        $text .= "}}}\n";
956        if ( $part_of_multiple ) {
957            $text .= "\n";
958        } elseif ( !$is_last_field_in_template ) {
959            $text .= "|-\n";
960        }
961        return $text;
962    }
963
964    function getArgumentsForInputCallSMW( array &$other_args ) {
965        if ( $this->template_field->getSemanticProperty() !== '' &&
966            !array_key_exists( 'semantic_property', $other_args ) ) {
967            $other_args['semantic_property'] = $this->template_field->getSemanticProperty();
968            $other_args['property_type'] = $this->template_field->getPropertyType();
969        }
970        // If autocompletion hasn't already been hardcoded in the form,
971        // and it's a property of type page, or a property of another
972        // type with 'autocomplete' specified, set the necessary
973        // parameters.
974        if ( !array_key_exists( 'autocompletion source', $other_args ) ) {
975            if ( $this->template_field->getPropertyType() == '_wpg' ) {
976                $other_args['autocompletion source'] = $this->template_field->getSemanticProperty();
977                $other_args['autocomplete field type'] = 'property';
978            } elseif ( array_key_exists( 'autocomplete', $other_args ) || array_key_exists( 'remote autocompletion', $other_args ) ) {
979                $other_args['autocompletion source'] = $this->template_field->getSemanticProperty();
980                $other_args['autocomplete field type'] = 'property';
981            }
982        }
983    }
984
985    function getArgumentsForInputCallCargo( array &$other_args ) {
986        $fullCargoField = $this->template_field->getFullCargoField();
987        if ( $fullCargoField !== null &&
988            array_key_exists( 'cargo where', $other_args ) ) {
989            $fullCargoField .= '|' . $other_args['cargo where'];
990        }
991        if ( $fullCargoField !== null &&
992            !array_key_exists( 'full_cargo_field', $other_args ) ) {
993            $other_args['full_cargo_field'] = $fullCargoField;
994        }
995
996        if ( $this->template_field->getFieldType() == 'Hierarchy' ) {
997            $other_args['structure'] = $this->template_field->getHierarchyStructure();
998        }
999
1000        if ( !array_key_exists( 'autocompletion source', $other_args ) ) {
1001            if (
1002                $this->template_field->getFieldType() == 'Page' ||
1003                array_key_exists( 'autocomplete', $other_args ) ||
1004                array_key_exists( 'remote autocompletion', $other_args )
1005            ) {
1006                if ( array_key_exists( 'mapping cargo table', $other_args ) &&
1007                array_key_exists( 'mapping cargo field', $other_args ) ) {
1008                    $mapping_cargo_field = $other_args[ 'mapping cargo field' ];
1009                    $mapping_cargo_table = $other_args[ 'mapping cargo table' ];
1010                    $other_args['autocompletion source'] = $mapping_cargo_table . '|' . $mapping_cargo_field;
1011                } else {
1012                    $other_args['autocompletion source'] = $fullCargoField;
1013                }
1014                $other_args['autocomplete field type'] = 'cargo field';
1015            }
1016        }
1017    }
1018
1019    /**
1020     * Since Page Forms uses a hook system for the functions that
1021     * create HTML inputs, most arguments are contained in the "$other_args"
1022     * array - create this array, using the attributes of this form
1023     * field and the template field it corresponds to, if any.
1024     * @param array|null $default_args
1025     * @return array
1026     */
1027    function getArgumentsForInputCall( ?array $default_args = null ) {
1028        $parser = PFUtils::getParser();
1029
1030        // start with the arguments array already defined
1031        $other_args = $this->mFieldArgs;
1032        // a value defined for the form field should always supersede
1033        // the coresponding value for the template field
1034        if ( $this->mPossibleValues != null ) {
1035            $other_args['possible_values'] = $this->mPossibleValues;
1036        } else {
1037            $other_args['possible_values'] = $this->template_field->getPossibleValues();
1038            if ( $this->hasFieldArg( 'mapping using translate' ) ) {
1039                $mappedValues = PFMappingUtils::getValuesWithTranslateMapping( $other_args['possible_values'], $other_args['mapping using translate'] );
1040                $other_args['value_labels'] = array_values( $mappedValues );
1041            } else {
1042                $other_args['value_labels'] = $this->template_field->getValueLabels();
1043            }
1044        }
1045        $other_args['is_list'] = ( $this->mIsList || $this->template_field->isList() );
1046        if ( $this->template_field->isMandatory() ) {
1047            $other_args['mandatory'] = true;
1048        }
1049        if ( $this->template_field->isUnique() ) {
1050            $other_args['unique'] = true;
1051        }
1052
1053        // Now add some extension-specific arguments to the input call.
1054        if ( defined( 'CARGO_VERSION' ) ) {
1055            $this->getArgumentsForInputCallCargo( $other_args );
1056        }
1057        if ( defined( 'SMW_VERSION' ) ) {
1058            $this->getArgumentsForInputCallSMW( $other_args );
1059        }
1060
1061        // Now merge in the default values set by PFFormPrinter, if
1062        // there were any - put the default values first, so that if
1063        // there's a conflict they'll be overridden.
1064        if ( $default_args != null ) {
1065            $other_args = array_merge( $default_args, $other_args );
1066        }
1067
1068        foreach ( $other_args as $argname => $argvalue ) {
1069            if ( is_string( $argvalue ) ) {
1070                $other_args[$argname] =
1071                    PFFormPrinter::getParsedValue( $parser, $argvalue );
1072            }
1073        }
1074
1075        return $other_args;
1076    }
1077}