static function newFromFormFieldTag($tag_components, $template_in_form, $form_is_disabled)
 {
     global $wgParser, $wgUser;
     $field_name = trim($tag_components[1]);
     // See if this field matches one of the fields defined for this
     // template - if it is, use all available information about
     // that field; if it's not, either include it in the form or
     // not, depending on whether the template has a 'strict'
     // setting in the form definition.
     $the_field = null;
     $all_fields = $template_in_form->getAllFields();
     foreach ($all_fields as $cur_field) {
         if ($field_name == $cur_field->getFieldName()) {
             $the_field = $cur_field;
             break;
         }
     }
     if ($the_field == null) {
         if ($template_in_form->strictParsing()) {
             $dummy_ff = new SFFormField();
             $dummy_ff->template_field = new SFTemplateField();
             $dummy_ff->mIsList = false;
             return $dummy_ff;
         }
         $the_field = SFTemplateField::create($field_name, null);
     }
     // Create an SFFormField object, containing this field as well
     // as settings from the form definition file.
     $f = new SFFormField();
     $f->template_field = $the_field;
     $f->mFieldArgs = array();
     $semantic_property = null;
     $cargo_table = $cargo_field = null;
     $show_on_select = array();
     $fullFieldName = $template_in_form->getTemplateName() . '[' . $field_name . ']';
     // Cycle through the other components.
     for ($i = 2; $i < count($tag_components); $i++) {
         $component = trim($tag_components[$i]);
         if ($component == 'mandatory') {
             $f->mIsMandatory = true;
         } elseif ($component == 'hidden') {
             $f->mIsHidden = true;
         } elseif ($component == 'restricted') {
             $f->mIsRestricted = !$wgUser || !$wgUser->isAllowed('editrestrictedfields');
         } elseif ($component == 'list') {
             $f->mIsList = true;
         } elseif ($component == 'unique') {
             $f->mFieldArgs['unique'] = true;
         } elseif ($component == 'edittools') {
             // free text only
             $f->mFieldArgs['edittools'] = true;
         }
         $sub_components = array_map('trim', explode('=', $component, 2));
         if (count($sub_components) == 1) {
             // add handling for single-value params, for custom input types
             $f->mFieldArgs[$sub_components[0]] = true;
             if ($component == 'holds template') {
                 $f->mIsHidden = true;
                 $f->mHoldsTemplate = true;
             }
         } elseif (count($sub_components) == 2) {
             // First, set each value as its own entry in $this->mFieldArgs.
             $f->mFieldArgs[$sub_components[0]] = $sub_components[1];
             // Then, do all special handling.
             if ($sub_components[0] == 'input type') {
                 $f->mInputType = $sub_components[1];
             } elseif ($sub_components[0] == 'default') {
                 // We call recursivePreprocess() here,
                 // and not the more standard
                 // recursiveTagParse(), so that
                 // wikitext in the value, and bare URLs,
                 // will not get turned into HTML.
                 $f->mDefaultValue = $wgParser->recursivePreprocess($sub_components[1]);
             } elseif ($sub_components[0] == 'preload') {
                 $f->mPreloadPage = $sub_components[1];
             } elseif ($sub_components[0] == 'show on select') {
                 // html_entity_decode() is needed to turn '&gt;' to '>'
                 $vals = explode(';', html_entity_decode($sub_components[1]));
                 foreach ($vals as $val) {
                     $val = trim($val);
                     if (empty($val)) {
                         continue;
                     }
                     $option_div_pair = explode('=>', $val, 2);
                     if (count($option_div_pair) > 1) {
                         $option = $option_div_pair[0];
                         $div_id = $option_div_pair[1];
                         if (array_key_exists($div_id, $show_on_select)) {
                             $show_on_select[$div_id][] = $option;
                         } else {
                             $show_on_select[$div_id] = array($option);
                         }
                     } else {
                         $show_on_select[$val] = array();
                     }
                 }
             } elseif ($sub_components[0] == 'autocomplete on property') {
                 $f->mFieldArgs['autocomplete field type'] = 'property';
                 $f->mFieldArgs['autocompletion source'] = $sub_components[1];
             } elseif ($sub_components[0] == 'autocomplete on category') {
                 $f->mFieldArgs['autocomplete field type'] = 'category';
                 $f->mFieldArgs['autocompletion source'] = $sub_components[1];
             } elseif ($sub_components[0] == 'autocomplete on concept') {
                 $f->mFieldArgs['autocomplete field type'] = 'concept';
                 $f->mFieldArgs['autocompletion source'] = $sub_components[1];
             } elseif ($sub_components[0] == 'autocomplete on namespace') {
                 $f->mFieldArgs['autocomplete field type'] = 'namespace';
                 $autocompletion_source = $sub_components[1];
                 // Special handling for "main" (blank)
                 // namespace.
                 if ($autocompletion_source == "") {
                     $autocompletion_source = "main";
                 }
                 $f->mFieldArgs['autocompletion source'] = $autocompletion_source;
             } elseif ($sub_components[0] == 'autocomplete from url') {
                 $f->mFieldArgs['autocomplete field type'] = 'external_url';
                 $f->mFieldArgs['autocompletion source'] = $sub_components[1];
                 // 'external' autocompletion is always done remotely, i.e. via API
                 $f->mFieldArgs['remote autocompletion'] = true;
             } elseif ($sub_components[0] == 'values') {
                 // Handle this one only after
                 // 'delimiter' has also been set.
                 $values = $wgParser->recursiveTagParse($sub_components[1]);
             } elseif ($sub_components[0] == 'values from property') {
                 $propertyName = $sub_components[1];
                 $f->mPossibleValues = SFUtils::getAllValuesForProperty($propertyName);
             } elseif ($sub_components[0] == 'values from query') {
                 $pages = SFUtils::getAllPagesForQuery($sub_components[1]);
                 foreach ($pages as $page) {
                     $page_name_for_values = $page->getDbKey();
                     $f->mPossibleValues[] = $page_name_for_values;
                 }
             } elseif ($sub_components[0] == 'values from category') {
                 $category_name = ucfirst($sub_components[1]);
                 $f->mPossibleValues = SFUtils::getAllPagesForCategory($category_name, 10);
             } elseif ($sub_components[0] == 'values from concept') {
                 $f->mPossibleValues = SFUtils::getAllPagesForConcept($sub_components[1]);
             } elseif ($sub_components[0] == 'values from namespace') {
                 $f->mPossibleValues = SFUtils::getAllPagesForNamespace($sub_components[1]);
             } elseif ($sub_components[0] == 'values dependent on') {
                 global $sfgDependentFields;
                 $sfgDependentFields[] = array($sub_components[1], $fullFieldName);
             } elseif ($sub_components[0] == 'unique for category') {
                 $f->mFieldArgs['unique'] = true;
                 $f->mFieldArgs['unique_for_category'] = $sub_components[1];
             } elseif ($sub_components[0] == 'unique for namespace') {
                 $f->mFieldArgs['unique'] = true;
                 $f->mFieldArgs['unique_for_namespace'] = $sub_components[1];
             } elseif ($sub_components[0] == 'unique for concept') {
                 $f->mFieldArgs['unique'] = true;
                 $f->mFieldArgs['unique_for_concept'] = $sub_components[1];
             } elseif ($sub_components[0] == 'property') {
                 $semantic_property = $sub_components[1];
             } elseif ($sub_components[0] == 'cargo table') {
                 $cargo_table = $sub_components[1];
             } elseif ($sub_components[0] == 'cargo field') {
                 $cargo_field = $sub_components[1];
             } elseif ($sub_components[0] == 'default filename') {
                 $default_filename = str_replace('&lt;page name&gt;', $page_name, $sub_components[1]);
                 // Parse value, so default filename can
                 // include parser functions.
                 $default_filename = $wgParser->recursiveTagParse($default_filename);
                 $f->mFieldArgs['default filename'] = $default_filename;
             } elseif ($sub_components[0] == 'restricted') {
                 $f->mIsRestricted = !array_intersect($wgUser->getEffectiveGroups(), array_map('trim', explode(',', $sub_components[1])));
             }
         }
     }
     // end for
     if (!array_key_exists('delimiter', $f->mFieldArgs)) {
         $f->mFieldArgs['delimiter'] = ",";
     }
     $delimiter = $f->mFieldArgs['delimiter'];
     // If the 'values' parameter was set, separate it based on the
     // 'delimiter' parameter, if any.
     if (!empty($values)) {
         // Remove whitespaces, and un-escape characters
         $valuesArray = array_map('trim', explode($delimiter, $values));
         $f->mPossibleValues = array_map('htmlspecialchars_decode', $valuesArray);
     }
     // If we're using Cargo, there's no equivalent for "values from
     // property" - instead, we just always get the values if a
     // field and table have been specified.
     if (is_null($f->mPossibleValues) && defined('CARGO_VERSION') && $cargo_table != null && $cargo_field != null) {
         // We only want the non-null values. Ideally this could
         // be done by calling getValuesForCargoField() with
         // an "IS NOT NULL" clause, but unfortunately that fails
         // for array/list fields.
         // Instead of getting involved with all that, we'll just
         // remove the null/blank values afterward.
         $cargoValues = SFUtils::getAllValuesForCargoField($cargo_table, $cargo_field);
         $f->mPossibleValues = array_filter($cargoValues, 'strlen');
     }
     if (!is_null($f->mPossibleValues)) {
         if (array_key_exists('mapping template', $f->mFieldArgs)) {
             $f->mPossibleValues = SFUtils::getLabelsFromTemplate($f->mPossibleValues, $f->mFieldArgs['mapping template']);
         } elseif (array_key_exists('mapping property', $f->mFieldArgs)) {
             $f->mPossibleValues = SFUtils::getLabelsFromProperty($f->mPossibleValues, $f->mFieldArgs['mapping property']);
         } elseif (array_key_exists('mapping cargo table', $f->mFieldArgs) && array_key_exists('mapping cargo field', $f->mFieldArgs)) {
             $f->mPossibleValues = SFUtils::getLabelsFromCargoField($f->mPossibleValues, $f->mFieldArgs['mapping cargo table'], $f->mFieldArgs['mapping cargo field']);
         }
     }
     // Backwards compatibility.
     if ($f->mInputType == 'datetime with timezone') {
         $f->mInputType = 'datetime';
         $f->mFieldArgs['include timezone'] = true;
     } elseif ($f->mInputType == 'text' || $f->mInputType == 'textarea') {
         // Backwards compatibility.
         $f->mFieldArgs['no autocomplete'] = true;
     }
     if ($template_in_form->allowsMultiple()) {
         $f->mFieldArgs['part_of_multiple'] = true;
     }
     if (count($show_on_select) > 0) {
         $f->mFieldArgs['show on select'] = $show_on_select;
     }
     // Disable this field if either the whole form is disabled, or
     // it's a restricted field and user doesn't have sysop privileges.
     $f->mIsDisabled = $form_is_disabled || $f->mIsRestricted;
     // Do some data storage specific to the Semantic MediaWiki and
     // Cargo extensions.
     if (defined('SMW_VERSION')) {
         // If a property was set in the form definition,
         // overwrite whatever is set in the template field -
         // this is somewhat of a hack, since parameters set in
         // the form definition are meant to go into the
         // SFFormField object, not the SFTemplateField object
         // it contains;
         // it seemed like too much work, though, to create an
         // SFFormField::setSemanticProperty() function just for
         // this call.
         if (!is_null($semantic_property)) {
             $f->template_field->setSemanticProperty($semantic_property);
         } else {
             $semantic_property = $f->template_field->getSemanticProperty();
         }
         if (!is_null($semantic_property)) {
             global $sfgFieldProperties;
             $sfgFieldProperties[$fullFieldName] = $semantic_property;
         }
     }
     if (defined('CARGO_VERSION')) {
         if ($cargo_table != null && $cargo_field != null) {
             $f->template_field->setCargoFieldData($cargo_table, $cargo_field);
         }
         $fullCargoField = $f->template_field->getFullCargoField();
         if (!is_null($fullCargoField)) {
             global $sfgCargoFields;
             $sfgCargoFields[$fullFieldName] = $fullCargoField;
         }
     }
     if ($template_in_form->getTemplateName() == null || $template_in_form->getTemplateName() === '') {
         $f->mInputName = $field_name;
     } elseif ($template_in_form->allowsMultiple()) {
         // 'num' will get replaced by an actual index, either in PHP
         // or in Javascript, later on
         $f->mInputName = $template_in_form->getTemplateName() . '[num][' . $field_name . ']';
         $f->setFieldArg('origName', $template_in_form->getTemplateName() . '[' . $field_name . ']');
     } else {
         $f->mInputName = $template_in_form->getTemplateName() . '[' . $field_name . ']';
     }
     return $f;
 }