Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
19.35% covered (danger)
19.35%
6 / 31
CRAP
22.99% covered (danger)
22.99%
237 / 1031
PFFormPrinter
0.00% covered (danger)
0.00%
0 / 1
19.35% covered (danger)
19.35%
6 / 31
79460.59
22.99% covered (danger)
22.99%
237 / 1031
 __construct
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
44 / 44
 setSemanticTypeHook
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setCargoTypeHook
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setInputTypeHook
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 registerInputType
100.00% covered (success)
100.00%
1 / 1
13
100.00% covered (success)
100.00%
40 / 40
 getInputType
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
 getDefaultInputTypeSMW
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 7
 getDefaultInputTypeCargo
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 7
 getPossibleInputTypesSMW
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 7
 getPossibleInputTypesCargo
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 7
 getAllInputTypes
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 showDeletionLog
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
 strReplaceFirst
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 6
 placeholderFormat
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
 makePlaceholderInFormHTML
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 multipleTemplateStartHTML
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 10
 multipleTemplateInstanceTableHTML
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 8
 multipleTemplateInstanceHTML
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 11
 multipleTemplateEndHTML
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 13
 tableHTML
0.00% covered (danger)
0.00%
0 / 1
156
0.00% covered (danger)
0.00%
0 / 46
 getSpreadsheetAutocompleteAttributes
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 15
 spreadsheetHTML
0.00% covered (danger)
0.00%
0 / 1
420
0.00% covered (danger)
0.00%
0 / 62
 getStringForCurrentTime
0.00% covered (danger)
0.00%
0 / 1
72
0.00% covered (danger)
0.00%
0 / 29
 getStringFromPassedInArray
0.00% covered (danger)
0.00%
0 / 1
462
0.00% covered (danger)
0.00%
0 / 46
 displayLoadingImage
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
6 / 6
 formHTML
0.00% covered (danger)
0.00%
0 / 1
28269.11
24.87% covered (danger)
24.87%
141 / 567
 getCargoBasedMapping
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 12
 formFieldHTML
0.00% covered (danger)
0.00%
0 / 1
306
0.00% covered (danger)
0.00%
0 / 42
 addTranslatableInput
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 11
 createFormFieldTranslateTag
0.00% covered (danger)
0.00%
0 / 1
90
0.00% covered (danger)
0.00%
0 / 12
 generateUUID
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 6
<?php
/**
 * Handles the creation and running of a user-created form.
 *
 * @author Yaron Koren
 * @author Nils Oppermann
 * @author Jeffrey Stuckman
 * @author Harold Solbrig
 * @author Daniel Hansch
 * @author Stephan Gambke
 * @author LY Meng
 * @file
 * @ingroup PF
 */
use MediaWiki\MediaWikiServices;
class PFFormPrinter {
    public $mSemanticTypeHooks;
    public $mCargoTypeHooks;
    public $mInputTypeHooks;
    public $standardInputsIncluded;
    public $mPageTitle;
    private $mInputTypeClasses;
    private $mDefaultInputForPropType;
    private $mDefaultInputForPropTypeList;
    private $mPossibleInputsForPropType;
    private $mPossibleInputsForPropTypeList;
    private $mDefaultInputForCargoType;
    private $mDefaultInputForCargoTypeList;
    private $mPossibleInputsForCargoType;
    private $mPossibleInputsForCargoTypeList;
    public function __construct() {
        global $wgPageFormsDisableOutsideServices;
        // Initialize variables.
        $this->mSemanticTypeHooks = [];
        $this->mCargoTypeHooks = [];
        $this->mInputTypeHooks = [];
        $this->mInputTypeClasses = [];
        $this->mDefaultInputForPropType = [];
        $this->mDefaultInputForPropTypeList = [];
        $this->mPossibleInputsForPropType = [];
        $this->mPossibleInputsForPropTypeList = [];
        $this->mDefaultInputForCargoType = [];
        $this->mDefaultInputForCargoTypeList = [];
        $this->mPossibleInputsForCargoType = [];
        $this->mPossibleInputsForCargoTypeList = [];
        $this->standardInputsIncluded = false;
        $this->registerInputType( 'PFTextInput' );
        $this->registerInputType( 'PFTextWithAutocompleteInput' );
        $this->registerInputType( 'PFTextAreaInput' );
        $this->registerInputType( 'PFTextAreaWithAutocompleteInput' );
        $this->registerInputType( 'PFDateInput' );
        $this->registerInputType( 'PFStartDateInput' );
        $this->registerInputType( 'PFEndDateInput' );
        $this->registerInputType( 'PFDatePickerInput' );
        $this->registerInputType( 'PFDateTimePicker' );
        $this->registerInputType( 'PFDateTimeInput' );
        $this->registerInputType( 'PFStartDateTimeInput' );
        $this->registerInputType( 'PFEndDateTimeInput' );
        $this->registerInputType( 'PFYearInput' );
        $this->registerInputType( 'PFCheckboxInput' );
        $this->registerInputType( 'PFDropdownInput' );
        $this->registerInputType( 'PFRadioButtonInput' );
        $this->registerInputType( 'PFCheckboxesInput' );
        $this->registerInputType( 'PFListBoxInput' );
        $this->registerInputType( 'PFComboBoxInput' );
        $this->registerInputType( 'PFTreeInput' );
        $this->registerInputType( 'PFTokensInput' );
        $this->registerInputType( 'PFRegExpInput' );
        $this->registerInputType( 'PFRatingInput' );
        // Add this if the Semantic Maps extension is not
        // included, or if it's SM (really Maps) v4.0 or higher.
        if ( !$wgPageFormsDisableOutsideServices ) {
            if ( !defined( 'SM_VERSION' ) || version_compare( SM_VERSION, '4.0', '>=' ) ) {
                $this->registerInputType( 'PFGoogleMapsInput' );
            }
            $this->registerInputType( 'PFOpenLayersInput' );
            $this->registerInputType( 'PFLeafletInput' );
        }
        // All-purpose setup hook.
        // Avoid PHP 7.1 warning from passing $this by reference.
        $formPrinterRef = $this;
        Hooks::run( 'PageForms::FormPrinterSetup', [ &$formPrinterRef ] );
    }
    public function setSemanticTypeHook( $type, $is_list, $class_name, $default_args ) {
        $this->mSemanticTypeHooks[$type][$is_list] = [ $class_name, $default_args ];
    }
    public function setCargoTypeHook( $type, $is_list, $class_name, $default_args ) {
        $this->mCargoTypeHooks[$type][$is_list] = [ $class_name, $default_args ];
    }
    public function setInputTypeHook( $input_type, $class_name, $default_args ) {
        $this->mInputTypeHooks[$input_type] = [ $class_name, $default_args ];
    }
    /**
     * Register all information about the passed-in form input class.
     *
     * @param string $inputTypeClass The full qualified class name representing the new input.
     * Must be derived from PFFormInput.
     */
    public function registerInputType( $inputTypeClass ) {
        $inputTypeName = call_user_func( [ $inputTypeClass, 'getName' ] );
        $this->mInputTypeClasses[$inputTypeName] = $inputTypeClass;
        $this->setInputTypeHook( $inputTypeName, $inputTypeClass, [] );
        $defaultProperties = call_user_func( [ $inputTypeClass, 'getDefaultPropTypes' ] );
        foreach ( $defaultProperties as $propertyType => $additionalValues ) {
            $this->setSemanticTypeHook( $propertyType, false, $inputTypeClass, $additionalValues );
            $this->mDefaultInputForPropType[$propertyType] = $inputTypeName;
        }
        $defaultPropertyLists = call_user_func( [ $inputTypeClass, 'getDefaultPropTypeLists' ] );
        foreach ( $defaultPropertyLists as $propertyType => $additionalValues ) {
            $this->setSemanticTypeHook( $propertyType, true, $inputTypeClass, $additionalValues );
            $this->mDefaultInputForPropTypeList[$propertyType] = $inputTypeName;
        }
        $defaultCargoTypes = call_user_func( [ $inputTypeClass, 'getDefaultCargoTypes' ] );
        foreach ( $defaultCargoTypes as $fieldType => $additionalValues ) {
            $this->setCargoTypeHook( $fieldType, false, $inputTypeClass, $additionalValues );
            $this->mDefaultInputForCargoType[$fieldType] = $inputTypeName;
        }
        $defaultCargoTypeLists = call_user_func( [ $inputTypeClass, 'getDefaultCargoTypeLists' ] );
        foreach ( $defaultCargoTypeLists as $fieldType => $additionalValues ) {
            $this->setCargoTypeHook( $fieldType, true, $inputTypeClass, $additionalValues );
            $this->mDefaultInputForCargoTypeList[$fieldType] = $inputTypeName;
        }
        $otherProperties = call_user_func( [ $inputTypeClass, 'getOtherPropTypesHandled' ] );
        foreach ( $otherProperties as $propertyTypeID ) {
            if ( array_key_exists( $propertyTypeID, $this->mPossibleInputsForPropType ) ) {
                $this->mPossibleInputsForPropType[$propertyTypeID][] = $inputTypeName;
            } else {
                $this->mPossibleInputsForPropType[$propertyTypeID] = [ $inputTypeName ];
            }
        }
        $otherPropertyLists = call_user_func( [ $inputTypeClass, 'getOtherPropTypeListsHandled' ] );
        foreach ( $otherPropertyLists as $propertyTypeID ) {
            if ( array_key_exists( $propertyTypeID, $this->mPossibleInputsForPropTypeList ) ) {
                $this->mPossibleInputsForPropTypeList[$propertyTypeID][] = $inputTypeName;
            } else {
                $this->mPossibleInputsForPropTypeList[$propertyTypeID] = [ $inputTypeName ];
            }
        }
        $otherCargoTypes = call_user_func( [ $inputTypeClass, 'getOtherCargoTypesHandled' ] );
        foreach ( $otherCargoTypes as $cargoType ) {
            if ( array_key_exists( $cargoType, $this->mPossibleInputsForCargoType ) ) {
                $this->mPossibleInputsForCargoType[$cargoType][] = $inputTypeName;
            } else {
                $this->mPossibleInputsForCargoType[$cargoType] = [ $inputTypeName ];
            }
        }
        $otherCargoTypeLists = call_user_func( [ $inputTypeClass, 'getOtherCargoTypeListsHandled' ] );
        foreach ( $otherCargoTypeLists as $cargoType ) {
            if ( array_key_exists( $cargoType, $this->mPossibleInputsForCargoTypeList ) ) {
                $this->mPossibleInputsForCargoTypeList[$cargoType][] = $inputTypeName;
            } else {
                $this->mPossibleInputsForCargoTypeList[$cargoType] = [ $inputTypeName ];
            }
        }
        // FIXME: No need to register these functions explicitly. Instead
        // formFieldHTML should call $someInput -> getJsInitFunctionData() and
        // store its return value. formHTML should at some (late) point use the
        // stored data.
        //
        // $initJSFunction = call_user_func( array( $inputTypeClass, 'getJsInitFunctionData' ) );
        // if ( !is_null( $initJSFunction ) ) {
        //     $wgPageFormsInitJSFunctions[] = $initJSFunction;
        // }
        //
        // $validationJSFunctions = call_user_func( array( $inputTypeClass, 'getJsValidationFunctionData' ) );
        // if ( count( $validationJSFunctions ) > 0 ) {
        //     $wgPageFormsValidationJSFunctions = array_merge( $wgPageFormsValidationJSFunctions, $initJSFunction );
        // }
    }
    public function getInputType( $inputTypeName ) {
        if ( array_key_exists( $inputTypeName, $this->mInputTypeClasses ) ) {
            return $this->mInputTypeClasses[$inputTypeName];
        } else {
            return null;
        }
    }
    public function getDefaultInputTypeSMW( $isList, $propertyType ) {
        if ( $isList ) {
            if ( array_key_exists( $propertyType, $this->mDefaultInputForPropTypeList ) ) {
                return $this->mDefaultInputForPropTypeList[$propertyType];
            } else {
                return null;
            }
        } else {
            if ( array_key_exists( $propertyType, $this->mDefaultInputForPropType ) ) {
                return $this->mDefaultInputForPropType[$propertyType];
            } else {
                return null;
            }
        }
    }
    public function getDefaultInputTypeCargo( $isList, $fieldType ) {
        if ( $isList ) {
            if ( array_key_exists( $fieldType, $this->mDefaultInputForCargoTypeList ) ) {
                return $this->mDefaultInputForCargoTypeList[$fieldType];
            } else {
                return null;
            }
        } else {
            if ( array_key_exists( $fieldType, $this->mDefaultInputForCargoType ) ) {
                return $this->mDefaultInputForCargoType[$fieldType];
            } else {
                return null;
            }
        }
    }
    public function getPossibleInputTypesSMW( $isList, $propertyType ) {
        if ( $isList ) {
            if ( array_key_exists( $propertyType, $this->mPossibleInputsForPropTypeList ) ) {
                return $this->mPossibleInputsForPropTypeList[$propertyType];
            } else {
                return [];
            }
        } else {
            if ( array_key_exists( $propertyType, $this->mPossibleInputsForPropType ) ) {
                return $this->mPossibleInputsForPropType[$propertyType];
            } else {
                return [];
            }
        }
    }
    public function getPossibleInputTypesCargo( $isList, $fieldType ) {
        if ( $isList ) {
            if ( array_key_exists( $fieldType, $this->mPossibleInputsForCargoTypeList ) ) {
                return $this->mPossibleInputsForCargoTypeList[$fieldType];
            } else {
                return [];
            }
        } else {
            if ( array_key_exists( $fieldType, $this->mPossibleInputsForCargoType ) ) {
                return $this->mPossibleInputsForCargoType[$fieldType];
            } else {
                return [];
            }
        }
    }
    public function getAllInputTypes() {
        return array_keys( $this->mInputTypeClasses );
    }
    /**
     * Show the set of previous deletions for the page being edited.
     * @param OutputPage $out
     * @return true
     */
    function showDeletionLog( $out ) {
        LogEventsList::showLogExtract( $out, 'delete', $this->mPageTitle->getPrefixedText(),
            '', [ 'lim' => 10,
                'conds' => [ "log_action != 'revision'" ],
                'showIfEmpty' => false,
                'msgKey' => [ 'moveddeleted-notice' ] ]
        );
        return true;
    }
    /**
     * Like PHP's str_replace(), but only replaces the first found
     * instance - unfortunately, str_replace() doesn't allow for that.
     * This code is basically copied directly from
     * http://www.php.net/manual/en/function.str-replace.php#86177
     * - this might make sense in the PFUtils class, if it's useful in
     * other places.
     * @param string $search
     * @param string $replace
     * @param string $subject
     * @return string
     */
    function strReplaceFirst( $search, $replace, $subject ) {
        $firstChar = strpos( $subject, $search );
        if ( $firstChar !== false ) {
            $beforeStr = substr( $subject, 0, $firstChar );
            $afterStr = substr( $subject, $firstChar + strlen( $search ) );
            return $beforeStr . $replace . $afterStr;
        } else {
            return $subject;
        }
    }
    static function placeholderFormat( $templateName, $fieldName ) {
        $templateName = str_replace( '_', ' ', $templateName );
        $fieldName = str_replace( '_', ' ', $fieldName );
        return $templateName . '___' . $fieldName;
    }
    static function makePlaceholderInFormHTML( $str ) {
        return '@insert"HTML_' . $str . '@';
    }
    function multipleTemplateStartHTML( $tif ) {
        // If placeholder is set, it means we want to insert a
        // multiple template form's HTML into the main form's HTML.
        // So, the HTML will be stored in $text.
        $text = "\t" . '<div class="multipleTemplateWrapper">' . "\n";
        $attrs = [ 'class' => 'multipleTemplateList' ];
        if ( $tif->getMinInstancesAllowed() !== null ) {
            $attrs['minimumInstances'] = $tif->getMinInstancesAllowed();
        }
        if ( $tif->getMaxInstancesAllowed() !== null ) {
            $attrs['maximumInstances'] = $tif->getMaxInstancesAllowed();
        }
        if ( $tif->getDisplayedFieldsWhenMinimized() != null ) {
            $attrs['data-displayed-fields-when-minimized'] = $tif->getDisplayedFieldsWhenMinimized();
        }
        $text .= "\t" . Html::openElement( 'div', $attrs ) . "\n";
        return $text;
    }
    /**
     * Creates the HTML for the inner table for every instance of a
     * multiple-instance template in the form.
     * @param bool $form_is_disabled
     * @param string $mainText
     * @return string
     */
    function multipleTemplateInstanceTableHTML( $form_is_disabled, $mainText ) {
        if ( $form_is_disabled ) {
            $addAboveButton = $removeButton = '';
        } else {
            $addAboveButton = Html::element( 'a', [ 'class' => "addAboveButton", 'title' => wfMessage( 'pf_formedit_addanotherabove' )->text() ] );
            $removeButton = Html::element( 'a', [ 'class' => "removeButton", 'title' => wfMessage( 'pf_formedit_remove' )->text() ] );
        }
        $text = <<<END
            <table class="multipleTemplateInstanceTable">
            <tr>
            <td class="instanceRearranger"></td>
            <td class="instanceMain">$mainText</td>
            <td class="instanceAddAbove">$addAboveButton</td>
            <td class="instanceRemove">$removeButton</td>
            </tr>
            </table>
END;
        return $text;
    }
    /**
     * Creates the HTML for a single instance of a multiple-instance
     * template.
     * @param PFTemplateInForm $template_in_form
     * @param bool $form_is_disabled
     * @param string &$section
     * @return string
     */
    function multipleTemplateInstanceHTML( $template_in_form, $form_is_disabled, &$section ) {
        global $wgPageFormsCalendarHTML;
        $wgPageFormsCalendarHTML[$template_in_form->getTemplateName()] = str_replace( '[num]', "[cf]", $section );
        // Add the character "a" onto the instance number of this input
        // in the form, to differentiate the inputs the form starts out
        // with from any inputs added by the Javascript.
        $section = str_replace( '[num]', "[{$template_in_form->getInstanceNum()}a]", $section );
        // @TODO - this replacement should be
        // case- and spacing-insensitive.
        // Also, keeping the "id=" attribute should not be
        // necessary; but currently it is, for "show on select".
        $section = preg_replace_callback(
            '/ id="(.*?)"/',
            static function ( $matches ) {
                $id = htmlspecialchars( $matches[1], ENT_QUOTES );
                return " id=\"$id\" data-origID=\"$id\" ";
            },
            $section
        );
        $text = "\t\t" . Html::rawElement( 'div',
                [
                // The "multipleTemplate" class is there for
                // backwards-compatibility with any custom CSS on people's
                // wikis before PF 2.0.9.
                'class' => "multipleTemplateInstance multipleTemplate"
            ],
            $this->multipleTemplateInstanceTableHTML( $form_is_disabled, $section )
        ) . "\n";
        return $text;
    }
    /**
     * Creates the end of the HTML for a multiple-instance template -
     * including the sections necessary for adding additional instances.
     * @param PFTemplateInForm $template_in_form
     * @param bool $form_is_disabled
     * @param string $section
     * @return string
     */
    function multipleTemplateEndHTML( $template_in_form, $form_is_disabled, $section ) {
        global $wgPageFormsTabIndex;
        $text = "\t\t" . Html::rawElement( 'div',
            [
                'class' => "multipleTemplateStarter",
                'style' => "display: none",
            ],
            $this->multipleTemplateInstanceTableHTML( $form_is_disabled, $section )
        ) . "\n";
        $attributes = [
            'tabIndex' => $wgPageFormsTabIndex,
            'classes' => [ 'multipleTemplateAdder' ],
            'label' => Sanitizer::decodeCharReferences( $template_in_form->getAddButtonText() ),
            'icon' => 'add'
        ];
        if ( $form_is_disabled ) {
            $attributes['disabled'] = true;
            $attributes['classes'] = [];
        }
        $button = new OOUI\ButtonWidget( $attributes );
        $text .= <<<END
    </div><!-- multipleTemplateList -->
        <p>$button</p>
        <div class="pfErrorMessages"></div>
    </div><!-- multipleTemplateWrapper -->
</fieldset>
END;
        return $text;
    }
    function tableHTML( $tif, $instanceNum ) {
        global $wgPageFormsFieldNum;
        $allGridValues = $tif->getGridValues();
        if ( array_key_exists( $instanceNum, $allGridValues ) ) {
            $gridValues = $allGridValues[$instanceNum];
        } else {
            $gridValues = null;
        }
        $html = '';
        foreach ( $tif->getFields() as $formField ) {
            $fieldName = $formField->template_field->getFieldName();
            if ( $gridValues == null ) {
                $curValue = null;
            } else {
                $curValue = $gridValues[$fieldName];
            }
            if ( $formField->holdsTemplate() ) {
                $attribs = [];
                if ( $formField->hasFieldArg( 'class' ) ) {
                    $attribs['class'] = $formField->getFieldArg( 'class' );
                }
                $html .= '</table>' . "\n";
                $html .= Html::hidden( $formField->getInputName(), $curValue, $attribs );
                $html .= $formField->additionalHTMLForInput( $curValue, $fieldName, $tif->getTemplateName() );
                $html .= '<table class="formtable">' . "\n";
                continue;
            }
            if ( $formField->isHidden() ) {
                $attribs = [];
                if ( $formField->hasFieldArg( 'class' ) ) {
                    $attribs['class'] = $formField->getFieldArg( 'class' );
                }
                $html .= Html::hidden( $formField->getInputName(), $curValue, $attribs );
                continue;
            }
            $wgPageFormsFieldNum++;
            if ( $formField->getLabel() !== null ) {
                $labelText = $formField->getLabel();
                // Kind of a @HACK - for a checkbox within
                // display=table, 'label' is used for two
                // purposes: the label column, and the text
                // after the checkbox. Unset the value here so
                // that it's only used for the first purpose,
                // and doesn't show up twice.
                $formField->setFieldArg( 'label', '' );
            } elseif ( $formField->getLabelMsg() !== null ) {
                $labelText = wfMessage( $formField->getLabelMsg() )->parse();
            } elseif ( $formField->template_field->getLabel() !== null ) {
                $labelText = $formField->template_field->getLabel() . ':';
            } else {
                $labelText = $fieldName . ': ';
            }
            $label = Html::element( 'label',
                [ 'for' => "input_$wgPageFormsFieldNum" ],
                $labelText );
            $labelCellAttrs = [];
            if ( $formField->hasFieldArg( 'tooltip' ) ) {
                $labelCellAttrs['data-tooltip'] = $formField->getFieldArg( 'tooltip' );
            }
            $labelCell = Html::rawElement( 'th', $labelCellAttrs, $label );
            $inputHTML = $this->formFieldHTML( $formField, $curValue );
            $inputHTML .= $formField->additionalHTMLForInput( $curValue, $fieldName, $tif->getTemplateName() );
            $inputCell = Html::rawElement( 'td', null, $inputHTML );
            $html .= Html::rawElement( 'tr', null, $labelCell . $inputCell ) . "\n";
        }
        $html = Html::rawElement( 'table', [ 'class' => 'formtable' ], $html );
        return $html;
    }
    function getSpreadsheetAutocompleteAttributes( $formFieldArgs ) {
        if ( array_key_exists( 'values from category', $formFieldArgs ) ) {
            return [ 'category', $formFieldArgs[ 'values from category' ] ];
        } elseif ( array_key_exists( 'cargo table', $formFieldArgs ) ) {
            $cargo_table = $formFieldArgs[ 'cargo table' ];
            $cargo_field = $formFieldArgs[ 'cargo field' ];
            return [ 'cargo field', $cargo_table . '|' . $cargo_field ];
        } elseif ( array_key_exists( 'values from property', $formFieldArgs ) ) {
            return [ 'property', $formFieldArgs['values from property'] ];
        } elseif ( array_key_exists( 'values from concept', $formFieldArgs ) ) {
            return [ 'concept', $formFieldArgs['values from concept'] ];
        } elseif ( array_key_exists( 'values dependent on', $formFieldArgs ) ) {
            return [ 'dep_on', '' ];
        } elseif ( array_key_exists( 'values from external data', $formFieldArgs ) ) {
            return [ 'external data', $formFieldArgs['origName'] ];
        } else {
            return [ '', '' ];
        }
    }
    function spreadsheetHTML( $tif ) {
        global $wgOut, $wgPageFormsGridValues, $wgPageFormsGridParams;
        global $wgPageFormsScriptPath;
        if ( empty( $tif->getFields() ) ) {
            return;
        }
        $wgOut->addModules( 'ext.pageforms.spreadsheet' );
        $gridParams = [];
        foreach ( $tif->getFields() as $formField ) {
            $templateField = $formField->template_field;
            $formFieldArgs = $formField->getFieldArgs();
            $possibleValues = $formField->getPossibleValues();
            $inputType = $formField->getInputType();
            $gridParamValues = [ 'name' => $templateField->getFieldName() ];
            list( $autocompletedatatype, $autocompletesettings ) = $this->getSpreadsheetAutocompleteAttributes( $formFieldArgs );
            if ( $formField->getLabel() !== null ) {
                $gridParamValues['label'] = $formField->getLabel();
            }
            if ( $formField->getDefaultValue() !== null ) {
                $gridParamValues['default'] = $formField->getDefaultValue();
            }
            // currently the spreadsheets in Page Forms doesn't support the tokens input
            // so it's better to take a default jspreadsheet editor for tokens
            if ( $formField->isList() || $inputType == 'tokens' ) {
                $autocompletedatatype = '';
                $autocompletesettings = '';
                $gridParamValues['type'] = 'text';
            } elseif ( !empty( $possibleValues )
                && $autocompletedatatype != 'category' && $autocompletedatatype != 'cargo field'
                && $autocompletedatatype != 'concept' && $autocompletedatatype != 'property' ) {
                $gridParamValues['values'] = $possibleValues;
                if ( $formField->isList() ) {
                    $gridParamValues['list'] = true;
                    $gridParamValues['delimiter'] = $formField->getFieldArg( 'delimiter' );
                }
            } elseif ( $inputType == 'textarea' ) {
                $gridParamValues['type'] = 'textarea';
            } elseif ( $inputType == 'checkbox' ) {
                $gridParamValues['type'] = 'checkbox';
            } elseif ( $inputType == 'date' ) {
                $gridParamValues['type'] = 'date';
            } elseif ( $inputType == 'datetime' ) {
                $gridParamValues['type'] = 'datetime';
            } elseif ( $possibleValues != null ) {
                array_unshift( $possibleValues, '' );
                $completePossibleValues = [];
                foreach ( $possibleValues as $value ) {
                    $completePossibleValues[] = [ 'Name' => $value, 'Id' => $value ];
                }
                $gridParamValues['type'] = 'select';
                $gridParamValues['items'] = $completePossibleValues;
                $gridParamValues['valueField'] = 'Id';
                $gridParamValues['textField'] = 'Name';
            } else {
                $gridParamValues['type'] = 'text';
            }
            $gridParamValues['autocompletedatatype'] = $autocompletedatatype;
            $gridParamValues['autocompletesettings'] = $autocompletesettings;
            $gridParamValues['inputType'] = $inputType;
            $gridParams[] = $gridParamValues;
        }
        $templateName = $tif->getTemplateName();
        $templateDivID = str_replace( ' ', '', $templateName ) . "Grid";
        $templateDivAttrs = [
            'class' => 'pfSpreadsheet',
            'id' => $templateDivID,
            'data-template-name' => $templateName
        ];
        if ( $tif->getHeight() != null ) {
            $templateDivAttrs['height'] = $tif->getHeight();
        }
        $loadingImage = Html::element( 'img', [ 'src' => "$wgPageFormsScriptPath/skins/loading.gif" ] );
        $loadingImageDiv = '<div class="loadingImage">' . $loadingImage . '</div>';
        $text = Html::rawElement( 'div', $templateDivAttrs, $loadingImageDiv );
        $wgPageFormsGridParams[$templateName] = $gridParams;
        $wgPageFormsGridValues[$templateName] = $tif->getGridValues();
        PFFormUtils::setGlobalVarsForSpreadsheet();
        return $text;
    }
    /**
     * Get a string representing the current time, for the time zone
     * specified in the wiki.
     * @param string $includeTime
     * @param string $includeTimezone
     * @return string
     */
    function getStringForCurrentTime( $includeTime, $includeTimezone ) {
        global $wgLocaltimezone, $wgAmericanDates, $wgPageForms24HourTime;
        if ( isset( $wgLocaltimezone ) ) {
            $serverTimezone = date_default_timezone_get();
            date_default_timezone_set( $wgLocaltimezone );
        }
        $cur_time = time();
        $year = date( "Y", $cur_time );
        $month = date( "n", $cur_time );
        $day = date( "j", $cur_time );
        if ( $wgAmericanDates == true ) {
            $month_names = PFFormUtils::getMonthNames();
            $month_name = $month_names[$month - 1];
            $curTimeString = "$month_name $day$year";
        } else {
            $curTimeString = "$year-$month-$day";
        }
        if ( isset( $wgLocaltimezone ) ) {
            date_default_timezone_set( $serverTimezone );
        }
        if ( !$includeTime ) {
            return $curTimeString;
        }
        if ( $wgPageForms24HourTime ) {
            $hour = str_pad( intval( substr( date( "G", $cur_time ), 0, 2 ) ), 2, '0', STR_PAD_LEFT );
        } else {
            $hour = str_pad( intval( substr( date( "g", $cur_time ), 0, 2 ) ), 2, '0', STR_PAD_LEFT );
        }
        $minute = str_pad( intval( substr( date( "i", $cur_time ), 0, 2 ) ), 2, '0', STR_PAD_LEFT );
        $second = str_pad( intval( substr( date( "s", $cur_time ), 0, 2 ) ), 2, '0', STR_PAD_LEFT );
        if ( $wgPageForms24HourTime ) {
            $curTimeString .= " $hour:$minute:$second";
        } else {
            $ampm = date( "A", $cur_time );
            $curTimeString .= " $hour:$minute:$second $ampm";
        }
        if ( $includeTimezone ) {
            $timezone = date( "T", $cur_time );
            $curTimeString .= " $timezone";
        }
        return $curTimeString;
    }
    /**
     * If the value passed in for a certain field, when a form is
     * submitted, is an array, then it might be from a checkbox
     * or date input - in that case, convert it into a string.
     * @param array $value
     * @param string $delimiter
     * @return string
     */
    static function getStringFromPassedInArray( $value, $delimiter ) {
        // If it's just a regular list, concatenate it.
        // This is needed due to some strange behavior
        // in PF, where, if a preload page is passed in
        // in the query string, the form ends up being
        // parsed twice.
        if ( array_key_exists( 'is_list', $value ) ) {
            unset( $value['is_list'] );
            return str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], implode( "$delimiter ", $value ) );
        }
        // if it has 1 or 2 elements, assume it's a checkbox; if it has
        // 3 elements, assume it's a date
        // - this handling will have to get more complex if other
        // possibilities get added
        if ( count( $value ) == 1 ) {
            return PFUtils::getWordForYesOrNo( false );
        } elseif ( count( $value ) == 2 ) {
            return PFUtils::getWordForYesOrNo( true );
        // if it's 3 or greater, assume it's a date or datetime
        } elseif ( count( $value ) >= 3 ) {
            $month = $value['month'];
            $day = $value['day'];
            if ( $day !== '' ) {
                global $wgAmericanDates;
                if ( $wgAmericanDates == false ) {
                    // pad out day to always be two digits
                    $day = str_pad( $day, 2, "0", STR_PAD_LEFT );
                }
            }
            $year = $value['year'];
            $hour = $minute = $second = $ampm24h = $timezone = null;
            if ( isset( $value['hour'] ) ) {
                $hour = $value['hour'];
            }
            if ( isset( $value['minute'] ) ) {
                $minute = $value['minute'];
            }
            if ( isset( $value['second'] ) ) {
                $second = $value['second'];
            }
            if ( isset( $value['ampm24h'] ) ) {
                $ampm24h = $value['ampm24h'];
            }
            if ( isset( $value['timezone'] ) ) {
                $timezone = $value['timezone'];
            }
            // if ( $month !== '' && $day !== '' && $year !== '' ) {
            // We can accept either year, or year + month, or year + month + day.
            // if ( $month !== '' && $day !== '' && $year !== '' ) {
            if ( $year !== '' ) {
                // special handling for American dates - otherwise, just
                // the standard year/month/day (where month is a number)
                global $wgAmericanDates;
                if ( $month == '' ) {
                    return $year;
                } elseif ( $day == '' ) {
                    if ( !$wgAmericanDates ) {
                        // The month is a number - we
                        // need it to be a string, so
                        // that the date will be parsed
                        // correctly if strtotime() is
                        // used.
                        $monthNames = PFFormUtils::getMonthNames();
                        $month = $monthNames[$month - 1];
                    }
                    return "$month $year";
                } else {
                    if ( $wgAmericanDates == true ) {
                        $new_value = "$month $day$year";
                    } else {
                        $new_value = "$year/$month/$day";
                    }
                    // If there's a day, include whatever
                    // time information we have.
                    if ( $hour !== null ) {
                        $new_value .= " " . str_pad( intval( substr( $hour, 0, 2 ) ), 2, '0', STR_PAD_LEFT ) . ":" . str_pad( intval( substr( $minute, 0, 2 ) ), 2, '0', STR_PAD_LEFT );
                    }
                    if ( $second !== null ) {
                        $new_value .= ":" . str_pad( intval( substr( $second, 0, 2 ) ), 2, '0', STR_PAD_LEFT );
                    }
                    if ( $ampm24h !== null ) {
                        $new_value .= " $ampm24h";
                    }
                    if ( $timezone !== null ) {
                        $new_value .= " $timezone";
                    }
                    return $new_value;
                }
            }
        }
        return '';
    }
    static function displayLoadingImage() {
        global $wgPageFormsScriptPath;
        $text = '<div id="loadingMask"></div>';
        $loadingBGImage = Html::element( 'img', [ 'src' => "$wgPageFormsScriptPath/skins/loadingbg.png" ] );
        $text .= '<div style="position: fixed; left: 50%; top: 50%;">' . $loadingBGImage . '</div>';
        $loadingImage = Html::element( 'img', [ 'src' => "$wgPageFormsScriptPath/skins/loading.gif" ] );
        $text .= '<div style="position: fixed; left: 50%; top: 50%; padding: 48px;">' . $loadingImage . '</div>';
        return Html::rawElement( 'span', [ 'class' => 'loadingImage' ], $text );
    }
    /**
     * This function is the real heart of the entire Page Forms
     * extension. It handles two main actions: (1) displaying a form on the
     * screen, given a form definition and possibly page contents (if an
     * existing page is being edited); and (2) creating actual page
     * contents, if the form was already submitted by the user.
     *
     * It also does some related tasks, like figuring out the page name (if
     * only a page formula exists).
     * @param string $form_def
     * @param bool $form_submitted
     * @param bool $source_is_page
     * @param string|null $form_id
     * @param string|null $existing_page_content
     * @param string|null $page_name
     * @param string|null $page_name_formula
     * @param bool $is_query
     * @param bool $is_embedded
     * @param bool $is_autocreate true when called by #formredlink with "create page"
     * @param array $autocreate_query query parameters from #formredlink
     * @param User|null $user
     * @return array
     * @throws FatalError
     * @throws MWException
     */
    function formHTML(
        $form_def,
        $form_submitted,
        $source_is_page,
        $form_id = null,
        $existing_page_content = null,
        $page_name = null,
        $page_name_formula = null,
        $is_query = false,
        $is_embedded = false,
        $is_autocreate = false,
        $autocreate_query = [],
        $user = null
    ) {
        global $wgRequest;
        // used to represent the current tab index in the form
        global $wgPageFormsTabIndex;
        // used for setting various HTML IDs
        global $wgPageFormsFieldNum;
        global $wgPageFormsShowExpandAllLink;
        // Initialize some variables.
        $wiki_page = new PFWikiPage();
        $wgPageFormsTabIndex = 0;
        $wgPageFormsFieldNum = 0;
        $source_page_matches_this_form = false;
        $form_page_title = null;
        $generated_page_name = $page_name_formula;
        $new_text = "";
        $original_page_content = $existing_page_content;
        // Disable all form elements if user doesn't have edit
        // permission - two different checks are needed, because
        // editing permissions can be set in different ways.
        // HACK - sometimes we don't know the page name in advance, but
        // we still need to set a title here for testing permissions.
        if ( $is_embedded ) {
            // If this is an embedded form (probably a 'RunQuery'),
            // just use the name of the actual page we're on.
            global $wgTitle;
            $this->mPageTitle = $wgTitle;
        } elseif ( $is_query ) {
            // We're in Special:RunQuery - just use that as the
            // title.
            global $wgTitle;
            $this->mPageTitle = $wgTitle;
        } elseif ( $page_name === '' || $page_name === null ) {
            $this->mPageTitle = Title::newFromText(
                $wgRequest->getVal( 'namespace' ) . ":Page Forms permissions test" );
        } else {
            $this->mPageTitle = Title::newFromText( $page_name );
        }
        if ( $user === null ) {
            $user = RequestContext::getMain()->getUser();
        }
        global $wgOut;
        // Show previous set of deletions for this page, if it's been
        // deleted before.
        if ( !$form_submitted &&
            ( $this->mPageTitle && !$this->mPageTitle->exists() &&
            $page_name_formula === null )
        ) {
            $this->showDeletionLog( $wgOut );
        }
        // Unfortunately, we can't just call userCan() or its
        // equivalent here because it seems to ignore the setting
        // "$wgEmailConfirmToEdit = true;". Instead, we'll just get the
        // permission errors from the start, and use those to determine
        // whether the page is editable.
        if ( !$is_query ) {
            $permissionErrors = MediaWikiServices::getInstance()->getPermissionManager()
                ->getPermissionErrors( 'edit', $user, $this->mPageTitle );
            if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) {
                $permissionErrors = [ [ 'readonlytext', [ MediaWikiServices::getInstance()->getReadOnlyMode()->getReason() ] ] ];
            }
            $userCanEditPage = count( $permissionErrors ) == 0;
            Hooks::run( 'PageForms::UserCanEditPage', [ $this->mPageTitle, &$userCanEditPage ] );
        }
        // Start off with a loading spinner - this will be removed by
        // the JavaScript once everything has finished loading.
        $form_text = self::displayLoadingImage();
        if ( $is_query || $userCanEditPage ) {
            $form_is_disabled = false;
            // Show "Your IP address will be recorded" warning if
            // user is anonymous, and it's not a query.
            if ( $user->isAnon() && !$is_query ) {
                // Based on code in MediaWiki's EditPage.php.
                $anonEditWarning = wfMessage( 'anoneditwarning',
                    // Log-in link
                    '{{fullurl:Special:UserLogin|returnto={{FULLPAGENAMEE}}}}',
                    // Sign-up link
                    '{{fullurl:Special:UserLogin/signup|returnto={{FULLPAGENAMEE}}}}' )->parse();
                $form_text .= Html::rawElement( 'div', [ 'id' => 'mw-anon-edit-warning', 'class' => 'warningbox' ], $anonEditWarning );
            }
        } else {
            $form_is_disabled = true;
            if ( $wgOut->getTitle() != null ) {
                $wgOut->setPageTitle( wfMessage( 'badaccess' )->text() );
                $wgOut->addWikiTextAsInterface( $wgOut->formatPermissionsErrorMessage( $permissionErrors, 'edit' ) );
                $wgOut->addHTML( "\n<hr />\n" );
            }
        }
        if ( $wgPageFormsShowExpandAllLink ) {
            $form_text .= Html::rawElement( 'p', [ 'id' => 'pf-expand-all' ],
                // @TODO - add an i18n message for this.
                Html::element( 'a', [ 'href' => '#' ], 'Expand all collapsed parts of the form' ) ) . "\n";
        }
        $parser = PFUtils::getParser()->getFreshParser();
        if ( !$parser->getOptions() ) {
            if ( method_exists( $parser, 'setOptions' ) ) {
                // MW 1.35+
                $parser->setOptions( ParserOptions::newFromUser( $user ) );
            } else {
                $parser->Options( ParserOptions::newFromUser( $user ) );
            }
        }
        if ( !$is_embedded || method_exists( $parser, 'setOptions' ) ) {
            // Once support for MW < 1.35 is removed, this check will no longer be necessary.
            // (It might be unnecessary already.)
            $parser->setTitle( $this->mPageTitle );
        }
        // This is needed in order to make sure $parser->mLinkHolders
        // is set.
        $parser->clearState();
        $form_def = PFFormUtils::getFormDefinition( $parser, $form_def, $form_id );
        // Turn form definition file into an array of sections, one for
        // each template definition (plus the first section).
        $form_def_sections = [];
        $start_position = 0;
        $section_start = 0;
        $free_text_was_included = false;
        $preloaded_free_text = null;
        // @HACK - replace the 'free text' standard input with a
        // field declaration to get it to be handled as a field.
        $form_def = str_replace( 'standard input|free text', 'field|#freetext#', $form_def );
        while ( $brackets_loc = strpos( $form_def, "{{{", $start_position ) ) {
            $brackets_end_loc = strpos( $form_def, "}}}", $brackets_loc );
            $bracketed_string = substr( $form_def, $brackets_loc + 3, $brackets_end_loc - ( $brackets_loc + 3 ) );
            $tag_components = PFUtils::getFormTagComponents( $bracketed_string );
            $tag_title = trim( $tag_components[0] );
            if ( $tag_title == 'for template' || $tag_title == 'end template' ) {
                // Create a section for everything up to here
                $section = substr( $form_def, $section_start, $brackets_loc - $section_start );
                $form_def_sections[] = $section;
                $section_start = $brackets_loc;
            }
            $start_position = $brackets_loc + 1;
        }
        // end while
        $form_def_sections[] = trim( substr( $form_def, $section_start ) );
        // Cycle through the form definition file, and possibly an
        // existing article as well, finding template and field
        // declarations and replacing them with form elements, either
        // blank or pre-populated, as appropriate.
        $template_name = null;
        $template = null;
        $tif = null;
        // This array will keep track of all the replaced @<name>@ strings
        $placeholderFields = [];
        for ( $section_num = 0; $section_num < count( $form_def_sections ); $section_num++ ) {
            $start_position = 0;
            // the append is there to ensure that the original
            // array doesn't get modified; is it necessary?
            $section = " " . $form_def_sections[$section_num];
            while ( $brackets_loc = strpos( $section, '{{{', $start_position ) ) {
                $brackets_end_loc = strpos( $section, "}}}", $brackets_loc );
                // For cases with more than 3 ending brackets,
                // take the last 3 ones as the tag end.
                while ( $section[$brackets_end_loc + 3] == "}" ) {
                    $brackets_end_loc++;
                }
                $bracketed_string = substr( $section, $brackets_loc + 3, $brackets_end_loc - ( $brackets_loc + 3 ) );
                $tag_components = PFUtils::getFormTagComponents( $bracketed_string );
                if ( count( $tag_components ) == 0 ) {
                    continue;
                }
                $tag_title = trim( $tag_components[0] );
                // Checks for forbidden characters
                if ( $tag_title != 'info' ) {
                    foreach ( $tag_components as $tag_component ) {
                        // Angled brackets could cause a security leak (and should not be necessary).
                        // Allow them in "default filename", though.
                        $tagParts = explode( '=', $tag_component, 2 );
                        if ( count( $tagParts ) == 2 && $tagParts[0] == 'default filename' ) {
                            continue;
                        }
                        if ( strpos( $tag_component, '<' ) !== false && strpos( $tag_component, '>' ) !== false ) {
                            throw new MWException(
                                '<div class="error">Error in form definition! The following field tag contains forbidden characters:</div>' .
                                "\n<pre>" . htmlspecialchars( $tag_component ) . "</pre>"
                            );
                        }
                    }
                }
                // =====================================================
                // for template processing
                // =====================================================
                if ( $tag_title == 'for template' ) {
                    if ( $tif ) {
                        $previous_template_name = $tif->getTemplateName();
                    } else {
                        $previous_template_name = '';
                    }
                    $template_name = str_replace( '_', ' ', $parser->recursiveTagParse( $tag_components[1] ) );
                    $is_new_template = ( $template_name != $previous_template_name );
                    if ( $is_new_template ) {
                        $template = PFTemplate::newFromName( $template_name );
                        $tif = PFTemplateInForm::newFromFormTag( $tag_components );
                    }
                    // Remove template tag.
                    $section = substr_replace( $section, '', $brackets_loc, $brackets_end_loc + 3 - $brackets_loc );
                    // If we are editing a page, and this
                    // template can be found more than
                    // once in that page, and multiple
                    // values are allowed, repeat this
                    // section.
                    if ( $source_is_page ) {
                        $tif->setPageRelatedInfo( $existing_page_content );
                        // Get the first instance of
                        // this template on the page
                        // being edited, even if there
                        // are more.
                        if ( $tif->pageCallsThisTemplate() ) {
                            $tif->setFieldValuesFromPage( $existing_page_content );
                            $existing_template_text = $tif->getFullTextInPage();
                            // Now remove this template from the text being edited.
                            $existing_page_content = $this->strReplaceFirst( $existing_template_text, '', $existing_page_content );
                            // If we've found a match in the source
                            // page, there's a good chance that this
                            // page was created with this form - note
                            // that, so we don't send the user a warning.
                            $source_page_matches_this_form = true;
                        }
                    }
                    // We get values from the request,
                    // regardless of whether the source is the
                    // page or a form submit, because even if
                    // the source is a page, values can still
                    // come from a query string.
                    // (Unless it's called from #formredlink.)
                    if ( !$is_autocreate ) {
                        $tif->setFieldValuesFromSubmit();
                    }
                    $tif->checkIfAllInstancesPrinted( $form_submitted, $source_is_page );
                    if ( !$tif->allInstancesPrinted() ) {
                        $wiki_page->addTemplate( $tif );
                    }
                // =====================================================
                // end template processing
                // =====================================================
                } elseif ( $tag_title == 'end template' ) {
                    if ( count( $tag_components ) > 1 ) {
                        throw new MWException( '<div class="error">Error in form definition: \'end template\' tag cannot contain any additional parameters.</div>' );
                    }
                    if ( $source_is_page ) {
                        // Add any unhandled template fields
                        // in the page as hidden variables.
                        $form_text .= PFFormUtils::unhandledFieldsHTML( $tif );
                    }
                    // Remove this tag from the $section variable.
                    $section = substr_replace( $section, '', $brackets_loc, $brackets_end_loc + 3 - $brackets_loc );
                    $template = null;
                    $tif = null;
                // =====================================================
                // field processing
                // =====================================================
                } elseif ( $tag_title == 'field' ) {
                    // If the template is null, that (hopefully)
                    // means we're handling the free text field.
                    // Make the template a dummy variable.
                    if ( $tif == null ) {
                        $template = new PFTemplate( null, [] );
                        // Get free text from the query string, if it was set.
                        if ( $wgRequest->getCheck( 'free_text' ) ) {
                            $standard_input = $wgRequest->getArray( 'standard_input', [] );
                            $standard_input['#freetext#'] = $wgRequest->getVal( 'free_text' );
                            $wgRequest->setVal( 'standard_input', $standard_input );
                        }
                        $tif = PFTemplateInForm::create( 'standard_input', null, null, null, [] );
                        $tif->setFieldValuesFromSubmit();
                    }
                    // We get the field name both here
                    // and in the PFFormField constructor,
                    // because PFFormField isn't equipped
                    // to deal with the #freetext# hack,
                    // among others.
                    $field_name = trim( $tag_components[1] );
                    $form_field = PFFormField::newFromFormFieldTag( $tag_components, $template, $tif, $form_is_disabled, $user );
                    // For special displays, add in the
                    // form fields, so we know the data
                    // structure.
                    if ( ( $tif->getDisplay() == 'table' && ( !$tif->allowsMultiple() || $tif->getInstanceNum() == 0 ) ) ||
                        ( $tif->getDisplay() == 'spreadsheet' && $tif->allowsMultiple() && $tif->getInstanceNum() == 0 ) || ( $tif->getDisplay() == 'calendar' && $tif->allowsMultiple() && $tif->getInstanceNum() == 0 ) ) {
                        $tif->addField( $form_field );
                    }
                    $val_modifier = null;
                    if ( $is_autocreate ) {
                        $values_from_query = $autocreate_query[$tif->getTemplateName()];
                        $cur_value = $form_field->getCurrentValue( $values_from_query, $form_submitted, $source_is_page, $tif->allInstancesPrinted(), $val_modifier );
                    } else {
                        $cur_value = $form_field->getCurrentValue( $tif->getValuesFromSubmit(), $form_submitted, $source_is_page, $tif->allInstancesPrinted(), $val_modifier );
                    }
                    $delimiter = $form_field->getFieldArg( 'delimiter' );
                    if ( $form_field->holdsTemplate() ) {
                        $placeholderFields[] = self::placeholderFormat( $tif->getTemplateName(), $field_name );
                    }
                    if ( $val_modifier !== null ) {
                        $page_value = $tif->getValuesFromPage()[$field_name];
                    }
                    if ( $val_modifier === '+' ) {
                        if ( preg_match( "#(,|\^)\s*$cur_value\s*(,|\$)#", $page_value ) === 0 ) {
                            if ( trim( $page_value ) !== '' ) {
                                // if page_value is empty, simply don't do anything, because then cur_value
                                // is already the value it has to be (no delimiter needed).
                                $cur_value = $page_value . $delimiter . $cur_value;
                            }
                        } else {
                            $cur_value = $page_value;
                        }
                        $tif->changeFieldValues( $field_name, $cur_value, $delimiter );
                    } elseif ( $val_modifier === '-' ) {
                        // get an array of elements to remove:
                        $remove = array_map( 'trim', explode( ",", $cur_value ) );
                        // process the current value:
                        $val_array = array_map( 'trim', explode( $delimiter, $page_value ) );
                        // remove element(s) from list
                        foreach ( $remove as $rmv ) {
                            // go through each element and remove match(es)
                            $key = array_search( $rmv, $val_array );
                            if ( $key !== false ) {
                                unset( $val_array[$key] );
                            }
                        }
                        // Convert modified array back to a comma-separated string value and modify
                        $cur_value = implode( ",", $val_array );
                        if ( $cur_value === '' ) {
                            // HACK: setting an empty string prevents anything from happening at all.
                            // set a dummy string that evaluates to an empty string
                            $cur_value = '{{subst:lc: }}';
                        }
                        $tif->changeFieldValues( $field_name, $cur_value, $delimiter );
                    }
                    // If the user is editing a page, and that page contains a call to
                    // the template being processed, get the current field's value
                    // from the template call
                    if ( $source_is_page && ( $tif->getFullTextInPage() != '' ) && !$form_submitted ) {
                        if ( $tif->hasValueFromPageForField( $field_name ) ) {
                            // Get value, and remove it,
                            // so that at the end we
                            // can have a list of all
                            // the fields that weren't
                            // handled by the form.
                            $cur_value = $tif->getAndRemoveValueFromPageForField( $field_name );
                            // If the field is a placeholder, the contents of this template
                            // parameter should be treated as elements parsed by an another
                            // multiple template form.
                            // By putting that at the very end of the parsed string, we'll
                            // have it processed as a regular multiple template form.
                            if ( $form_field->holdsTemplate() ) {
                                $existing_page_content .= $cur_value;
                            }
                        } elseif ( isset( $cur_value ) && !empty( $cur_value ) ) {
                            // Do nothing.
                        } else {
                            $cur_value = '';
                        }
                    }
                    // Handle the free text field.
                    if ( $field_name == '#freetext#' ) {
                        // If there was no preloading, this will just be blank.
                        $preloaded_free_text = $cur_value;
                        // Add placeholders for the free text in both the form and
                        // the page, using <free_text> tags - once all the free text
                        // is known (at the end), it will get substituted in.
                        if ( $form_field->isHidden() ) {
                            $new_text = Html::hidden( 'pf_free_text', '!free_text!' );
                        } else {
                            $wgPageFormsTabIndex++;
                            $wgPageFormsFieldNum++;
                            if ( $cur_value === '' || $cur_value === null ) {
                                $default_value = '!free_text!';
                            } else {
                                $default_value = $cur_value;
                            }
                            $freeTextInput = new PFTextAreaInput( $input_number = null, $default_value, 'pf_free_text', ( $form_is_disabled || $form_field->isRestricted() ), $form_field->getFieldArgs() );
                            $freeTextInput->addJavaScript();
                            $new_text = $freeTextInput->getHtmlText();
                            if ( $form_field->hasFieldArg( 'edittools' ) ) {
                                // borrowed from EditPage::showEditTools()
                                $edittools_text = $parser->recursiveTagParse( wfMessage( 'edittools', [ 'content' ] )->text() );
                                $new_text .= <<<END
        <div class="mw-editTools">
        $edittools_text
        </div>
END;
                            }
                        }
                        $free_text_was_included = true;
                        $wiki_page->addFreeTextSection();
                    }
                    if ( $tif->getTemplateName() === '' || $field_name == '#freetext#' ) {
                        $section = substr_replace( $section, $new_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc );
                    } else {
                        if ( is_array( $cur_value ) ) {
                            // @TODO - is this code ever called?
                            $delimiter = $form_field->getFieldArg( 'is_list' );
                            // first, check if it's a list
                            if ( array_key_exists( 'is_list', $cur_value ) &&
                                    $cur_value['is_list'] == true ) {
                                $cur_value_in_template = "";
                                foreach ( $cur_value as $key => $val ) {
                                    if ( $key !== "is_list" ) {
                                        if ( $cur_value_in_template != "" ) {
                                            $cur_value_in_template .= $delimiter . " ";
                                        }
                                        $cur_value_in_template .= $val;
                                    }
                                }
                            } else {
                                // If it's not a list, it's probably from a checkbox or date input -
                                // convert the values into a string.
                                $cur_value_in_template = self::getStringFromPassedInArray( $cur_value, $delimiter );
                            }
                        } elseif ( $form_field->holdsTemplate() ) {
                            // If this field holds an embedded template,
                            // and the value is not an array, it means
                            // there are no instances of the template -
                            // set the value to null to avoid getting
                            // whatever is currently on the page.
                            $cur_value_in_template = null;
                        } else {
                            // value is not an array.
                            $cur_value_in_template = $cur_value;
                        }
                        // If we're creating the page name from a formula based on
                        // form values, see if the current input is part of that formula,
                        // and if so, substitute in the actual value.
                        if ( $form_submitted && $generated_page_name !== '' ) {
                            // This line appears to be unnecessary.
                            // $generated_page_name = str_replace('.', '_', $generated_page_name);
                            $generated_page_name = str_replace( ' ', '_', $generated_page_name );
                            $escaped_input_name = str_replace( ' ', '_', $form_field->getInputName() );
                            $generated_page_name = str_ireplace( "<$escaped_input_name>", $cur_value_in_template, $generated_page_name );
                            // Once the substitution is done, replace underlines back
                            // with spaces.
                            $generated_page_name = str_replace( '_', ' ', $generated_page_name );
                        }
                        if ( defined( 'CARGO_VERSION' ) && $form_field->hasFieldArg( 'mapping cargo table' ) &&
                        $form_field->hasFieldArg( 'mapping cargo field' ) && $form_field->hasFieldArg( 'mapping cargo value field' ) ) {
                                $mappingCargoTable = $form_field->getFieldArg( 'mapping cargo table' );
                                $mappingCargoField = $form_field->getFieldArg( 'mapping cargo field' );
                                $mappingCargoValueField = $form_field->getFieldArg( 'mapping cargo value field' );
                                if ( !$form_submitted && $cur_value !== null && $cur_value !== '' ) {
                                    $cur_value = $this->getCargoBasedMapping( $cur_value, $mappingCargoTable, $mappingCargoField, $mappingCargoValueField, $form_field );
                                }
                                if ( $form_submitted && $cur_value_in_template !== null && $cur_value_in_template !== '' ) {
                                    $cur_value_in_template = $this->getCargoBasedMapping( $cur_value_in_template, $mappingCargoTable, $mappingCargoValueField, $mappingCargoField, $form_field );
                                }
                        }
                        if ( $cur_value !== '' &&
                            ( $form_field->hasFieldArg( 'mapping template' ) ||
                            $form_field->hasFieldArg( 'mapping property' ) ||
                            ( $form_field->hasFieldArg( 'mapping cargo table' ) &&
                            $form_field->hasFieldArg( 'mapping cargo field' ) ) ||
                            $form_field->getUseDisplayTitle() ) ) {
                            // If the input type is "tokens', the value is not
                            // an array, but the delimiter still needs to be set.
                            if ( !is_array( $cur_value ) ) {
                                if ( $form_field->isList() ) {
                                    $delimiter = $form_field->getFieldArg( 'delimiter' );
                                } else {
                                    $delimiter = null;
                                }
                            }
                            $cur_value = $form_field->valueStringToLabels( $cur_value, $delimiter );
                        }
                        // Call hooks - unfortunately this has to be split into two
                        // separate calls, because of the different variable names in
                        // each case.
                        // @TODO - should it be $cur_value for both cases? Or should the
                        // hook perhaps modify both variables?
                        if ( $form_submitted ) {
                            Hooks::run( 'PageForms::CreateFormField', [ &$form_field, &$cur_value_in_template, true ] );
                        } else {
                            $this->createFormFieldTranslateTag( $template, $tif, $form_field, $cur_value );
                            Hooks::run( 'PageForms::CreateFormField', [ &$form_field, &$cur_value, false ] );
                        }
                        // if this is not part of a 'multiple' template, increment the
                        // global tab index (used for correct tabbing)
                        if ( !$form_field->hasFieldArg( 'part_of_multiple' ) ) {
                            $wgPageFormsTabIndex++;
                        }
                        // increment the global field number regardless
                        $wgPageFormsFieldNum++;
                        if ( $source_is_page && !$tif->allInstancesPrinted() ) {
                            // If the source is a page, don't use the default
                            // values - except for newly-added instances of a
                            // multiple-instance template.
                        // If the field is a date field, and its default value was set
                        // to 'now', and it has no current value, set $cur_value to be
                        // the current date.
                        } elseif ( $form_field->getDefaultValue() == 'now' &&
                                // if the date is hidden, cur_value will already be set
                                // to the default value
                                ( $cur_value == '' || $cur_value == 'now' ) ) {
                            $input_type = $form_field->getInputType();
                            // We don't handle the 'datepicker' and 'datetimepicker'
                            // input types here, because they have their own
                            // formatting; instead, they handle 'now' themselves.
                            if ( $input_type == 'date' || $input_type == 'datetime' ||
                                    $input_type == 'year' ||
                                    ( $input_type == '' && $form_field->getTemplateField()->getPropertyType() == '_dat' ) ) {
                                $cur_value_in_template = self::getStringForCurrentTime( $input_type == 'datetime', $form_field->hasFieldArg( 'include timezone' ) );
                            }
                        // If the field is a text field, and its default value was set
                        // to 'current user', and it has no current value, set $cur_value
                        // to be the current user.
                        } elseif ( $form_field->getDefaultValue() == 'current user' &&
                            // if the input is hidden, cur_value will already be set
                            // to the default value
                            ( $cur_value === '' || $cur_value == 'current user' )
                        ) {
                            if ( method_exists( $user, 'isRegistered' ) ) {
                                // MW 1.34+
                                $cur_value_in_template = $user->isRegistered() ? $user->getName() : '';
                            } else {
                                $cur_value_in_template = $user->getName();
                            }
                            $cur_value = $cur_value_in_template;
                        // UUID is the only default value (so far) that can also be set
                        // by the JavaScript, for multiple-instance templates - for the
                        // other default values, there's no real need to have a
                        // different value for each instance.
                        } elseif ( $form_field->getDefaultValue() == 'uuid' &&
                            ( $cur_value == '' || $cur_value == 'uuid' )
                        ) {
                            if ( $tif->allowsMultiple() ) {
                                // Will be set by the JS.
                                $form_field->setFieldArg( 'class', 'new-uuid' );
                            } else {
                                $cur_value = $cur_value_in_template = self::generateUUID();
                            }
                        }
                        // If all instances have been
                        // printed, that means we're
                        // now printing a "starter"
                        // div - set the current value
                        // to null, unless it's the
                        // default value.
                        // (Ideally it wouldn't get
                        // set at all, but that seems a
                        // little harder.)
                        if ( $tif->allInstancesPrinted() && $form_field->getDefaultValue() == null ) {
                            $cur_value = null;
                        }
                        $new_text = $this->formFieldHTML( $form_field, $cur_value );
                        $new_text .= $form_field->additionalHTMLForInput( $cur_value, $field_name, $tif->getTemplateName() );
                        if ( $new_text ) {
                            $wiki_page->addTemplateParam( $template_name, $tif->getInstanceNum(), $field_name, $cur_value_in_template );
                            $section = substr_replace( $section, $new_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc );
                            $start_position = $brackets_loc + strlen( $new_text );
                        } else {
                            $start_position = $brackets_end_loc;
                        }
                    }
                    if ( $tif->allowsMultiple() && !$tif->allInstancesPrinted() ) {
                        $wordForYes = PFUtils::getWordForYesOrNo( true );
                        if ( $form_field->getInputType() == 'checkbox' ) {
                            if ( strtolower( $cur_value ) == strtolower( $wordForYes ) || strtolower( $cur_value ) == 'yes' || $cur_value == '1' ) {
                                $cur_value = true;
                            } else {
                                $cur_value = false;
                            }
                        }
                    }
                    if ( $tif->getDisplay() != null && ( !$tif->allowsMultiple() || !$tif->allInstancesPrinted() ) ) {
                        $tif->addGridValue( $field_name, $cur_value );
                    }
                // =====================================================
                // standard input processing
                // =====================================================
                } elseif ( $tag_title == 'standard input' ) {
                    // handle all the possible values
                    $input_name = $tag_components[1];
                    $input_label = null;
                    $attr = [];
                    // if it's a query, ignore all standard inputs except run query
                    if ( ( $is_query && $input_name != 'run query' ) || ( !$is_query && $input_name == 'run query' ) ) {
                        $new_text = "";
                        $section = substr_replace( $section, $new_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc );
                        continue;
                    }
                    // set a flag so that the standard 'form bottom' won't get displayed
                    $this->standardInputsIncluded = true;
                    // cycle through the other components
                    $is_checked = false;
                    for ( $i = 2; $i < count( $tag_components ); $i++ ) {
                        $component = $tag_components[$i];
                        $sub_components = array_map( 'trim', explode( '=', $component ) );
                        if ( count( $sub_components ) == 1 ) {
                            if ( $sub_components[0] == 'checked' ) {
                                $is_checked = true;
                            }
                        } elseif ( count( $sub_components ) == 2 ) {
                            switch ( $sub_components[0] ) {
                            case 'label':
                                $input_label = $parser->recursiveTagParse( $sub_components[1] );
                                break;
                            case 'class':
                                $attr['class'] = $sub_components[1];
                                break;
                            case 'style':
                                $attr['style'] = Sanitizer::checkCSS( $sub_components[1] );
                                break;
                            }
                        }
                    }
                    if ( $input_name == 'summary' ) {
                        $value = $wgRequest->getVal( 'wpSummary' );
                        $new_text = PFFormUtils::summaryInputHTML( $form_is_disabled, $input_label, $attr, $value );
                    } elseif ( $input_name == 'minor edit' ) {
                        $is_checked = $wgRequest->getCheck( 'wpMinoredit' );
                        $new_text = PFFormUtils::minorEditInputHTML( $form_submitted, $form_is_disabled, $is_checked, $input_label, $attr );
                    } elseif ( $input_name == 'watch' ) {
                        $is_checked = $wgRequest->getCheck( 'wpWatchthis' );
                        $new_text = PFFormUtils::watchInputHTML( $form_submitted, $form_is_disabled, $is_checked, $input_label, $attr );
                    } elseif ( $input_name == 'save' ) {
                        $new_text = PFFormUtils::saveButtonHTML( $form_is_disabled, $input_label, $attr );
                    } elseif ( $input_name == 'save and continue' ) {
                        // Remove save and continue button in one-step-process
                        if ( $this->mPageTitle == $page_name ) {
                            $new_text = PFFormUtils::saveAndContinueButtonHTML( $form_is_disabled, $input_label, $attr );
                        } else {
                            $new_text = '';
                        }
                    } elseif ( $input_name == 'preview' ) {
                        $new_text = PFFormUtils::showPreviewButtonHTML( $form_is_disabled, $input_label, $attr );
                    } elseif ( $input_name == 'changes' ) {
                        $new_text = PFFormUtils::showChangesButtonHTML( $form_is_disabled, $input_label, $attr );
                    } elseif ( $input_name == 'cancel' ) {
                        $new_text = PFFormUtils::cancelLinkHTML( $form_is_disabled, $input_label, $attr );
                    } elseif ( $input_name == 'run query' ) {
                        $new_text = PFFormUtils::runQueryButtonHTML( $form_is_disabled, $input_label, $attr );
                    }
                    $section = substr_replace( $section, $new_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc );
                // =====================================================
                // for section processing
                // =====================================================
                } elseif ( $tag_title == 'section' ) {
                    $wgPageFormsFieldNum++;
                    $wgPageFormsTabIndex++;
                    $section_name = trim( $tag_components[1] );
                    $page_section_in_form = PFPageSection::newFromFormTag( $tag_components, $user );
                    $section_text = null;
                    // Split the existing page contents into the textareas in the form.
                    $default_value = "";
                    $section_start_loc = 0;
                    if ( $source_is_page && $existing_page_content !== null ) {
                        // For the last section of the page, there is no trailing newline in
                        // $existing_page_content, but the code below expects it. This code
                        // ensures that there is always trailing newline. T72202
                        if ( substr( $existing_page_content, -1 ) !== "\n" ) {
                            $existing_page_content .= "\n";
                        }
                        $equalsSigns = str_repeat( '=', $page_section_in_form->getSectionLevel() );
                        $searchStr =
                            '/^' .
                            preg_quote( $equalsSigns, '/' ) .
                            '[ ]*?' .
                            preg_quote( $section_name, '/' ) .
                            '[ ]*?' .
                            preg_quote( $equalsSigns, '/' ) .
                            '$/m';
                        if ( preg_match( $searchStr, $existing_page_content, $matches, PREG_OFFSET_CAPTURE ) ) {
                            $section_start_loc = $matches[0][1];
                            $header_text = $matches[0][0];
                            $existing_page_content = str_replace( $header_text, '', $existing_page_content );
                        } else {
                            $section_start_loc = 0;
                        }
                        $section_end_loc = -1;
                        // get the position of the next template or section defined in the form which is not empty and hidden if empty
                        $previous_brackets_end_loc = $brackets_end_loc;
                        $next_section_found = false;
                        // loop until the next section is found
                        while ( !$next_section_found ) {
                            $next_bracket_start_loc = strpos( $section, '{{{', $previous_brackets_end_loc );
                            if ( $next_bracket_start_loc == false ) {
                                $section_end_loc = strpos( $existing_page_content, '{{', $section_start_loc );
                                $next_section_found = true;
                            } else {
                                $next_bracket_end_loc = strpos( $section, '}}}', $next_bracket_start_loc );
                                $bracketed_string_next_section = substr( $section, $next_bracket_start_loc + 3, $next_bracket_end_loc - ( $next_bracket_start_loc + 3 ) );
                                $tag_components_next_section = PFUtils::getFormTagComponents( $bracketed_string_next_section );
                                $page_next_section_in_form = PFPageSection::newFromFormTag( $tag_components_next_section, $user );
                                $tag_title_next_section = trim( $tag_components_next_section[0] );
                                if ( $tag_title_next_section == 'section' ) {
                                    // There is no pattern match for the next section if the section is empty and its hideIfEmpty attribute is set
                                    if ( preg_match( '/(^={1,6}[ ]*?' . preg_quote( $tag_components_next_section[1], '/' ) . '[ ]*?={1,6}\s*?$)/m', $existing_page_content, $matches, PREG_OFFSET_CAPTURE ) ) {
                                        $section_end_loc = $matches[0][1];
                                        $next_section_found = true;
                                    // Check for the next section if no pattern match
                                    } elseif ( $page_next_section_in_form->isHideIfEmpty() ) {
                                        $previous_brackets_end_loc = $next_bracket_end_loc;
                                    } else {
                                        // If none of the above conditions is satisfied, exit the loop.
                                        break;
                                    }
                                } else {
                                    $next_section_found = true;
                                }
                            }
                        }
                        if ( $section_end_loc === -1 || $section_end_loc === null ) {
                            $section_text = substr( $existing_page_content, $section_start_loc );
                            $existing_page_content = substr( $existing_page_content, 0, $section_start_loc );
                        } else {
                            $section_text = substr( $existing_page_content, $section_start_loc, $section_end_loc - $section_start_loc );
                            $existing_page_content = substr( $existing_page_content, 0, $section_start_loc ) . substr( $existing_page_content, $section_end_loc );
                        }
                    }
                    // If input is from the form.
                    if ( ( !$source_is_page ) && $wgRequest ) {
                        $text_per_section = $wgRequest->getArray( '_section' );
                        if ( is_array( $text_per_section ) && array_key_exists( $section_name, $text_per_section ) ) {
                            $section_text = $text_per_section[$section_name];
                        } else {
                            $section_text = '';
                        }
                        // $section_options will allow to pass additional options in the future without breaking backword compatibility
                        $section_options = [ 'hideIfEmpty' => $page_section_in_form->isHideIfEmpty() ];
                        $wiki_page->addSection( $section_name, $page_section_in_form->getSectionLevel(), $section_text, $section_options );
                    }
                    $section_text = trim( $section_text );
                    // Set input name for query string.
                    $input_name = '_section' . '[' . $section_name . ']';
                    $other_args = $page_section_in_form->getSectionArgs();
                    $other_args['isSection'] = true;
                    if ( $page_section_in_form->isMandatory() ) {
                        $other_args['mandatory'] = true;
                    }
                    if ( $page_section_in_form->isHidden() ) {
                        $form_section_text = Html::hidden( $input_name, $section_text );
                    } else {
                        $sectionInput = new PFTextAreaInput( $wgPageFormsFieldNum, $section_text, $input_name, ( $form_is_disabled || $page_section_in_form->isRestricted() ), $other_args );
                        $sectionInput->addJavaScript();
                        $form_section_text = $sectionInput->getHtmlText();
                    }
                    $section = substr_replace( $section, $form_section_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc );
                // =====================================================
                // page info processing
                // =====================================================
                } elseif ( $tag_title == 'info' ) {
                    // TODO: Generate an error message if this is included more than once
                    foreach ( array_slice( $tag_components, 1 ) as $component ) {
                        $sub_components = array_map( 'trim', explode( '=', $component, 2 ) );
                        // Tag names are case-insensitive
                        $tag = strtolower( $sub_components[0] );
                        if ( $tag == 'create title' || $tag == 'add title' ) {
                            // Handle this only if
                            // we're adding a page.
                            if ( !$is_query && !$this->mPageTitle->exists() ) {
                                $form_page_title = $sub_components[1];
                            }
                        } elseif ( $tag == 'edit title' ) {
                            // Handle this only if
                            // we're editing a page.
                            if ( !$is_query && $this->mPageTitle->exists() ) {
                                $form_page_title = $sub_components[1];
                            }
                        } elseif ( $tag == 'query title' ) {
                            // Handle this only if
                            // we're in 'RunQuery'.
                            if ( $is_query ) {
                                $form_page_title = $sub_components[1];
                            }
                        } elseif ( $tag == 'includeonly free text' || $tag == 'onlyinclude free text' ) {
                            $wiki_page->makeFreeTextOnlyInclude();
                        } elseif ( $tag == 'query form at top' ) {
                            // TODO - this should be made a field of
                            // some non-static class that actually
                            // prints the form, instead of requiring
                            // a global variable.
                            global $wgPageFormsRunQueryFormAtTop;
                            $wgPageFormsRunQueryFormAtTop = true;
                        }
                    }
                    // Replace the {{{info}}} tag with a hidden span, instead of a blank, to avoid a
                    // potential security issue.
                    $section = substr_replace( $section, '<span style="visibility: hidden;"></span>', $brackets_loc, $brackets_end_loc + 3 - $brackets_loc );
                // =====================================================
                // default outer level processing
                // =====================================================
                } else {
                    // Tag is not one of the allowed values -
                    // ignore it, other than to HTML-escape it.
                    $form_section_text = htmlspecialchars( substr( $section, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc ) );
                    $section = substr_replace( $section, $form_section_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc );
                    $start_position = $brackets_end_loc;
                }
                // end if
            }
            // end while
            if ( $tif && ( !$tif->allowsMultiple() || $tif->allInstancesPrinted() ) ) {
                $template_text = $wiki_page->createTemplateCallsForTemplateName( $tif->getTemplateName() );
                // Escape the '$' characters for the preg_replace() call.
                $template_text = str_replace( '$', '\$', $template_text );
                // If there is a placeholder in the text, we
                // know that we are doing a replace.
                if ( $existing_page_content && strpos( $existing_page_content, '{{{insertionpoint}}}', 0 ) !== false ) {
                    $existing_page_content = preg_replace( '/\{\{\{insertionpoint\}\}\}(\r?\n?)/',
                        preg_replace( '/\}\}/m', '}�',
                            preg_replace( '/\{\{/m', '�{', $template_text ) ) .
                        "{{{insertionpoint}}}",
                        $existing_page_content );
                }
            }
            $multipleTemplateHTML = '';
            if ( $tif ) {
                if ( $tif->getLabel() != null ) {
                    $fieldsetStartHTML = "<fieldset>\n" . Html::element( 'legend', null, $tif->getLabel() ) . "\n";
                    $fieldsetStartHTML .= $tif->getIntro();
                    if ( !$tif->allowsMultiple() ) {
                        $form_text .= $fieldsetStartHTML;
                    } elseif ( $tif->allowsMultiple() && $tif->getInstanceNum() == 0 ) {
                        $multipleTemplateHTML .= $fieldsetStartHTML;
                    }
                } else {
                    if ( !$tif->allowsMultiple() ) {
                        $form_text .= $tif->getIntro();
                    }
                    if ( $tif->allowsMultiple() && $tif->getInstanceNum() == 0 ) {
                        $multipleTemplateHTML .= $tif->getIntro();
                    }
                }
            }
            if ( $tif && $tif->allowsMultiple() ) {
                if ( $tif->getDisplay() == 'spreadsheet' ) {
                    if ( $tif->allInstancesPrinted() ) {
                        $multipleTemplateHTML .= $this->spreadsheetHTML( $tif );
                        // For spreadsheets, this needs
                        // to be specially inserted.
                        if ( $tif->getLabel() != null ) {
                            $multipleTemplateHTML .= "</fieldset>\n";
                        }
                    }
                } elseif ( $tif->getDisplay() == 'calendar' ) {
                    if ( $tif->allInstancesPrinted() ) {
                        global $wgPageFormsCalendarParams, $wgPageFormsCalendarValues;
                        global $wgPageFormsScriptPath;
                        $text = '';
                        $params = [];
                        foreach ( $tif->getFields() as $formField ) {
                            $templateField = $formField->template_field;
                            $inputType = $formField->getInputType();
                            $values = [ 'name' => $templateField->getFieldName() ];
                            if ( $formField->getLabel() !== null ) {
                                $values['title'] = $formField->getLabel();
                            }
                            $possibleValues = $formField->getPossibleValues();
                            if ( $inputType == 'textarea' ) {
                                $values['type'] = 'textarea';
                            } elseif ( $inputType == 'datetime' ) {
                                $values['type'] = 'datetime';
                            } elseif ( $inputType == 'checkbox' ) {
                                $values['type'] = 'checkbox';
                            } elseif ( $inputType == 'checkboxes' ) {
                                $values['type'] = 'checkboxes';
                            } elseif ( $inputType == 'listbox' ) {
                                $values[