Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 39
CRAP
0.00% covered (danger)
0.00%
0 / 529
PFFormField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 39
64262
0.00% covered (danger)
0.00%
0 / 529
 create
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 12
 getTemplateField
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 setTemplateField
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getInputType
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 setInputType
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 hasFieldArg
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getFieldArgs
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getFieldArg
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 setFieldArg
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getDefaultValue
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 isMandatory
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 setIsMandatory
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 isHidden
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 setIsHidden
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 isRestricted
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 setIsRestricted
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 holdsTemplate
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 isList
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getPossibleValues
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
 getUseDisplayTitle
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getInputName
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getLabel
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getLabelMsg
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 isDisabled
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 setDescriptionArg
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 newFromFormFieldTag
0.00% covered (danger)
0.00%
0 / 1
6972
0.00% covered (danger)
0.00%
0 / 189
 cleanupTranslateTags
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 16
 getCurrentValue
0.00% covered (danger)
0.00%
0 / 1
1640
0.00% covered (danger)
0.00%
0 / 68
 setMappedValues
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 8
 setValuesWithMappingTemplate
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 16
 setValuesWithMappingProperty
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 16
 setValuesWithMappingCargoField
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 16
 labelToValue
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
 valueStringToLabels
0.00% covered (danger)
0.00%
0 / 1
72
0.00% covered (danger)
0.00%
0 / 15
 additionalHTMLForInput
0.00% covered (danger)
0.00%
0 / 1
342
0.00% covered (danger)
0.00%
0 / 34
 createMarkup
0.00% covered (danger)
0.00%
0 / 1
380
0.00% covered (danger)
0.00%
0 / 44
 getArgumentsForInputCallSMW
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 12
 getArgumentsForInputCallCargo
0.00% covered (danger)
0.00%
0 / 1
132
0.00% covered (danger)
0.00%
0 / 20
 getArgumentsForInputCall
0.00% covered (danger)
0.00%
0 / 1
156
0.00% covered (danger)
0.00%
0 / 26
<?php
/**
 *
 * @file
 * @ingroup PF
 */
use MediaWiki\MediaWikiServices;
/**
 * This class is distinct from PFTemplateField in that it represents a template
 * field defined in a form definition - it contains an PFTemplateField object
 * within it (the $template_field variable), along with the other properties
 * for that field that are set within the form.
 * @ingroup PF
 */
class PFFormField {
    /**
     * @var PFTemplateField
     */
    public $template_field;
    private $mInputType;
    private $mIsMandatory;
    private $mIsHidden;
    private $mIsRestricted;
    private $mPossibleValues;
    private $mUseDisplayTitle;
    private $mIsList;
    /**
     * The following fields are not set by the form-creation page
     * (though they could be).
     */
    private $mDefaultValue;
    private $mPreloadPage;
    private $mHoldsTemplate;
    private $mIsUploadable;
    private $mFieldArgs;
    private $mDescriptionArgs;
    private $mLabel;
    private $mLabelMsg;
    /**
     * somewhat of a hack - these two fields are for a field in a specific
     * representation of a form, not the form definition; ideally these
     * should be contained in a third 'field' class, called something like
     * PFFormInstanceField, which holds these fields plus an instance of
     * PFFormField. Too much work?
     */
    private $mInputName;
    private $mIsDisabled;
    /**
     * @param PFTemplateField $template_field
     *
     * @return self
     */
    static function create( PFTemplateField $template_field ) {
        $f = new PFFormField();
        $f->template_field = $template_field;
        $f->mInputType = null;
        $f->mIsMandatory = false;
        $f->mIsHidden = false;
        $f->mIsRestricted = false;
        $f->mIsUploadable = false;
        $f->mPossibleValues = null;
        $f->mUseDisplayTitle = false;
        $f->mFieldArgs = [];
        $f->mDescriptionArgs = [];
        return $f;
    }
    /**
     * @return PFTemplateField
     */
    public function getTemplateField() {
        return $this->template_field;
    }
    /**
     * @param PFTemplateField $templateField
     */
    public function setTemplateField( $templateField ) {
        $this->template_field = $templateField;
    }
    public function getInputType() {
        return $this->mInputType;
    }
    public function setInputType( $inputType ) {
        $this->mInputType = $inputType;
    }
    public function hasFieldArg( $key ) {
        return array_key_exists( $key, $this->mFieldArgs );
    }
    public function getFieldArgs() {
        return $this->mFieldArgs;
    }
    public function getFieldArg( $key ) {
        return $this->mFieldArgs[$key];
    }
    public function setFieldArg( $key, $value ) {
        $this->mFieldArgs[$key] = $value;
    }
    public function getDefaultValue() {
        return $this->mDefaultValue;
    }
    public function isMandatory() {
        return $this->mIsMandatory;
    }
    public function setIsMandatory( $isMandatory ) {
        $this->mIsMandatory = $isMandatory;
    }
    public function isHidden() {
        return $this->mIsHidden;
    }
    public function setIsHidden( $isHidden ) {
        $this->mIsHidden = $isHidden;
    }
    public function isRestricted() {
        return $this->mIsRestricted;
    }
    public function setIsRestricted( $isRestricted ) {
        $this->mIsRestricted = $isRestricted;
    }
    public function holdsTemplate() {
        return $this->mHoldsTemplate;
    }
    public function isList() {
        return $this->mIsList;
    }
    public function getPossibleValues() {
        if ( $this->mPossibleValues != null ) {
            return $this->mPossibleValues;
        } else {
            return $this->template_field->getPossibleValues();
        }
    }
    public function getUseDisplayTitle() {
        return $this->mUseDisplayTitle;
    }
    public function getInputName() {
        return $this->mInputName;
    }
    public function getLabel() {
        return $this->mLabel;
    }
    public function getLabelMsg() {
        return $this->mLabelMsg;
    }
    public function isDisabled() {
        return $this->mIsDisabled;
    }
    public function setDescriptionArg( $key, $value ) {
        $this->mDescriptionArgs[$key] = $value;
    }
    static function newFromFormFieldTag(
        $tag_components,
        $template,
        $template_in_form,
        $form_is_disabled,
        User $user
    ) {
        global $wgPageFormsEmbeddedTemplates;
        $parser = PFUtils::getParser();
        $f = new PFFormField();
        $f->mFieldArgs = [];
        $field_name = trim( $tag_components[1] );
        $template_name = $template_in_form->getTemplateName();
        // See if this field matches one of the fields defined for this
        // template - if it does, use all available information about
        // that field; if it doesn't, either include it in the form or
        // not, depending on whether the template has a 'strict'
        // setting in the form definition.
        $template_field = $template->getFieldNamed( $field_name );
        if ( $template_field != null ) {
            $f->template_field = $template_field;
        } else {
            if ( $template_in_form->strictParsing() ) {
                $f->template_field = new PFTemplateField();
                $f->mIsList = false;
                return $f;
            }
            $f->template_field = PFTemplateField::create( $field_name, null );
        }
        $embeddedTemplate = $f->template_field->getHoldsTemplate();
        if ( $embeddedTemplate != '' ) {
            $f->mIsHidden = true;
            $f->mHoldsTemplate = true;
            // Store this information so that the embedded/"held"
            // template - which is hopefully after this one in the
            // form definition - can be handled correctly. In forms,
            // both the embedding field and the embedded template are
            // specified as such, but in templates (i.e., with
            // #template_params), it's only the embedding field.
            $wgPageFormsEmbeddedTemplates[$embeddedTemplate] = [ $template_name, $field_name ];
        }
        $semantic_property = null;
        $cargo_table = $cargo_field = $cargo_where = null;
        $show_on_select = [];
        $fullFieldName = $template_name . '[' . $field_name . ']';
        $values = $valuesSourceType = $valuesSource = null;
        // Cycle through the other components.
        for ( $i = 2; $i < count( $tag_components ); $i++ ) {
            $component = trim( $tag_components[$i] );
            if ( $component == 'mandatory' ) {
                $f->mIsMandatory = true;
            } elseif ( $component == 'hidden' ) {
                $f->mIsHidden = true;
            } elseif ( $component == 'restricted' ) {
                $f->mIsRestricted = ( !$user || !$user->isAllowed( 'editrestrictedfields' ) );
            } elseif ( $component == 'list' ) {
                $f->mIsList = true;
            } elseif ( $component == 'unique' ) {
                $f->mFieldArgs['unique'] = true;
            } elseif ( $component == 'edittools' ) {
                // free text only
                $f->mFieldArgs['edittools'] = true;
            }
            $sub_components = array_map( 'trim', explode( '=', $component, 2 ) );
            if ( count( $sub_components ) == 1 ) {
                // add handling for single-value params, for custom input types
                $f->mFieldArgs[$sub_components[0]] = true;
                if ( $component == 'holds template' ) {
                    $f->mIsHidden = true;
                    $f->mHoldsTemplate = true;
                }
            } elseif ( count( $sub_components ) == 2 ) {
                // First, set each value as its own entry in $this->mFieldArgs.
                $f->mFieldArgs[$sub_components[0]] = $sub_components[1];
                // Then, do all special handling.
                if ( $sub_components[0] == 'input type' ) {
                    $f->mInputType = $sub_components[1];
                } elseif ( $sub_components[0] == 'default' ) {
                    // We call recursivePreprocess() here,
                    // and not the more standard
                    // recursiveTagParse(), so that
                    // wikitext in the value, and bare URLs,
                    // will not get turned into HTML.
                    $f->mDefaultValue = $parser->recursivePreprocess( $sub_components[1] );
                } elseif ( $sub_components[0] == 'preload' ) {
                    $f->mPreloadPage = $sub_components[1];
                } elseif ( $sub_components[0] == 'label' ) {
                    $f->mLabel = $sub_components[1];
                } elseif ( $sub_components[0] == 'label msg' ) {
                    $f->mLabelMsg = $sub_components[1];
                } elseif ( $sub_components[0] == 'show on select' ) {
                    // html_entity_decode() is needed to turn '&gt;' to '>'
                    $vals = explode( ';', html_entity_decode( $sub_components[1] ) );
                    foreach ( $vals as $val ) {
                        $val = trim( $val );
                        if ( empty( $val ) ) {
                            continue;
                        }
                        $option_div_pair = explode( '=>', $val, 2 );
                        if ( count( $option_div_pair ) > 1 ) {
                            $option = trim( $parser->recursiveTagParse( $option_div_pair[0] ) );
                            $div_id = $option_div_pair[1];
                            if ( array_key_exists( $div_id, $show_on_select ) ) {
                                $show_on_select[$div_id][] = $option;
                            } else {
                                $show_on_select[$div_id] = [ $option ];
                            }
                        } else {
                            $show_on_select[$val] = [];
                        }
                    }
                } elseif ( $sub_components[0] == 'values' ) {
                    // Handle this one only after
                    // 'delimiter' has also been set.
                    $values = $parser->recursiveTagParse( $sub_components[1] );
                } elseif ( $sub_components[0] == 'values from property' ) {
                    $propertyName = $sub_components[1];
                    $f->mPossibleValues = PFValuesUtils::getAllValuesForProperty( $propertyName );
                } elseif ( $sub_components[0] == 'values from query' ) {
                    $valuesSourceType = 'query';
                    $valuesSource = $sub_components[1];
                } elseif ( $sub_components[0] == 'values from category' ) {
                    $valuesSource = $parser->recursiveTagParse( $sub_components[1] );
                    global $wgCapitalLinks;
                    if ( $wgCapitalLinks ) {
                        $valuesSource = ucfirst( $valuesSource );
                    }
                    $valuesSourceType = 'category';
                } elseif ( $sub_components[0] == 'values from concept' ) {
                    $valuesSourceType = 'concept';
                    $valuesSource = $parser->recursiveTagParse( $sub_components[1] );
                } elseif ( $sub_components[0] == 'values from namespace' ) {
                    $valuesSourceType = 'namespace';
                    $valuesSource = $parser->recursiveTagParse( $sub_components[1] );
                } elseif ( $sub_components[0] == 'values dependent on' ) {
                    global $wgPageFormsDependentFields;
                    $wgPageFormsDependentFields[] = [ $sub_components[1], $fullFieldName ];
                } elseif ( $sub_components[0] == 'unique for category' ) {
                    $f->mFieldArgs['unique'] = true;
                    $f->mFieldArgs['unique_for_category'] = $parser->recursiveTagParse( $sub_components[1] );
                } elseif ( $sub_components[0] == 'unique for namespace' ) {
                    $f->mFieldArgs['unique'] = true;
                    $f->mFieldArgs['unique_for_namespace'] = $parser->recursiveTagParse( $sub_components[1] );
                } elseif ( $sub_components[0] == 'unique for concept' ) {
                    $f->mFieldArgs['unique'] = true;
                    $f->mFieldArgs['unique_for_concept'] = $parser->recursiveTagParse( $sub_components[1] );
                } elseif ( $sub_components[0] == 'property' ) {
                    $semantic_property = $sub_components[1];
                } elseif ( $sub_components[0] == 'cargo table' ) {
                    $cargo_table = $sub_components[1];
                } elseif ( $sub_components[0] == 'cargo field' ) {
                    $cargo_field = $sub_components[1];
                } elseif ( $sub_components[0] == 'cargo where' ) {
                    $cargo_where = $sub_components[1];
                } elseif ( $sub_components[0] == 'default filename' ) {
                    global $wgTitle;
                    $page_name = $wgTitle->getText();
                    if ( $wgTitle->isSpecialPage() ) {
                        // If it's of the form
                        // Special:FormEdit/form/target,
                        // get just the target.
                        $pageNameComponents = explode( '/', $page_name, 3 );
                        if ( count( $pageNameComponents ) == 3 ) {
                            $page_name = $pageNameComponents[2];
                        }
                    }
                    $default_filename = str_replace( '<page name>', $page_name, $sub_components[1] );
                    // Parse value, so default filename can
                    // include parser functions.
                    $default_filename = $parser->recursiveTagParse( $default_filename );
                    $f->mFieldArgs['default filename'] = $default_filename;
                } elseif ( $sub_components[0] == 'restricted' ) {
                    if ( method_exists( MediaWikiServices::class, 'getUserGroupManager' ) ) {
                        // MediaWiki >= 1.35
                        $effectiveGroups = MediaWikiServices::getInstance()->getUserGroupManager()->getUserEffectiveGroups( $user );
                    } else {
                        $effectiveGroups = $user->getEffectiveGroups();
                    }
                    $f->mIsRestricted = !array_intersect(
                        $effectiveGroups, array_map( 'trim', explode( ',', $sub_components[1] ) )
                    );
                }
            }
        }
        // end for
        if ( $valuesSourceType !== null ) {
            $f->mPossibleValues = PFValuesUtils::getAutocompleteValues( $valuesSource, $valuesSourceType );
            if ( in_array( $valuesSourceType, [ 'category', 'namespace', 'concept' ] ) ) {
                global $wgPageFormsUseDisplayTitle;
                $f->mUseDisplayTitle = $wgPageFormsUseDisplayTitle;
            }
        }
        if ( !array_key_exists( 'delimiter', $f->mFieldArgs ) ) {
            $delimiterFromTemplate = $f->getTemplateField()->getDelimiter();
            if ( $delimiterFromTemplate == '' ) {
                $f->mFieldArgs['delimiter'] = ',';
            } else {
                $f->mFieldArgs['delimiter'] = $delimiterFromTemplate;
                $f->mIsList = true;
            }
        }
        $delimiter = $f->mFieldArgs['delimiter'];
        // If the 'values' parameter was set, separate it based on the
        // 'delimiter' parameter, if any.
        if ( $values != null ) {
            // Remove whitespaces, and un-escape characters
            $valuesArray = array_map( 'trim', explode( $delimiter, $values ) );
            $f->mPossibleValues = array_map( 'htmlspecialchars_decode', $valuesArray );
        }
        // If we're using Cargo, there's no equivalent for "values from
        // property" - instead, we just always get the values if a
        // field and table have been specified.
        if ( $f->mPossibleValues === null && defined( 'CARGO_VERSION' ) && $cargo_table != null && $cargo_field != null ) {
            // We only want the non-null values. Ideally this could
            // be done by calling getValuesForCargoField() with
            // an "IS NOT NULL" clause, but unfortunately that fails
            // for array/list fields.
            // Instead of getting involved with all that, we'll just
            // remove the null/blank values afterward.
            $cargoValues = PFValuesUtils::getAllValuesForCargoField( $cargo_table, $cargo_field );
            $f->mPossibleValues = array_filter( $cargoValues, 'strlen' );
        }
        if ( defined( 'CARGO_VERSION' ) && $cargo_where != null ) {
            if ( $cargo_table == null || $cargo_field == null ) {
                $fullCargoField = $f->template_field->getFullCargoField();
                $table_and_field = explode( '|', $fullCargoField );
                $cargo_table = $table_and_field[0];
                $cargo_field = $table_and_field[1];
            }
            $cargoValues = PFValuesUtils::getValuesForCargoField( $cargo_table, $cargo_field, $cargo_where );
            $f->mPossibleValues = array_filter( $cargoValues, 'strlen' );
        }
        if ( $f->mPossibleValues == null ) {
            $f->mPossibleValues = $f->template_field->getPossibleValues();
        }
        $mappingType = null;
        if ( array_key_exists( 'mapping template', $f->mFieldArgs ) ) {
            $mappingType = 'template';
        } elseif ( array_key_exists( 'mapping property', $f->mFieldArgs ) ) {
            $mappingType = 'property';
        } elseif ( array_key_exists( 'mapping cargo table', $f->mFieldArgs ) &&
            array_key_exists( 'mapping cargo field', $f->mFieldArgs ) ) {
            $mappingType = 'cargo field';
        } elseif ( $f->mUseDisplayTitle ) {
            $f->mPossibleValues = PFValuesUtils::disambiguateLabels( $f->mPossibleValues );
        }
        if ( $mappingType !== null && !empty( $f->mPossibleValues ) ) {
            // If we're going to be mapping values, we need to have
            // the exact page name - and if these values come from
            // "values from namespace", the namespace prefix was
            // not included, so we need to add it now.
            if ( $valuesSourceType == 'namespace' && $valuesSource != '' && $valuesSource != 'Main' ) {
                foreach ( $f->mPossibleValues as $index => &$value ) {
                    $value = $valuesSource . ':' . $value;
                }
                // Has to be set to false to not mess up the
                // handling.
                $f->mUseDisplayTitle = false;
            }
            $f->setMappedValues( $mappingType );
        }
        if ( $template_in_form->allowsMultiple() ) {
            $f->mFieldArgs['part_of_multiple'] = true;
        }
        if ( count( $show_on_select ) > 0 ) {
            $f->mFieldArgs['show on select'] = $show_on_select;
        }
        // Disable this field if either the whole form is disabled, or
        // it's a restricted field and user doesn't have sysop privileges.
        $f->mIsDisabled = ( $form_is_disabled || $f->mIsRestricted );
        // Do some data storage specific to the Semantic MediaWiki and
        // Cargo extensions.
        if ( defined( 'SMW_VERSION' ) ) {
            // If a property was set in the form definition,
            // overwrite whatever is set in the template field -
            // this is somewhat of a hack, since parameters set in
            // the form definition are meant to go into the
            // PFFormField object, not the PFTemplateField object
            // it contains;
            // it seemed like too much work, though, to create an
            // PFFormField::setSemanticProperty() function just for
            // this call.
            if ( $semantic_property !== null ) {
                $f->template_field->setSemanticProperty( $semantic_property );
            } else {
                $semantic_property = $f->template_field->getSemanticProperty();
            }
            if ( $semantic_property !== null ) {
                global $wgPageFormsFieldProperties;
                $wgPageFormsFieldProperties[$fullFieldName] = $semantic_property;
            }
        }
        if ( defined( 'CARGO_VERSION' ) ) {
            if ( $cargo_table != null && $cargo_field != null ) {
                $f->template_field->setCargoFieldData( $cargo_table, $cargo_field );
            }
            $fullCargoField = $f->template_field->getFullCargoField();
            if ( $fullCargoField !== null ) {
                global $wgPageFormsCargoFields;
                $wgPageFormsCargoFields[$fullFieldName] = $fullCargoField;
            }
        }
        if ( $template_name === null || $template_name === '' ) {
            $f->mInputName = $field_name;
        } elseif ( $template_in_form->allowsMultiple() ) {
            // 'num' will get replaced by an actual index, either in PHP
            // or in Javascript, later on
            $f->mInputName = $template_name . '[num][' . $field_name . ']';
            $f->setFieldArg( 'origName', $fullFieldName );
        } else {
            $f->mInputName = $fullFieldName;
        }
        return $f;
    }
    function cleanupTranslateTags( &$value ) {
        $i = 0;
        // If there are two tags ("<!--T:X-->") with no content between them, remove the first one.
        while ( preg_match( '/(<!--T:[0-9]+-->\s*)(<!--T:[0-9]+-->)/', $value, $matches ) ) {
            $value = str_replace( $matches[1], '', $value );
            if ( $i++ > 200 ) {
                // Is this necessary?
                break;
            }
        }
        $i = 0;
        // If there is a tag ("<!--T:X-->") at the end, with nothing after, remove it.
        while ( preg_match( '#(<!--T:[0-9]+-->\s*)(</translate>)#', $value, $matches ) ) {
            $value = str_replace( $matches[1], '', $value );
            if ( $i++ > 200 ) {
                // Is this necessary?
                break;
            }
        }
        $i = 0;
        // If there is a tag ("<!--T:X-->") not separated from a template call ("{{ ..."),
        // add a new line between them.
        while ( preg_match( '/(<!--T:[0-9]+-->)({{[^}]+}}\s*)/', $value, $matches ) ) {
            $value = str_replace( $matches[1], $matches[1] . "\n", $value );
            if ( $i++ > 200 ) {
                // Is this necessary?
                break;
            }
        }
    }
    function getCurrentValue( $template_instance_query_values, $form_submitted, $source_is_page, $all_instances_printed, &$val_modifier = null ) {
        // Get the value from the request, if
        // it's there, and if it's not an array.
        $field_name = $this->template_field->getFieldName();
        $delimiter = $this->mFieldArgs['delimiter'];
        $escaped_field_name = str_replace( "'", "\'", $field_name );
        if ( PFUtils::isTranslateEnabled() && $this->hasFieldArg( 'translatable' ) && $this->getFieldArg( 'translatable' ) ) {
            // If this is a translatable field, and both it and its corresponding translate ID tag are passed in, we add it.
            $fieldName = $this->getTemplateField()->getFieldName();
            $fieldNameTag = $fieldName . '_translate_number_tag';
            if ( isset( $template_instance_query_values[$fieldName] ) && isset( $template_instance_query_values[$fieldNameTag] ) ) {
                $tag = $template_instance_query_values[$fieldNameTag];
                if ( !preg_match( '/( |\n)$/', $tag ) ) {
                    $tag .= "\n";
                }
                if ( trim( $template_instance_query_values[$fieldName] ) ) {
                    // Don't add the tag if field content has been removed.
                    $template_instance_query_values[$fieldName] = $tag . $template_instance_query_values[$fieldName];
                }
            }
            // If user has deleted some content, and there is some translate tag ("<!--T:X-->") with no content, remove the tag.
            if ( isset( $template_instance_query_values[$fieldName] ) ) {
                $this->cleanupTranslateTags( $template_instance_query_values[$fieldName] );
            }
        }
        if ( isset( $template_instance_query_values ) &&
            $template_instance_query_values != null &&
            is_array( $template_instance_query_values )
        ) {
            // If the field name contains an apostrophe, the array
            // sometimes has the apostrophe escaped, and sometimes
            // not. For now, just check for both versions.
            // @TODO - figure this out.
            $field_query_val = null;
            if ( array_key_exists( $escaped_field_name, $template_instance_query_values ) ) {
                $field_query_val = $template_instance_query_values[$escaped_field_name];
            } elseif ( array_key_exists( $field_name, $template_instance_query_values ) ) {
                $field_query_val = $template_instance_query_values[$field_name];
            } else {
                // The next checks are to allow for support for appending/prepending with autoedit.
                if ( array_key_exists( "$field_name+", $template_instance_query_values ) ) {
                    $field_query_val = $template_instance_query_values["$field_name+"];
                    $val_modifier = '+';
                } elseif ( array_key_exists( "$field_name-", $template_instance_query_values ) ) {
                    $field_query_val = $template_instance_query_values["$field_name-"];
                    $val_modifier = '-';
                }
            }
            if ( $form_submitted && $field_query_val != '' ) {
                $map_field = false;
                if ( array_key_exists( 'map_field', $template_instance_query_values ) &&
                    array_key_exists( $field_name, $template_instance_query_values['map_field'] ) ) {
                    $map_field = true;
                }
                if ( is_array( $field_query_val ) ) {
                    $cur_values = [];
                    if ( $map_field && $this->mPossibleValues !== null ) {
                        foreach ( $field_query_val as $key => $val ) {
                            $val = trim( $val );
                            if ( $key === 'is_list' ) {
                                $cur_values[$key] = $val;
                            } else {
                                $cur_values[] = $this->labelToValue( $val );
                            }
                        }
                    } else {
                        foreach ( $field_query_val as $key => $val ) {
                            $cur_values[$key] = $val;
                        }
                    }
                    return PFFormPrinter::getStringFromPassedInArray( $cur_values, $delimiter );
                } else {
                    $field_query_val = trim( $field_query_val );
                    if ( $map_field && $this->mPossibleValues !== null ) {
                        // this should be replaced with an input type neutral way of
                        // figuring out if this scalar input type is a list
                        if ( $this->mInputType == "tokens" ) {
                            $this->mIsList = true;
                        }
                        if ( $this->mIsList ) {
                            $cur_values = array_map( 'trim', explode( $delimiter, $field_query_val ) );
                            foreach ( $cur_values as $key => $val ) {
                                $cur_values[$key] = $this->labelToValue( $val );
                            }
                            return implode( $delimiter, $cur_values );
                        }
                        return $this->labelToValue( $field_query_val );
                    }
                    return $field_query_val;
                }
            }
            if ( !$form_submitted && $field_query_val != '' ) {
                if ( is_array( $field_query_val ) ) {
                    $str = PFFormPrinter::getStringFromPassedInArray( $field_query_val, $delimiter );
                } else {
                    $str = $field_query_val;
                }
                return str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], $str );
            }
        }
        // Default values in new instances of multiple-instance
        // templates should always be set, even for existing pages.
        $part_of_multiple = array_key_exists( 'part_of_multiple', $this->mFieldArgs );
        $printing_starter_instance = $part_of_multiple && $all_instances_printed;
        if ( ( !$source_is_page || $printing_starter_instance ) && !$form_submitted ) {
            if ( $this->mDefaultValue !== null ) {
                // Set to the default value specified in the form, if it's there.
                return $this->mDefaultValue;
            } elseif ( $this->mPreloadPage ) {
                return PFFormUtils::getPreloadedText( $this->mPreloadPage );
            }
        }
        // We're still here...
        return null;
    }
    function setMappedValues( $mappingType ) {
        if ( $mappingType == 'template' ) {
            $this->setValuesWithMappingTemplate();
        } elseif ( $mappingType == 'property' ) {
            $this->setValuesWithMappingProperty();
        } elseif ( $mappingType == 'cargo field' ) {
            $this->setValuesWithMappingCargoField();
        }
        $this->mPossibleValues = PFValuesUtils::disambiguateLabels( $this->mPossibleValues );
    }
    /**
     * Helper function to get an array of labels from an array of values
     * given a mapping template.
     */
    function setValuesWithMappingTemplate() {
        $labels = [];
        $templateName = $this->mFieldArgs['mapping template'];
        $title = Title::makeTitleSafe( NS_TEMPLATE, $templateName );
        $templateExists = $title->exists();
        foreach ( $this->mPossibleValues as $index => $value ) {
            if ( $this->mUseDisplayTitle ) {
                $value = $index;
            }
            if ( $templateExists ) {
                $label = trim( PFUtils::getParser()->recursiveTagParse( '{{' . $templateName .
                    '|' . $value . '}}' ) );
                if ( $label == '' ) {
                    $labels[$value] = $value;
                } else {
                    $labels[$value] = $label;
                }
            } else {
                $labels[$value] = $value;
            }
        }
        $this->mPossibleValues = $labels;
    }
    /**
     * Helper function to get an array of labels from an array of values
     * given a mapping property.
     */
    function setValuesWithMappingProperty() {
        $store = PFUtils::getSMWStore();
        if ( $store == null ) {
            return;
        }
        $propertyName = $this->mFieldArgs['mapping property'];
        $labels = [];
        foreach ( $this->mPossibleValues as $index => $value ) {
            if ( $this->mUseDisplayTitle ) {
                $value = $index;
            }
            $labels[$value] = $value;
            $subject = Title::newFromText( $value );
            if ( $subject != null ) {
                $vals = PFValuesUtils::getSMWPropertyValues( $store, $subject, $propertyName );
                if ( count( $vals ) > 0 ) {
                    $labels[$value] = trim( $vals[0] );
                }
            }
        }
        $this->mPossibleValues = $labels;
    }
    /**
     * Helper function to get an array of labels from an array of values
     * given a mapping Cargo table/field.
     */
    function setValuesWithMappingCargoField() {
        $labels = [];
        foreach ( $this->mPossibleValues as $index => $value ) {
            if ( $this->mUseDisplayTitle ) {
                $value = $index;
            }
            $labels[$value] = $value;
            if ( $this->hasFieldArg( 'mapping cargo value field' ) ) {
                $valueField = $this->mFieldArgs['mapping cargo value field'];
            } else {
                $valueField = '_pageName';
            }
            $vals = PFValuesUtils::getValuesForCargoField(
                $this->mFieldArgs['mapping cargo table'],
                $this->mFieldArgs['mapping cargo field'],
                $valueField . '="' . $value . '"'
            );
            if ( count( $vals ) > 0 ) {
                $labels[$value] = html_entity_decode( trim( $vals[0] ) );
            }
        }
        $this->mPossibleValues = $labels;
    }
    /**
     * Map a label back to a value.
     * @param string $label
     * @return string
     */
    function labelToValue( $label ) {
        $value = array_search( $label, $this->mPossibleValues );
        if ( $value === false ) {
            return $label;
        } else {
            return $value;
        }
    }
    /**
     * Map a template field value into labels.
     * @param string $valueString
     * @param string $delimiter
     * @return string|string[]
     */
    public function valueStringToLabels( $valueString, $delimiter ) {
        if ( strlen( trim( $valueString ) ) === 0 ||
            $this->mPossibleValues === null ) {
            return $valueString;
        }
        if ( $delimiter !== null ) {
            $values = array_map( 'trim', explode( $delimiter, $valueString ) );
        } else {
            $values = [ $valueString ];
        }
        $labels = [];
        foreach ( $values as $value ) {
            if ( $value != '' ) {
                if ( array_key_exists( $value, $this->mPossibleValues ) ) {
                    $labels[] = $this->mPossibleValues[$value];
                } else {
                    $labels[] = $value;
                }
            }
        }
        if ( count( $labels ) > 1 ) {
            return $labels;
        } else {
            return $labels[0];
        }
    }
    public function additionalHTMLForInput( $cur_value, $field_name, $template_name ) {
        $text = '';
        // Add a field just after the hidden field, within the HTML, to
        // locate where the multiple-templates HTML, stored in
        // $multipleTemplateString, should be inserted.
        if ( $this->mHoldsTemplate ) {
            $text .= PFFormPrinter::makePlaceholderInFormHTML( PFFormPrinter::placeholderFormat( $template_name, $field_name ) );
        }
        // If this field is disabled, add a hidden field holding
        // the value of this field, because disabled inputs for some
        // reason don't submit their value.
        if ( $this->mIsDisabled ) {
            if ( $field_name == 'free text' || $field_name == '#freetext#' ) {
                $text .= Html::hidden( 'pf_free_text', '!free_text!' );
            } else {
                if ( is_array( $cur_value ) ) {
                    $delimiter = $this->mFieldArgs['delimiter'];
                    $text .= Html::hidden( $this->mInputName, implode( $delimiter, $cur_value ) );
                } else {
                    $text .= Html::hidden( $this->mInputName, $cur_value );
                }
            }
        }
        if ( $this->hasFieldArg( 'mapping template' ) ||
            $this->hasFieldArg( 'mapping property' ) ||
            ( $this->hasFieldArg( 'mapping cargo table' ) &&
            $this->hasFieldArg( 'mapping cargo field' ) ) ||
            $this->mUseDisplayTitle ) {
            if ( $this->hasFieldArg( 'part_of_multiple' ) ) {
                $text .= Html::hidden( $template_name . '[num][map_field][' . $field_name . ']', 'true' );
            } else {
                $text .= Html::hidden( $template_name . '[map_field][' . $field_name . ']', 'true' );
            }
        }
        if ( $this->hasFieldArg( 'unique' ) ) {
            global $wgPageFormsFieldNum;
            $semantic_property = $this->template_field->getSemanticProperty();
            if ( $semantic_property != null ) {
                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_property', $semantic_property );
            }
            $fullCargoField = $this->template_field->getFullCargoField();
            if ( $fullCargoField != null ) {
                // It's inefficient to get these values via
                // text parsing, but oh well.
                list( $cargo_table, $cargo_field ) = explode( '|', $fullCargoField, 2 );
                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_cargo_table', $cargo_table );
                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_cargo_field', $cargo_field );
            }
            if ( $this->hasFieldArg( 'unique_for_category' ) ) {
                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_for_category', $this->getFieldArg( 'unique_for_category' ) );
            }
            if ( $this->hasFieldArg( 'unique_for_namespace' ) ) {
                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_for_namespace', $this->getFieldArg( 'unique_for_namespace' ) );
            }
            if ( $this->hasFieldArg( 'unique_for_concept' ) ) {
                $text .= Html::hidden( 'input_' . $wgPageFormsFieldNum . '_unique_for_concept', $this->getFieldArg( 'unique_for_concept' ) );
            }
        }
        return $text;
    }
    /**
     * For now, HTML of an individual field depends on whether or not it's
     * part of multiple-instance template; this may change if handling of
     * such templates in form definitions gets more sophisticated.
     *
     * @param bool $part_of_multiple
     * @param bool $is_last_field_in_template
     * @return string
     */
    function createMarkup( $part_of_multiple, $is_last_field_in_template ) {
        $text = "";
        $descPlaceholder = "";
        $textBeforeField = "";
        if ( array_key_exists( "Description", $this->mDescriptionArgs ) ) {
            $fieldDesc = $this->mDescriptionArgs['Description'];
            if ( $fieldDesc != '' ) {
                if ( isset( $this->mDescriptionArgs['DescriptionTooltipMode'] ) ) {
                    // The wikitext we use for tooltips
                    // depends on which other extensions
                    // are installed.
                    if ( defined( 'SMW_VERSION' ) ) {
                        // Semantic MediaWiki
                        $descPlaceholder = " {{#info:$fieldDesc}}";
                    } elseif ( class_exists( 'SimpleTooltipParserFunction' ) ) {
                        // SimpleTooltip
                        $descPlaceholder = " {{#tip-info:$fieldDesc}}";
                    } else {
                        // Don't make it a tooltip.
                        $descPlaceholder = '<br><p class="pfFieldDescription" style="font-size:0.7em; color:gray;">' . $fieldDesc . '</p>';
                    }
                } else {
                    $descPlaceholder = '<br><p class="pfFieldDescription" style="font-size:0.7em; color:gray;">' . $fieldDesc . '</p>';
                }
            }
        }
        if ( array_key_exists( "TextBeforeField", $this->mDescriptionArgs ) ) {
            $textBeforeField = $this->mDescriptionArgs['TextBeforeField'];
        }
        $fieldLabel = $this->template_field->getLabel();
        if ( $textBeforeField != '' ) {
            $fieldLabel = $textBeforeField . ' ' . $fieldLabel;
        }
        if ( $part_of_multiple ) {
            $text .= "'''$fieldLabel:''' $descPlaceholder";
        } else {
            $text .= "$fieldLabel$descPlaceholder\n";
        }
        if ( !$part_of_multiple ) {
            $text .= "| ";
        }
        $text .= "{{{field|" . $this->template_field->getFieldName();
        if ( $this->mIsHidden ) {
            $text .= "|hidden";
        } elseif ( $this->getInputType() !== null ) {
            $text .= "|input type=" . $this->getInputType();
        }
        foreach ( $this->mFieldArgs as $arg => $value ) {
            if ( $value === true ) {
                $text .= "|$arg";
            } elseif ( $arg === 'uploadable' ) {
                // Are there similar value-less arguments
                // that need to be handled here?
                $text .= "|$arg";
            } else {
                $text .= "|$arg=$value";
            }
        }
        if ( $this->mIsMandatory ) {
            $text .= "|mandatory";
        } elseif ( $this->mIsRestricted ) {
            $text .= "|restricted";
        }
        $text .= "}}}\n";
        if ( $part_of_multiple ) {
            $text .= "\n";
        } elseif ( !$is_last_field_in_template ) {
            $text .= "|-\n";
        }
        return $text;
    }
    function getArgumentsForInputCallSMW( array &$other_args ) {
        if ( $this->template_field->getSemanticProperty() !== '' &&
            !array_key_exists( 'semantic_property', $other_args ) ) {
            $other_args['semantic_property'] = $this->template_field->getSemanticProperty();
            $other_args['property_type'] = $this->template_field->getPropertyType();
        }
        // If autocompletion hasn't already been hardcoded in the form,
        // and it's a property of type page, or a property of another
        // type with 'autocomplete' specified, set the necessary
        // parameters.
        if ( !array_key_exists( 'autocompletion source', $other_args ) ) {
            if ( $this->template_field->getPropertyType() == '_wpg' ) {
                $other_args['autocompletion source'] = $this->template_field->getSemanticProperty();
                $other_args['autocomplete field type'] = 'property';
            } elseif ( array_key_exists( 'autocomplete', $other_args ) || array_key_exists( 'remote autocompletion', $other_args ) ) {
                $other_args['autocompletion source'] = $this->template_field->getSemanticProperty();
                $other_args['autocomplete field type'] = 'property';
            }
        }
    }
    function getArgumentsForInputCallCargo( array &$other_args ) {
        $fullCargoField = $this->template_field->getFullCargoField();
        if ( $fullCargoField !== null &&
            !array_key_exists( 'full_cargo_field', $other_args ) ) {
                if ( array_key_exists( 'cargo where', $other_args ) ) {
                    $other_args['full_cargo_field'] = $fullCargoField . '|' . $other_args['cargo where'];
                } else {
                    $other_args['full_cargo_field'] = $fullCargoField;
                }
        }
        if ( $this->template_field->getFieldType() == 'Hierarchy' ) {
            $other_args['structure'] = $this->template_field->getHierarchyStructure();
        }
        if ( !array_key_exists( 'autocompletion source', $other_args ) ) {
            if (
                $this->template_field->getFieldType() == 'Page' ||
                array_key_exists( 'autocomplete', $other_args ) ||
                array_key_exists( 'remote autocompletion', $other_args )
            ) {
                if ( array_key_exists( 'mapping cargo table', $other_args ) &&
                array_key_exists( 'mapping cargo field', $other_args ) ) {
                    $mapping_cargo_field = $other_args[ 'mapping cargo field' ];
                    $mapping_cargo_table = $other_args[ 'mapping cargo table' ];
                    $other_args['autocompletion source'] = $mapping_cargo_table . '|' . $mapping_cargo_field;
                } else {
                    $other_args['autocompletion source'] = $this->template_field->getFullCargoField();
                }
                $other_args['autocomplete field type'] = 'cargo field';
            }
        }
    }
    /**
     * Since Page Forms uses a hook system for the functions that
     * create HTML inputs, most arguments are contained in the "$other_args"
     * array - create this array, using the attributes of this form
     * field and the template field it corresponds to, if any.
     * @param array|null $default_args
     * @return array
     */
    function getArgumentsForInputCall( array $default_args = null ) {
        $parser = PFUtils::getParser();
        // start with the arguments array already defined
        $other_args = $this->mFieldArgs;
        // a value defined for the form field should always supersede
        // the coresponding value for the template field
        if ( $this->mPossibleValues != null ) {
            $other_args['possible_values'] = $this->mPossibleValues;
        } else {
            $other_args['possible_values'] = $this->template_field->getPossibleValues();
            if ( $this->hasFieldArg( 'mapping using translate' ) ) {
                $other_args['value_labels'] = [];
                foreach ( $other_args['possible_values'] as $key ) {
                    $other_args['value_labels'][$key] = $parser->recursiveTagParse( '{{int:' . $this->getFieldArg( 'mapping using translate' ) . $key . '}}' );
                }
            } else {
                $other_args['value_labels'] = $this->template_field->getValueLabels();
            }
        }
        $other_args['is_list'] = ( $this->mIsList || $this->template_field->isList() );
        if ( $this->template_field->isMandatory() ) {
            $other_args['mandatory'] = true;
        }
        if ( $this->template_field->isUnique() ) {
            $other_args['unique'] = true;
        }
        // Now add some extension-specific arguments to the input call.
        if ( defined( 'CARGO_VERSION' ) ) {
            $this->getArgumentsForInputCallCargo( $other_args );
        }
        if ( defined( 'SMW_VERSION' ) ) {
            $this->getArgumentsForInputCallSMW( $other_args );
        }
        // Now merge in the default values set by PFFormPrinter, if
        // there were any - put the default values first, so that if
        // there's a conflict they'll be overridden.
        if ( $default_args != null ) {
            $other_args = array_merge( $default_args, $other_args );
        }
        foreach ( $other_args as $argname => $argvalue ) {
            if ( is_string( $argvalue ) ) {
                $other_args[$argname] =
                    $parser->recursiveTagParse( $argvalue );
            }
        }
        return $other_args;
    }
}