Version: current

      1 <?php
      2 /**
      3  * TableField behaves in the same manner as TableListField, however allows the addition of
      4  * fields and editing of attributes specified, and filtering results.
      5  *
      6  * Caution: If you insert DropdownFields in the fieldTypes-array, make sure they have an empty first option.
      7  * Otherwise the saving can't determine if a new row should really be saved.
      8  *
      9  * Caution: TableField relies on {@FormResponse} to reload the field after it is saved.
     10  * A TableField-instance should never be saved twice without reloading, because otherwise it
     11  * can't determine if a field is new (=create) or existing (=update), and will produce duplicates.
     12  *
     13  * @param $name string The fieldname
     14  * @param $sourceClass string The source class of this field
     15  * @param $fieldList array An array of field headings of Fieldname => Heading Text (eg. heading1)
     16  * @param $fieldTypes array An array of field types of fieldname => fieldType (eg. formfield). Do not use for extra data/hiddenfields.
     17  * @param $filterField string The field to filter by.  Give the filter value in $sourceFilter.  The value will automatically be set on new records.
     18  * @param $sourceFilter string If $filterField has a value, then this is the value to filter by.  Otherwise, it is a SQL filter expression.
     19  * @param $editExisting boolean (Note: Has to stay on this position for legacy reasons)
     20  * @param $sourceSort string
     21  * @param $sourceJoin string
     22  *
     23  * @todo We should refactor this to support a single FieldSet instead of evaluated Strings for building FormFields
     24  *
     25  * @package forms
     26  * @subpackage fields-relational
     27  */
     28
     29 class TableField extends TableListField {
     30
     31     protected $sourceClass;
     32
     33     protected $sourceFilter;
     34
     35     protected $fieldList;
     36
     37     /**
     38      * A "Field = Value" filter can be specified by setting $this->filterField and $this->filterValue.  This has the advantage of auto-populating
     39      * new records
     40      */
     41     protected $filterField = null;
     42
     43     /**
     44      * A "Field = Value" filter can be specified by setting $this->filterField and $this->filterValue.  This has the advantage of auto-populating
     45      * new records
     46      */
     47     protected $filterValue = null;
     48
     49     /**
     50      * @var $fieldTypes FieldSet
     51      * Caution: Use {@setExtraData()} instead of manually adding HiddenFields if you want to
     52      * preset relations or other default data.
     53      */
     54     protected $fieldTypes;
     55
     56     protected $sourceSort;
     57
     58     protected $sourceJoin;
     59
     60     /**
     61      * @var $template string Template-Overrides
     62      */
     63     protected $template = "TableField";
     64
     65     /**
     66      * @var $extraData array Any extra data that need to be included, e.g. to retain
     67      * has-many relations. Format: array('FieldName' => 'Value')
     68      */
     69     protected $extraData;
     70
     71     protected $tempForm;
     72
     73     /**
     74      * Influence output without having to subclass the template.
     75      */
     76     protected $permissions = array(
     77         "edit",
     78         "delete",
     79         "add",
     80         //"export",
     81     );
     82
     83     public $transformationConditions = array();
     84
     85     /**
     86      * @var $requiredFields array Required fields as a numerical array.
     87      * Please use an instance of Validator on the including
     88      * form.
     89      */
     90     protected $requiredFields = null;
     91
     92     /**
     93      * Shows a row of empty fields for adding a new record
     94      * (turned on by default).
     95      * Please use {@link TableField::$permissions} to control
     96      * if the "add"-functionality incl. button is shown at all.
     97      *
     98      * @param boolean $showAddRow
     99      */
    100     public $showAddRow = true;
    101
    102     /**
    103      * Automatically detect a has-one relationship
    104      * in the popup (=child-class) and save the relation ID.
    105      *
    106      * @var boolean
    107      */
    108     protected $relationAutoSetting = true;
    109
    110     function __construct($name, $sourceClass, $fieldList = null, $fieldTypes, $filterField = null,
    111                         $sourceFilter = null, $editExisting = true, $sourceSort = null, $sourceJoin = null) {
    112
    113         $this->fieldTypes = $fieldTypes;
    114         $this->filterField = $filterField;
    115
    116         $this->editExisting = $editExisting;
    117
    118         // If we specify filterField, then an implicit source filter of "filterField = sourceFilter" is used.
    119         if($filterField) {
    120             $this->filterValue = $sourceFilter;
    121             $sourceFilter = "\"$filterField\" = '" . Convert::raw2sql($sourceFilter) . "'";
    122         }
    123         parent::__construct($name, $sourceClass, $fieldList, $sourceFilter, $sourceSort, $sourceJoin);
    124     }
    125
    126     /**
    127      * Displays the headings on the template
    128      *
    129      * @return DataObjectSet
    130      */
    131     function Headings() {
    132         $i=0;
    133         foreach($this->fieldList as $fieldName => $fieldTitle) {
    134             $extraClass = "col".$i;
    135             $class = $this->fieldTypes[$fieldName];
    136             if(is_object($class)) $class = "";
    137             $class = $class." ".$extraClass;
    138             $headings[] = new ArrayData(array("Name" => $fieldName, "Title" => $fieldTitle, "Class" => $class));
    139             $i++;
    140         }
    141         return new DataObjectSet($headings);
    142     }
    143
    144     /**
    145      * Calculates the number of columns needed for colspans
    146      * used in template
    147      *
    148      * @return int
    149      */
    150     function ItemCount() {
    151         return count($this->fieldList);
    152     }
    153
    154     /**
    155      * Returns the databased saved items, from DataObjects
    156      *
    157      * @return DataObjectSet
    158      */
    159      /*
    160         by menedem:
    161             без этой функции корректно работает паджинация для TableField. Ее нет в TableField версии 3.1.5 (единственное отличие)
    162             без паджинации большой список в TableField не занееш - всегда сохраняет всю портянку
    163             оставляю пока так, если вылезут глюки - нужно вернуть
    164     function sourceItems() {
    165         if($this->customSourceItems) {
    166             $items = $this->customSourceItems;
    167         } elseif($this->cachedSourceItems) {
    168             $items = $this->cachedSourceItems;
    169         } else {
    170             // get query
    171             $dataQuery = $this->getQuery();
    172             // get data
    173             $records = $dataQuery->execute();
    174             $items = singleton($this->sourceClass)->buildDataObjectSet($records);
    175         }
    176
    177         return $items;
    178     }
    179     */
    180     /**
    181      * Displays the items from {@link sourceItems()} using the encapsulation object.
    182      * If the field value has been set as an array (e.g. after a failed validation),
    183      * it generates the rows from array data instead.
    184      * Used in the formfield template to iterate over each row.
    185      *
    186      * @return DataObjectSet Collection of {@link TableField_Item}
    187      */
    188     function Items() {
    189         // holds TableField_Item instances
    190         $items = new DataObjectSet();
    191
    192         $sourceItems = $this->sourceItems();
    193
    194         // either load all rows from the field value,
    195         // (e.g. when validation failed), or from sourceItems()
    196         if($this->value) {
    197             if(!$sourceItems) $sourceItems = new DataObjectSet();
    198
    199             // get an array keyed by rows, rather than values
    200             $rows = $this->sortData(ArrayLib::invert($this->value));
    201             // ignore all rows which are already saved
    202             if(isset($rows['new'])) {
    203                 $newRows = $this->sortData($rows['new']);
    204                 // iterate over each value (not each row)
    205                 $i = 0;
    206                 foreach($newRows as $idx => $newRow){
    207                     // set a pseudo-ID
    208                     $newRow['ID'] = "new";
    209
    210                     // unset any extradata
    211                     foreach($newRow as $k => $v){
    212                         if($this->extraData && array_key_exists($k, $this->extraData)){
    213                             unset($newRow[$k]);
    214                         }
    215                     }
    216
    217                     // generate a temporary DataObject container (not saved in the database)
    218                     $sourceClass = $this->sourceClass;
    219                     $sourceItems->push(new $sourceClass($newRow));
    220
    221                     $i++;
    222                 }
    223             }
    224         }
    225
    226         // generate a new TableField_Item instance from each collected item
    227         if($sourceItems) foreach($sourceItems as $sourceItem) {
    228             $items->push($this->generateTableFieldItem($sourceItem));
    229         }
    230
    231         // add an empty TableField_Item for a single "add row"
    232         if($this->showAddRow && $this->Can('add')) {
    233             $items->push(new TableField_Item(null, $this, null, $this->fieldTypes, true));
    234         }
    235
    236         return $items;
    237     }
    238
    239     /**
    240      * Generates a new {@link TableField} instance
    241      * by loading a fieldset for this row into a temporary form.
    242      *
    243      * @param DataObject $dataObj
    244      * @return TableField_Item
    245      */
    246     protected function generateTableFieldItem($dataObj) {
    247         // Load the data in to a temporary form (for correct field types)
    248         $form = new Form(
    249             $this,
    250             null,
    251             $this->FieldSetForRow(),
    252             new FieldSet()
    253         );
    254         $form->loadDataFrom($dataObj);
    255
    256         // Add the item to our new DataObjectSet, with a wrapper class.
    257         return new TableField_Item($dataObj, $this, $form, $this->fieldTypes);
    258     }
    259
    260     /**
    261      * @return array
    262      */
    263     function FieldList() {
    264         return $this->fieldList;
    265     }
    266
    267     /**
    268      * Saves the Dataobjects contained in the field
    269      */
    270     function saveInto(DataObject $record) {
    271         // CMS sometimes tries to set the value to one.
    272         if(is_array($this->value)){
    273             $newFields = array();
    274
    275             // Sort into proper array
    276             $value = ArrayLib::invert($this->value);
    277             $dataObjects = $this->sortData($value, $record->ID);
    278
    279             // New fields are nested in their own sub-array, and need to be sorted separately
    280             if(isset($dataObjects['new']) && $dataObjects['new']) {
    281                 $newFields = $this->sortData($dataObjects['new'], $record->ID);
    282             }
    283
    284             // Update existing fields
    285             // @todo Should this be in an else{} statement?
    286             $savedObjIds = $this->saveData($dataObjects, $this->editExisting);
    287
    288             // Save newly added record
    289             if($savedObjIds || $newFields) {
    290                 $savedObjIds = $this->saveData($newFields,false);
    291             }
    292
    293             // Optionally save the newly created records into a relationship
    294             // on $record. This assumes the name of this formfield instance
    295             // is set to a relationship name on $record.
    296             if($this->relationAutoSetting) {
    297                 $relationName = $this->Name();
    298                 if($record->has_many($relationName) || $record->many_many($relationName)) {
    299                     if($savedObjIds) foreach($savedObjIds as $id => $status) {
    300                         $record->$relationName()->add($id);
    301                     }
    302                 }
    303             }
    304
    305             // Update the internal source items cache
    306             $this->value = null;
    307             $items = $this->sourceItems();
    308
    309             FormResponse::update_dom_id($this->id(), $this->FieldHolder());
    310         }
    311     }
    312
    313     /**
    314      * Get all {@link FormField} instances necessary for a single row,
    315      * respecting the casting set in {@link $fieldTypes}.
    316      * Doesn't populate with any data. Optionally performs a readonly
    317      * transformation if {@link $IsReadonly} is set, or the current user
    318      * doesn't have edit permissions.
    319      *
    320      * @return FieldSet
    321      */
    322     function FieldSetForRow() {
    323         $fieldset = new FieldSet();
    324         if($this->fieldTypes){
    325             foreach($this->fieldTypes as $key => $fieldType) {
    326                 if(isset($fieldType->class) && is_subclass_of($fieldType, 'FormField')) {
    327                     // using clone, otherwise we would just add stuff to the same field-instance
    328                     $field = clone $fieldType;
    329                 } elseif(strpos($fieldType, '(') === false) {
    330                     $field = new $fieldType($key);
    331                 } else {
    332                     $fieldName = $key;
    333                     $fieldTitle = "";
    334                     $field = eval("return new $fieldType;");
    335                 }
    336                 if($this->IsReadOnly || !$this->Can('edit')) {
    337                     $field = $field->performReadonlyTransformation();
    338                 }
    339                 $fieldset->push($field);
    340             }
    341         }else{
    342             USER_ERROR("TableField::FieldSetForRow() - Fieldtypes were not specified",E_USER_WARNING);
    343         }
    344
    345         return $fieldset;
    346     }
    347
    348     function performReadonlyTransformation() {
    349         $clone = clone $this;
    350         $clone->permissions = array('show');
    351         $clone->setReadonly(true);
    352         return $clone;
    353     }
    354
    355     function performDisabledTransformation() {
    356         $clone = clone $this;
    357         $clone->setPermissions(array('show'));
    358         $clone->setDisabled(true);
    359         return $clone;
    360     }
    361
    362     /**
    363      * Needed for Form->callfieldmethod.
    364      */
    365     public function getField($fieldName, $combinedFieldName = null) {
    366         $fieldSet = $this->FieldSetForRow();
    367         $field = $fieldSet->dataFieldByName($fieldName);
    368         if(!$field) {
    369             return false;
    370         }
    371
    372         if($combinedFieldName) {
    373             $field->Name = $combinedFieldName;
    374         }
    375
    376         return $field;
    377     }
    378
    379     /**
    380      * Called on save, it creates the appropriate objects and writes them
    381      * to the database.
    382      *
    383      * @param DataObjectSet $dataObjects
    384      * @param boolean $existingValues If set to TRUE, it tries to find existing objects
    385      *  based on the database IDs passed as array keys in $dataObjects parameter.
    386      *  If set to FALSE, it will always create new object (default: TRUE)
    387      * @return array Array of saved object IDs in the key, and the status ("Updated") in the value
    388      */
    389     function saveData($dataObjects, $existingValues = true) {
    390         if(!$dataObjects) return false;
    391
    392         $savedObjIds = array();
    393         $fieldset = $this->FieldSetForRow();
    394
    395         // add hiddenfields
    396         if($this->extraData) {
    397             foreach($this->extraData as $fieldName => $fieldValue) {
    398                 $fieldset->push(new HiddenField($fieldName));
    399             }
    400         }
    401
    402         $form = new Form($this, null, $fieldset, new FieldSet());
    403
    404         foreach ($dataObjects as $objectid => $fieldValues) {
    405             // 'new' counts as an empty column, don't save it
    406             if($objectid === "new") continue;
    407
    408             // extra data was creating fields, but
    409             if($this->extraData) {
    410                 $fieldValues = array_merge( $this->extraData, $fieldValues );
    411             }
    412
    413             // either look for an existing object, or create a new one
    414             if($existingValues) {
    415                 $obj = DataObject::get_by_id($this->sourceClass, $objectid);
    416             } else {
    417                 $sourceClass = $this->sourceClass;
    418                 $obj = new $sourceClass();
    419             }
    420
    421             // Legacy: Use the filter as a predefined relationship-ID
    422             if($this->filterField && $this->filterValue) {
    423                 $filterField = $this->filterField;
    424                 $obj->$filterField = $this->filterValue;
    425             }
    426
    427             // Determine if there is changed data for saving
    428             $dataFields = array();
    429             foreach($fieldValues as $type => $value) {
    430                 // if the field is an actual datafield (not a preset hiddenfield)
    431                 if(is_array($this->extraData)) {
    432                     if(!in_array($type, array_keys($this->extraData))) {
    433                         $dataFields[$type] = $value;
    434                     }
    435                 // all fields are real
    436                 } else {
    437                     $dataFields[$type] = $value;
    438                 }
    439             }
    440             $dataValues = ArrayLib::array_values_recursive($dataFields);
    441             // determine if any of the fields have a value (loose checking with empty())
    442             $hasData = false;
    443             foreach($dataValues as $value) {
    444                 if(!empty($value)) $hasData = true;
    445             }
    446
    447             if($hasData) {
    448                 $form->loadDataFrom($fieldValues, true);
    449                 $form->saveInto($obj);
    450
    451                 $objectid = $obj->write();
    452
    453                 $savedObjIds[$objectid] = "Updated";
    454             }
    455
    456         }
    457
    458         return $savedObjIds;
    459    }
    460
    461     /**
    462      * Organises the data in the appropriate manner for saving
    463      *
    464      * @param array $data
    465      * @param int $recordID
    466      * @return array Collection of maps suitable to construct DataObjects
    467      */
    468     function sortData($data, $recordID = null) {
    469         if(!$data) return false;
    470
    471         $sortedData = array();
    472
    473         foreach($data as $field => $rowData) {
    474             $i = 0;
    475             if(!is_array($rowData)) continue;
    476
    477             foreach($rowData as $id => $value) {
    478                 if($value == '$recordID') $value = $recordID;
    479
    480                 if($value) $sortedData[$id][$field] = $value;
    481
    482                 $i++;
    483             }
    484
    485             // TODO ADD stuff for removing rows with incomplete data
    486         }
    487
    488         return $sortedData;
    489     }
    490
    491     /**
    492      * @param $extraData array
    493      */
    494     function setExtraData($extraData) {
    495         $this->extraData = $extraData;
    496     }
    497
    498     /**
    499      * @return array
    500      */
    501     function getExtraData() {
    502         return $this->extraData;
    503     }
    504
    505     /**
    506      * Sets the template to be rendered with
    507      */
    508     function FieldHolder() {
    509         Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/prototype/prototype.js');
    510         Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/behaviour/behaviour.js');
    511         Requirements::javascript(SAPPHIRE_DIR . '/javascript/prototype_improvements.js');
    512         Requirements::javascript(THIRDPARTY_DIR . '/scriptaculous/effects.js');
    513         Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
    514         Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableListField.js');
    515         Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableField.js');
    516         Requirements::css(SAPPHIRE_DIR . '/css/TableListField.css');
    517
    518         return $this->renderWith($this->template);
    519     }
    520
    521     /**
    522      * @return Int
    523      */
    524     function sourceID() {
    525         return $this->filterField;
    526     }
    527
    528     function setTransformationConditions($conditions) {
    529         $this->transformationConditions = $conditions;
    530     }
    531
    532     function jsValidation() {
    533         $js = "";
    534
    535         $items = $this->Items();
    536         if($items) foreach($items as $item) {
    537             foreach($item->Fields() as $field) {
    538                 //if the field type has some special specific specification for validation of itself
    539                 $js .= $field->jsValidation($this->form->class."_".$this->form->Name());
    540             }
    541         }
    542
    543         // TODO Implement custom requiredFields
    544         $items = $this->sourceItems();
    545         if($items && $this->requiredFields && $items->count()) {
    546             foreach ($this->requiredFields as $field) {
    547                 foreach($items as $item){
    548                     $cellName = $this->Name().'['.$item->ID.']['.$field.']';
    549                     $js .= "\n";
    550                     if($fields->dataFieldByName($cellName)) {
    551                         $js .= <<<JS
    552 if(typeof fromAnOnBlur != 'undefined'){
    553     if(fromAnOnBlur.name == '$cellName')
    554         require(fromAnOnBlur);
    555 }else{
    556     require('$cellName');
    557 }
    558 JS;
    559                     }
    560                 }
    561             }
    562         }
    563
    564         return $js;
    565     }
    566
    567     function php($data) {
    568         $valid = true;
    569
    570         if($data['methodName'] != 'delete') {
    571             $items = $this->Items();
    572             if($items) foreach($items as $item) {
    573                 foreach($item->Fields() as $field) {
    574                     $valid = $field->validate($this) && $valid;
    575                 }
    576             }
    577
    578             return $valid;
    579         } else {
    580             return $valid;
    581         }
    582     }
    583
    584     function validate($validator) {
    585         $errorMessage = '';
    586         $valid = true;
    587
    588         // @todo should only contain new elements
    589         $items = $this->Items();
    590         if($items) foreach($items as $item) {
    591             foreach($item->Fields() as $field) {
    592                 $valid = $field->validate($validator) && $valid;
    593             }
    594         }
    595
    596         //debug::show($this->form->Message());
    597         if($this->requiredFields&&$sourceItemsNew&&$sourceItemsNew->count()) {
    598             foreach ($this->requiredFields as $field) {
    599                 foreach($sourceItemsNew as $item){
    600                     $cellName = $this->Name().'['.$item->ID.']['.$field.']';
    601                     $cName =  $this->Name().'[new]['.$field.'][]';
    602
    603                     if($fieldObj = $fields->dataFieldByName($cellName)) {
    604                         if(!trim($fieldObj->Value())){
    605                             $title = $fieldObj->Title();
    606                             $errorMessage .= sprintf(
    607                                 _t('TableField.ISREQUIRED', "In %s '%s' is required."),
    608                                 $this->name,
    609                                 $title
    610                             );
    611                             $errorMessage .= "<br />";
    612                         }
    613                     }
    614                 }
    615             }
    616         }
    617
    618         if($errorMessage){
    619             $messageType .= "validation";
    620             $message .= "<br />".$errorMessage;
    621
    622             $validator->validationError($this->name, $message, $messageType);
    623         }
    624
    625         return $valid;
    626     }
    627
    628     function setRequiredFields($fields) {
    629         $this->requiredFields = $fields;
    630     }
    631
    632     /**
    633      * @param boolean $value
    634      */
    635     function setRelationAutoSetting($value) {
    636         $this->relationAutoSetting = $value;
    637     }
    638
    639     /**
    640      * @return boolean
    641      */
    642     function getRelationAutoSetting() {
    643         return $this->relationAutoSetting;
    644     }
    645 }
    646
    647 /**
    648  * Single record in a TableField.
    649  * @package forms
    650  * @subpackage fields-relational
    651  * @see TableField
    652  */
    653 class TableField_Item extends TableListField_Item {
    654
    655     /**
    656      * @var FieldSet $fields
    657      */
    658     protected $fields;
    659
    660     protected $data;
    661
    662     protected $fieldTypes;
    663
    664     protected $isAddRow;
    665
    666     protected $extraData;
    667
    668     /**
    669      * Each row contains a dataobject with any number of attributes
    670      * @param $ID int The ID of the record
    671      * @param $form Form A Form object containing all of the fields for this item.  The data should be loaded in
    672      * @param $fieldTypes array An array of name => fieldtype for use when creating a new field
    673      * @param $parent TableListField The parent table for quick reference of names, and id's for storing values.
    674      */
    675     function __construct($item = null, $parent, $form, $fieldTypes, $isAddRow = false) {
    676         $this->data = $form;
    677         $this->fieldTypes = $fieldTypes;
    678         $this->isAddRow = $isAddRow;
    679         $this->item = $item;
    680
    681         parent::__construct(($this->item) ? $this->item : new DataObject(), $parent);
    682
    683         $this->fields = $this->createFields();
    684     }
    685     /**
    686      * Represents each cell of the table with an attribute.
    687      *
    688      * @return FieldSet
    689      */
    690     function createFields() {
    691         // Existing record
    692         if($this->item && $this->data) {
    693             $form = $this->data;
    694             $this->fieldset = $form->Fields();
    695             $this->fieldset->removeByName('SecurityID');
    696             if($this->fieldset) {
    697                 $i=0;
    698                 foreach($this->fieldset as $field) {
    699                     $origFieldName = $field->Name();
    700
    701                     // set unique fieldname with id
    702                     $combinedFieldName = $this->parent->Name() . "[" . $this->ID() . "][" . $origFieldName . "]";
    703                     // ensure to set field to nested array notation
    704                     // if its an unsaved row, or the "add row" which is present by default
    705                     if($this->isAddRow || $this->ID() == 'new') $combinedFieldName .= '[]';
    706
    707                     // get value
    708                     if(strpos($origFieldName,'.') === false) {
    709                         $value = $field->dataValue();
    710                     } else {
    711                         // this supports the syntax fieldName = Relation.RelatedField
    712                         $fieldNameParts = explode('.', $origFieldName)  ;
    713                         $tmpItem = $this->item;
    714                         for($j=0;$j<sizeof($fieldNameParts);$j++) {
    715                             $relationMethod = $fieldNameParts[$j];
    716                             $idField = $relationMethod . 'ID';
    717                             if($j == sizeof($fieldNameParts)-1) {
    718                                 $value = $tmpItem->$relationMethod;
    719                             } else {
    720                                 $tmpItem = $tmpItem->$relationMethod();
    721                             }
    722                         }
    723                     }
    724
    725                     $field->Name = $combinedFieldName;
    726                     $field->setValue($field->dataValue());
    727                     $field->addExtraClass('col'.$i);
    728                     $field->setForm($this->data);
    729
    730                     // transformation
    731                     if(isset($this->parent->transformationConditions[$origFieldName])) {
    732                         $transformation = $this->parent->transformationConditions[$origFieldName]['transformation'];
    733                         $rule = str_replace("\$","\$this->item->", $this->parent->transformationConditions[$origFieldName]['rule']);
    734                         $ruleApplies = null;
    735                         eval('$ruleApplies = ('.$rule.');');
    736                         if($ruleApplies) {
    737                             $field = $field->$transformation();
    738                         }
    739                     }
    740
    741                     // formatting
    742                     $item = $this->item;
    743                     $value = $field->Value();
    744                     if(array_key_exists($origFieldName, $this->parent->fieldFormatting)) {
    745                         $format = str_replace('$value', "__VAL__", $this->parent->fieldFormatting[$origFieldName]);
    746                         $format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
    747                         $format = str_replace('__VAL__', '$value', $format);
    748                         eval('$value = "' . $format . '";');
    749                         $field->dontEscape = true;
    750                         $field->setValue($value);
    751                     }
    752
    753                     $this->fields[] = $field;
    754                     $i++;
    755                 }
    756             }
    757         // New record
    758         } else {
    759             $list = $this->parent->FieldList();
    760             $i=0;
    761             foreach($list as $fieldName => $fieldTitle) {
    762                 if(strpos($fieldName, ".")) {
    763                     $shortFieldName = substr($fieldName, strpos($fieldName, ".")+1, strlen($fieldName));
    764                 } else {
    765                     $shortFieldName = $fieldName;
    766                 }
    767                 $combinedFieldName = $this->parent->Name() . "[new][" . $shortFieldName . "][]";
    768                 $fieldType = $this->fieldTypes[$fieldName];
    769                 if(isset($fieldType->class) && is_subclass_of($fieldType, 'FormField')) {
    770                     $field = clone $fieldType; // we can't use the same instance all over, as we change names
    771                     $field->Name = $combinedFieldName;
    772                 } elseif(strpos($fieldType, '(') === false) {
    773                     //echo ("<li>Type: ".$fieldType." fieldName: ". $filedName. " Title: ".$fieldTitle."</li>");
    774                     $field = new $fieldType($combinedFieldName,$fieldTitle);
    775                 } else {
    776                     $field = eval("return new " . $fieldType . ";");
    777                 }
    778                 $field->addExtraClass('col'.$i);
    779                 $this->fields[] = $field;
    780                 $i++;
    781             }
    782         }
    783         return new FieldSet($this->fields);
    784     }
    785
    786     function Fields() {
    787         return $this->fields;
    788     }
    789
    790     function ExtraData() {
    791         $content = "";
    792         $id = ($this->item->ID) ? $this->item->ID : "new";
    793         if($this->parent->getExtraData()) {
    794             foreach($this->parent->getExtraData() as $fieldName=>$fieldValue) {
    795                 $name = $this->parent->Name() . "[" . $id . "][" . $fieldName . "]";
    796                 if($this->isAddRow) $name .= '[]';
    797                 $field = new HiddenField($name, null, $fieldValue);
    798                 $content .= $field->FieldHolder() . "\n";
    799             }
    800         }
    801
    802         return $content;
    803     }
    804
    805     /**
    806      * Get the flag isAddRow of this item,
    807      * to indicate if the item is that blank last row in the table which is not in the database
    808      *
    809      * @return boolean
    810      */
    811     function IsAddRow(){
    812         return $this->isAddRow;
    813     }
    814
    815 }
    816
    817 ?>

[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. -