Version: current

      1 <?php
      2 /**
      3  * Represents a field in a form.
      4  *
      5  * A FieldSet contains a number of FormField objects which make up the whole of a form.
      6  * In addition to single fields, FormField objects can be "composite", for example, the {@link TabSet}
      7  * field.  Composite fields let us define complex forms without having to resort to custom HTML.
      8  *
      9  * <b>Subclassing</b>
     10  *
     11  * Define a {@link dataValue()} method that returns a value suitable for inserting into a single database field.
     12  * For example, you might tidy up the format of a date or currency field.
     13  * Define {@link saveInto()} to totally customise saving.
     14  * For example, data might be saved to the filesystem instead of the data record,
     15  * or saved to a component of the data record instead of the data record itself.
     16  *
     17  * @package forms
     18  * @subpackage core
     19  */
     20 class FormField extends RequestHandler {
     21     // Глобальный флаг использования html5 полей на сайте
     22     protected static $use_html5 = false;
     23
     24     // разрешение использования html5 для текущего поля
     25     protected $fieldHTML5 = true;
     26
     27     protected $form;
     28     protected $name, $title, $value ,$message, $messageType, $extraClass;
     29     protected $extraAttributes = array();
     30     protected $html5Attributes = array();
     31
     32     // autocomplete parametr, can use without html5
     33     protected $autocomplete = false;
     34
     35     /**
     36      * @var $description string Adds a "title"-attribute to the markup.
     37      * @todo Implement in all subclasses
     38      */
     39     protected $description;
     40
     41     /**
     42      * @var $extraClasses array Extra CSS-classes for the formfield-container
     43      */
     44     protected $extraClasses = array();
     45
     46     public $dontEscape;
     47
     48     /**
     49      * Name of the template used to render this form field. If not set, then will look up the class
     50      * ancestry for the first matching template where the template name equals the class name.
     51      *
     52      * To explicitly use a custom template or one named other than the form field see
     53      * {@link setTemplate()}.
     54      *
     55      * @var string
     56      */
     57     protected $template;
     58
     59     /**
     60      * Name of the template used to render this form field. If not set, then will look up the class
     61      * ancestry for the first matching template where the template name equals the class name.
     62      *
     63      * To explicitly use a custom template or one named other than the form field see
     64      * {@link setFieldHolderTemplate()}.
     65      *
     66      * @var string
     67      */
     68     protected $fieldHolderTemplate;
     69
     70     /**
     71      * @var string
     72      */
     73     protected $smallFieldHolderTemplate;
     74
     75     /**
     76      * @var $rightTitle string Used in SmallFieldHolder() to force a right-aligned label.
     77      */
     78     protected $rightTitle;
     79
     80     /**
     81      * @var $leftTitle string Used in SmallFieldHolder() to force a left-aligned label with correct spacing.
     82      * Please use $title for FormFields rendered with FieldHolder().
     83      */
     84     protected $leftTitle;
     85
     86     /**
     87      * Set the "tabindex" HTML attribute on the field.
     88      *
     89      * @var int
     90      */
     91     protected $tabIndex;
     92
     93     /**
     94      * Stores a reference to the FieldSet that contains this object.
     95      * @var FieldSet
     96      */
     97     protected $containerFieldSet;
     98
     99     /**
    100      * @var $readonly boolean
    101      */
    102     protected $readonly = false;
    103
    104     /**
    105      * @var $disabled boolean
    106      */
    107     protected $disabled = false;
    108
    109     /**
    110      * @var Custom Validation Message for the Field
    111      */
    112     protected $customValidationMessage = "";
    113
    114     static function allow_html5($val = true) {
    115         self::$use_html5 = $val;
    116     }
    117
    118     static function use_html5() {
    119         return self::$use_html5;
    120     }
    121
    122     function allowHTML5($val) {
    123         $this->fieldHTML5 = $val;
    124     }
    125
    126     function useHTML5() {
    127         return (self::use_html5() && $this->fieldHTML5);
    128     }
    129
    130     // возможные значения атрибута autocomplete поля (взято тут https://wiki.whatwg.org/wiki/Autocomplete_Types#4.10.7.3.1.1_Specifying_field_data_type_hints)
    131     static $possible_autocomplete_values = array('name', 'given-name', 'family-name', 'street-address', 'city', 'region', 'postal-code', 'country-name', 'email', 'tel');
    132
    133     /**
    134      * Create a new field.
    135      * @param name The internal field name, passed to forms.
    136      * @param title The field label.
    137      * @param value The value of the field.
    138      * @param form Reference to the container form
    139      * @param maxLength The Maximum length of the attribute
    140      */
    141     function __construct($name, $title = null, $value = null, $form = null, $rightTitle = null) {
    142         $this->name = $name;
    143         $this->title = ($title === null) ? $name : $title;
    144
    145         if($value !== NULL) $this->setValue($value);
    146         if($form) $this->setForm($form);
    147
    148         parent::__construct();
    149     }
    150
    151     // выставляем html5-атрибут поля
    152     function setHTML5Attribute($title, $value) {
    153         $this->html5Attributes[$title] = $value;
    154     }
    155
    156     // возвращаем html5-атрибут, если задан
    157     function getHTML5Attribute($title) {
    158         if (isset($this->html5Attributes[$title])) {
    159             return $this->html5Attributes[$title];
    160         }
    161         return false;
    162     }
    163
    164     function setAutocomplete($value) {
    165         if (array_search($value, self::$possible_autocomplete_values) !== false) {
    166             $this->autocomplete = $value;
    167             return true;
    168         }
    169         return false;
    170     }
    171
    172     /**
    173      * Return a Link to this field
    174      */
    175     function Link($action = null) {
    176         return Controller::join_links($this->form->FormAction(), 'field/' . $this->name, $action);
    177     }
    178
    179     /**
    180      * Returns the HTML ID of the field - used in the template by label tags.
    181      * The ID is generated as FormName_FieldName.  All Field functions should ensure
    182      * that this ID is included in the field.
    183      */
    184     function id() {
    185         $name = ereg_replace('(^-)|(-$)','',ereg_replace('[^A-Za-z0-9_-]+','-',$this->Name()));
    186         if($this->form) return $this->form->FormName() . '_' . $name;
    187         else return $name;
    188     }
    189
    190     /**
    191      * Returns the field name - used by templates.
    192      *
    193      * @return string
    194      */
    195     function Name() {
    196         return $this->name;
    197     }
    198
    199     function attrName() {
    200         return $this->name;
    201     }
    202
    203     /**
    204      * Returns the field message, used by form validation.
    205      * Use {@link setError()} to set this property.
    206      *
    207      * @return string
    208      */
    209     function Message() {
    210         return $this->message;
    211     }
    212
    213     /**
    214      * Returns the field message type, used by form validation.
    215      * Arbitrary value which is mostly used for CSS classes
    216      * in the rendered HTML, e.g. "required".
    217      * Use {@link setError()} to set this property.
    218      *
    219      * @return string
    220      */
    221     function MessageType() {
    222         return $this->messageType;
    223     }
    224
    225     /**
    226      * Returns the field value - used by templates.
    227      */
    228     function Value() {
    229         return $this->value;
    230     }
    231
    232     /**
    233      * Method to save this form field into the given data object.
    234      * By default, makes use of $this->dataValue()
    235      */
    236     function saveInto(DataObjectInterface $record) {
    237         if($this->name) {
    238             $record->setCastedField($this->name, $this->dataValue());
    239         }
    240     }
    241
    242     /**
    243      * Returns the field value suitable for insertion into the data object
    244      */
    245     function dataValue() {
    246         return $this->value;
    247     }
    248
    249     /**
    250      * Returns the field label - used by templates.
    251      */
    252     function Title() {
    253         return $this->title;
    254     }
    255
    256     function setTitle($val) {
    257         $this->title = $val;
    258     }
    259
    260     function RightTitle() {
    261         return $this->rightTitle;
    262     }
    263
    264     function setRightTitle($val) {
    265         $this->rightTitle = $val;
    266     }
    267
    268     function LeftTitle() {
    269         return $this->leftTitle;
    270     }
    271
    272     function setLeftTitle($val) {
    273         $this->leftTitle = $val;
    274     }
    275
    276     /**
    277      * Set tabindex HTML attribute
    278      * (defaults to none).
    279      *
    280      * @param int $index
    281      */
    282     public function setTabIndex($index) {
    283         $this->tabIndex = $index;
    284     }
    285
    286     /**
    287      * Get tabindex (if previously set)
    288      *
    289      * @return int
    290      */
    291     public function getTabIndex() {
    292         return $this->tabIndex;
    293     }
    294
    295     /**
    296      * Get tabindex HTML string
    297      *
    298      * @param int $increment Increase current tabindex by this value
    299      * @return string
    300      */
    301     protected function getTabIndexHTML($increment = 0) {
    302         $tabIndex = (int)$this->getTabIndex() + (int)$increment;
    303         return (is_numeric($tabIndex)) ? ' tabindex = "' . $tabIndex . '"' : '';
    304     }
    305
    306     /**
    307      * Compiles all CSS-classes. Optionally includes a "nolabel"-class
    308      * if no title was set on the formfield.
    309      * Uses {@link Message()} and {@link MessageType()} to add validatoin
    310      * error classes which can be used to style the contained tags.
    311      *
    312      * @return String CSS-classnames
    313      */
    314     function extraClass() {
    315         $output = "";
    316         if(is_array($this->extraClasses)) {
    317             $output = " " . implode($this->extraClasses, " ");
    318         }
    319
    320         // Allow customization of label and field tag positioning
    321         if(!$this->Title()) $output .= " nolabel";
    322
    323         // Allow custom styling of any element in the container based
    324         // on validation errors, e.g. red borders on input tags.
    325         // CSS-Class needs to be different from the one rendered
    326         // through {@link FieldHolder()}
    327         if($this->Message()) $output .= " holder-" . $this->MessageType();
    328
    329         return $output;
    330     }
    331
    332     /**
    333      * Add a CSS-class to the formfield-container.
    334      *
    335      * @param $class String
    336      */
    337     function addExtraClass($class) {
    338         $this->extraClasses[$class] = $class;
    339     }
    340
    341     /**
    342      * Remove a CSS-class from the formfield-container.
    343      *
    344      * @param $class String
    345      */
    346     function removeExtraClass($class) {
    347         if(isset($this->extraClasses) && array_key_exists($class, $this->extraClasses)) unset($this->extraClasses[$class]);
    348     }
    349
    350     /**
    351      * Return FormField extraClasses as DataObjectSet
    352      *
    353      * @return DataObjectSet
    354      */
    355     function getExtraClasses() {
    356         $classes = new DataObjectSet();
    357         if (count($this->extraClasses)) {
    358             foreach($this->extraClasses as $class) {
    359                 $classes->push(new ArrayData(array(
    360                     'Class' => $class
    361                 )));
    362             }
    363         }
    364         return $classes;
    365     }
    366
    367     /**
    368      * Add an attribute
    369      *
    370      * @param $name String
    371      * @param $value String
    372      */
    373     function addExtraAttribute($name, $value) {
    374         $this->extraAttributes[$name] = $value;
    375     }
    376
    377     /**
    378      * Remove an attribute
    379      *
    380      * @param $name String
    381      */
    382     function removeExtraAttribute($name) {
    383         if(isset($this->extraAttributes) && array_key_exists($name, $this->extraAttributes)) unset($this->extraAttributes[$name]);
    384     }
    385
    386     /**
    387      * Returns a version of a title suitable for insertion into an HTML attribute
    388      */
    389     function attrTitle() {
    390         return Convert::raw2att($this->title);
    391     }
    392     /**
    393      * Returns a version of a title suitable for insertion into an HTML attribute
    394      */
    395     function attrValue() {
    396         return Convert::raw2att($this->value);
    397     }
    398
    399     /**
    400      * Set the field value.
    401      * Returns $this.
    402      */
    403     function setValue($value) {
    404         $this->value = $value; return $this;
    405     }
    406
    407     /**
    408      * Set the field name
    409      */
    410     function setName($name) {
    411         $this->name = $name;
    412     }
    413
    414     /**
    415      * Set the container form.
    416      * This is called whenever you create a new form and put fields inside it, so that you don't
    417      * have to worry about linking the two.
    418      */
    419     function setForm($form) {
    420         $this->form = $form;
    421     }
    422
    423     /**
    424      * Get the currently used form.
    425      *
    426      * @return Form
    427      */
    428     function getForm() {
    429         return $this->form;
    430     }
    431
    432     /**
    433      * Return TRUE if security token protection is enabled on the parent {@link Form}.
    434      *
    435      * @return bool
    436      */
    437     public function securityTokenEnabled() {
    438         return $this->getForm() && $this->getForm()->securityTokenEnabled();
    439     }
    440
    441     /**
    442      * Sets the error message to be displayed on the form field
    443      * Set by php validation of the form
    444      */
    445     function setError($message,$messageType){
    446         $this->message = $message;
    447         $this->messageType = $messageType;
    448     }
    449
    450     /**
    451      * Set the custom error message to show instead of the default
    452      * format of Please Fill In XXX. Different from setError() as
    453      * that appends it to the standard error messaging
    454      *
    455      * @param String Message for the error
    456      */
    457     public function setCustomValidationMessage($msg) {
    458         $this->customValidationMessage = $msg;
    459     }
    460
    461     /**
    462      * Get the custom error message for this form field. If a custom
    463      * message has not been defined then just return blank. The default
    464      * error is defined on {@link Validator}.
    465      *
    466      * @todo Should the default error message be stored here instead
    467      * @return String
    468      */
    469     public function getCustomValidationMessage() {
    470         return $this->customValidationMessage;
    471     }
    472
    473     /**
    474      * Returns the form field - used by templates.
    475      * Although FieldHolder is generally what is inserted into templates, all of the field holder
    476      * templates make use of $Field.  It's expected that FieldHolder will give you the "complete"
    477      * representation of the field on the form, whereas Field will give you the core editing widget,
    478      * such as an input tag.
    479      *
    480      * Our base FormField class just returns a span containing the value.  This should be overridden!
    481      */
    482     function Field() {
    483         if($this->value) $value = $this->dontEscape ? ($this->reserveNL ? Convert::raw2xml($this->value) : $this->value) : Convert::raw2xml($this->value);
    484         else $value = '<i>(' . _t('FormField.NONE', 'none') . ')</i>';
    485
    486         $attributes = array(
    487             'id' => $this->id(),
    488             'class' => 'readonly ' . $this->class . ($this->extraClass() ? $this->extraClass() : '')
    489         );
    490
    491         $hiddenAttributes = array(
    492             'type' => 'hidden',
    493             'name' => $this->name,
    494             'value' => $this->value,
    495             'tabindex' => $this->getTabIndex()
    496         );
    497
    498         $containerSpan = $this->createTag('span', $attributes, $value);
    499         $hiddenInput = $this->createTag('input', $hiddenAttributes);
    500
    501         return $containerSpan . "\n" . $hiddenInput;
    502     }
    503
    504     /**
    505      * Returns a "Field Holder" for this field - used by templates.
    506      * Forms are constructed from by concatenating a number of these field holders.  The default
    507      * field holder is a label and form field inside a paragraph tag.
    508      *
    509      * Composite fields can override FieldHolder to create whatever visual effects you like.  It's
    510      * a good idea to put the actual HTML for field holders into templates.  The default field holder
    511      * is the DefaultFieldHolder template.  This lets you override the HTML for specific sites, if it's
    512      * necessary.
    513      *
    514      * @todo Add "validationError" if needed.
    515      */
    516     function FieldHolder() {
    517         if ($this->Required()) $this->addExtraClass('requiredField');
    518         return $this->renderWith($this->fieldHolderTemplates());
    519     }
    520
    521     /**
    522      * Returns a restricted field holder used within things like FieldGroups.
    523      */
    524     function SmallFieldHolder() {
    525        return $this->renderWith($this->smallFieldHolderTemplates());
    526     }
    527
    528     /**
    529      * Set name of template (without path or extension).
    530      *
    531      * Caution: Not consistently implemented in all subclasses, please check the {@link Field()}
    532      * method on the subclass for support.
    533      *
    534      * @param string $template
    535      *
    536      * @return $this
    537      */
    538     public function setTemplate($template) {
    539         $this->template = $template;
    540
    541         return $this;
    542     }
    543
    544     /**
    545      * Set name of template (without path or extension) for the holder, which in turn is
    546      * responsible for rendering {@link Field()}.
    547      *
    548      * Caution: Not consistently implemented in all subclasses, please check the {@link Field()}
    549      * method on the subclass for support.
    550      *
    551      * @param string $fieldHolderTemplate
    552      *
    553      * @return $this
    554      */
    555     public function setFieldHolderTemplate($fieldHolderTemplate) {
    556         $this->fieldHolderTemplate = $fieldHolderTemplate;
    557
    558         return $this;
    559     }
    560
    561     /**
    562      * Set name of template (without path or extension) for the small holder, which in turn is
    563      * responsible for rendering {@link Field()}.
    564      *
    565      * Caution: Not consistently implemented in all subclasses, please check the {@link Field()}
    566      * method on the subclass for support.
    567      *
    568      * @param string $smallFieldHolderTemplate
    569      *
    570      * @return $this
    571      */
    572     public function setSmallFieldHolderTemplate($smallFieldHolderTemplate) {
    573         $this->smallFieldHolderTemplate = $smallFieldHolderTemplate;
    574
    575         return $this;
    576     }
    577
    578     /**
    579      * Generate an array of class name strings to use for rendering this form field into HTML.
    580      *
    581      * @param string $customTemplate
    582      * @param string $customTemplateSuffix
    583      *
    584      * @return array
    585      */
    586     private function _templates($customTemplate = null, $customTemplateSuffix = null) {
    587         $matches = array();
    588         foreach(array_reverse(ClassInfo::ancestry($this)) as $className) {
    589             $matches[] = $className . $customTemplateSuffix;
    590             if($className == "FormField") {
    591                 break;
    592             }
    593         }
    594         if($customTemplate) {
    595             array_unshift($matches, $customTemplate);
    596         }
    597         return $matches;
    598     }
    599
    600     /**
    601      * Returns an array of templates to use for rendering {@link FieldHolder}.
    602      *
    603      * @return array
    604      */
    605     public function fieldTemplates() {
    606         return $this->_templates($this->template);
    607     }
    608
    609     /**
    610      * Returns an array of templates to use for rendering {@link FieldHolder}.
    611      *
    612      * @return array
    613      */
    614     public function fieldHolderTemplates() {
    615         return $this->_templates(
    616             $this->fieldHolderTemplate,
    617             '_holder'
    618         );
    619     }
    620
    621     /**
    622      * Returns an array of templates to use for rendering {@link SmallFieldHolder}.
    623      *
    624      * @return array
    625      */
    626     public function smallFieldHolderTemplates() {
    627         return $this->_templates(
    628             $this->smallFieldHolderTemplate,
    629             '_holder_small'
    630         );
    631     }
    632
    633     /**
    634      * Returns true if this field is a composite field.
    635      * To create composite field types, you should subclass {@link CompositeField}.
    636      */
    637     function isComposite() { return false; }
    638
    639     /**
    640      * Returns true if this field has its own data.
    641      * Some fields, such as titles and composite fields, don't actually have any data.  It doesn't
    642      * make sense for data-focused methods to look at them.  By overloading hasData() to return false,
    643      * you can prevent any data-focused methods from looking at it.
    644      *
    645      * @see FieldSet::collateDataFields()
    646      */
    647     function hasData() { return true; }
    648
    649     /**
    650      * @return boolean
    651      */
    652     function isReadonly() {
    653         return $this->readonly;
    654     }
    655
    656     /**
    657      * Sets readonly-flag on form-field. Please use performReadonlyTransformation()
    658      * to actually transform this instance.
    659      * @param $bool boolean Setting "false" has no effect on the field-state.
    660      */
    661     function setReadonly($bool) {
    662         $this->readonly = $bool;
    663     }
    664
    665     /**
    666      * @return boolean
    667      */
    668     function isDisabled() {
    669         return $this->disabled;
    670     }
    671
    672     /**
    673      * Sets disabed-flag on form-field. Please use performDisabledTransformation()
    674      * to actually transform this instance.
    675      * @param $bool boolean Setting "false" has no effect on the field-state.
    676      */
    677     function setDisabled($bool) {
    678         $this->disabled = $bool;
    679     }
    680
    681     /**
    682      * Returns a readonly version of this field
    683      */
    684     function performReadonlyTransformation() {
    685         $field = new ReadonlyField($this->name, $this->title, $this->value);
    686         $field->addExtraClass($this->extraClass());
    687         $field->setForm($this->form);
    688         return $field;
    689     }
    690
    691     /**
    692      * Return a disabled version of this field
    693      */
    694     function performDisabledTransformation() {
    695         $clone = clone $this;
    696         $disabledClassName = $clone->class . '_Disabled';
    697         if( ClassInfo::exists( $disabledClassName ) )
    698             return new $disabledClassName( $this->name, $this->title, $this->value );
    699         elseif($clone->hasMethod('setDisabled')){
    700             $clone->setDisabled(true);
    701             return $clone;
    702         }else{
    703             return $this->performReadonlyTransformation();
    704         }
    705     }
    706
    707     function transform(FormTransformation $trans) {
    708         return $trans->transform($this);
    709     }
    710
    711     function hasClass($class){
    712         $patten = '/'.strtolower($class).'/i';
    713         $subject = strtolower($this->class." ".$this->extraClass());
    714         return preg_match($patten, $subject);
    715     }
    716
    717     /**
    718      * Returns the field type - used by templates.
    719      * The field type is the class name with the word Field dropped off the end, all lowercase.
    720      * It's handy for assigning HTML classes.
    721      */
    722     function Type() {return strtolower(ereg_replace('Field$','',$this->class)); }
    723
    724     /**
    725      * Construct and return HTML tag.
    726      *
    727      * @todo Transform to static helper method.
    728      */
    729     function createTag($tag, $attributes, $content = null, $useHTML5 = true) {
    730         $attributes = array_merge($attributes, $this->extraAttributes);
    731         if ($useHTML5 && $this->useHTML5()) {
    732             $attributes = array_merge($attributes, $this->html5Attributes);
    733         }
    734         if ($this->autocomplete) {
    735             $attributes = array_merge($attributes, array('autocomplete' => $this->autocomplete));
    736         }
    737
    738         $attrData = new DataObjectSet();
    739         foreach($attributes as $k => $v) {
    740             if(!empty($v) || $v === '0' || $k == 'value') {
    741                 $attrData->push(new ArrayData(array(
    742                     'Attribute' => $k,
    743                     'Value' => Convert::raw2att($v),
    744                 )));
    745             }
    746         }
    747
    748         $data = array('Attrubutes' => $attrData, 'Tag' => $tag);
    749         if (is_object($content) && is_a($content, 'DataObjectSet')) {
    750             $data['Options'] = $content;
    751         } elseif(is_array($content)) {
    752             $data['Options'] = new DataObjectSet($content);
    753         } else {
    754             $data['Content'] = $content;
    755         }
    756         return $this->customise($data)->renderWith($this->fieldTemplates());
    757     }
    758
    759     /**
    760      * javascript handler Functions for each field type by default
    761      * formfield doesnt have a validation function
    762      *
    763      * @todo shouldn't this be an abstract method?
    764      */
    765     function jsValidation() {
    766     }
    767
    768     /**
    769      * Validation Functions for each field type by default
    770      * formfield doesnt have a validation function
    771      *
    772      * @todo shouldn't this be an abstract method?
    773      */
    774     function validate() {
    775         return true;
    776     }
    777
    778     /**
    779      * Describe this field, provide help text for it.
    780      * The function returns this so it can be used like this:
    781      * $action = FormAction::create('submit', 'Submit')->describe("Send your changes to be approved")
    782      */
    783     function describe($description) {
    784         $this->description = $description;
    785         return $this;
    786     }
    787
    788     function debug() {
    789         return "$this->class ($this->name: $this->title : <font style='color:red;'>$this->message</font>) = $this->value";
    790     }
    791
    792     /**
    793      * This function is used by the template processor.  If you refer to a field as a $ variable, it
    794      * will return the $Field value.
    795      */
    796     function forTemplate() {
    797         return $this->Field();
    798     }
    799
    800     /**
    801      * @uses Validator->fieldIsRequired()
    802      * @return boolean
    803      */
    804     function Required() {
    805         if($this->form && ($validator = $this->form->Validator)) {
    806             return $validator->fieldIsRequired($this->name);
    807         }
    808     }
    809
    810     /**
    811      * Takes a fieldname and converts camelcase to spaced
    812      * words. Also resolves combined fieldnames with dot syntax
    813      * to spaced words.
    814      *
    815      * Examples:
    816      * - 'TotalAmount' will return 'Total Amount'
    817      * - 'Organisation.ZipCode' will return 'Organisation Zip Code'
    818      *
    819      * @param string $fieldName
    820      * @return string
    821      */
    822     public function name_to_label($fieldName) {
    823         if(strpos($fieldName, '.') !== false) {
    824             $parts = explode('.', $fieldName);
    825             $label = $parts[count($parts)-2] . ' ' . $parts[count($parts)-1];
    826         } else {
    827             $label = $fieldName;
    828         }
    829         $label = preg_replace("/([a-z]+)([A-Z])/","$1 $2", $label);
    830
    831         return $label;
    832     }
    833
    834     /**
    835      * Set the fieldset that contains this field.
    836      *
    837      * @param FieldSet $containerFieldSet
    838      */
    839     function setContainerFieldSet($containerFieldSet) {
    840         $this->containerFieldSet = $containerFieldSet;
    841     }
    842
    843     function rootFieldSet() {
    844         if(is_object($this->containerFieldSet)) return $this->containerFieldSet->rootFieldSet();
    845         else user_error("rootFieldSet() called on $this->class object without a containerFieldSet", E_USER_ERROR);
    846     }
    847
    848 }
    849 ?>

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -