Example #1
0
 /**
  * Create a new form, with the given fields an action buttons.
  * 
  * @param Controller $controller The parent controller, necessary to create the appropriate form action tag.
  * @param String $name The method on the controller that will return this form object.
  * @param FieldList $fields All of the fields in the form - a {@link FieldList} of {@link FormField} objects.
  * @param FieldList $actions All of the action buttons in the form - a {@link FieldLis} of
  *                           {@link FormAction} objects
  * @param Validator $validator Override the default validator instance (Default: {@link RequiredFields})
  */
 public function __construct($controller, $name, FieldList $fields, FieldList $actions, $validator = null)
 {
     parent::__construct();
     if (!$fields instanceof FieldList) {
         throw new InvalidArgumentException('$fields must be a valid FieldList instance');
     }
     if (!$actions instanceof FieldList) {
         throw new InvalidArgumentException('$actions must be a valid FieldList instance');
     }
     if ($validator && !$validator instanceof Validator) {
         throw new InvalidArgumentException('$validator must be a Validator instance');
     }
     $fields->setForm($this);
     $actions->setForm($this);
     $this->fields = $fields;
     $this->actions = $actions;
     $this->controller = $controller;
     $this->name = $name;
     if (!$this->controller) {
         user_error("{$this->class} form created without a controller", E_USER_ERROR);
     }
     // Form validation
     $this->validator = $validator ? $validator : new RequiredFields();
     $this->validator->setForm($this);
     // Form error controls
     $this->setupFormErrors();
     // Check if CSRF protection is enabled, either on the parent controller or from the default setting. Note that
     // method_exists() is used as some controllers (e.g. GroupTest) do not always extend from Object.
     if (method_exists($controller, 'securityTokenEnabled') || method_exists($controller, 'hasMethod') && $controller->hasMethod('securityTokenEnabled')) {
         $securityEnabled = $controller->securityTokenEnabled();
     } else {
         $securityEnabled = SecurityToken::is_enabled();
     }
     $this->securityToken = $securityEnabled ? new SecurityToken() : new NullSecurityToken();
 }
 /**
  * @return FieldList|null
  */
 public function getRecordDataFields(DataObjectInterface $record)
 {
     $fieldsFunction = $this->getFieldsFunction();
     if (!$fieldsFunction) {
         $fieldsFunction = $this->getDefaultFieldsFunction($record);
     }
     $fields = null;
     if (is_callable($fieldsFunction)) {
         $fields = $fieldsFunction($record);
     } else {
         if (method_exists($record, $fieldsFunction) || method_exists($record, 'hasMethod') && $record->hasMethod($fieldsFunction)) {
             $fields = $record->{$fieldsFunction}();
         } else {
             if ($this->fieldsFunctionFallback) {
                 $fieldsFunction = $this->getDefaultFieldsFunction($record);
                 $fields = $record->{$fieldsFunction}();
             } else {
                 throw new Exception($record->class . '::' . $fieldsFunction . ' function does not exist.');
             }
         }
     }
     if (!$fields || !$fields instanceof FieldList) {
         throw new Exception('Function callback on ' . __CLASS__ . ' must return a FieldList.');
     }
     $record->extend('updateMultiEditFields', $fields);
     $fields = $fields->dataFields();
     if (!$fields) {
         $errorMessage = __CLASS__ . ' is missing fields for record #' . $record->ID . ' on class "' . $record->class . '".';
         if (is_callable($fieldsFunction)) {
             throw new Exception($errorMessage . '. This is due to the closure set with "setFieldsFunction" not returning a populated FieldList.');
         }
         throw new Exception($errorMessage . '. This is due ' . $record->class . '::' . $fieldsFunction . ' not returning a populated FieldList.');
     }
     //
     $recordExists = $record->exists();
     // Set value from record if it exists or if re-loading data after failed form validation
     $recordShouldSetValue = $recordExists || $record->MultiRecordField_NewID;
     // Setup sort field
     $sortFieldName = $this->getSortFieldName();
     if ($sortFieldName) {
         $sortField = isset($fields[$sortFieldName]) ? $fields[$sortFieldName] : null;
         if ($sortField && !$sortField instanceof HiddenField) {
             throw new Exception('Cannot utilize drag and drop sort functionality if the sort field is explicitly used on form. Suggestion: $fields->removeByName("' . $sortFieldName . '") in ' . $record->class . '::' . $fieldsFunction . '().');
         }
         if (!$sortField) {
             $sortValue = $recordShouldSetValue ? $record->{$sortFieldName} : self::SORT_INVALID;
             $sortField = HiddenField::create($sortFieldName);
             if ($sortField instanceof HiddenField) {
                 $sortField->setAttribute('value', $sortValue);
             } else {
                 $sortField->setValue($sortValue);
             }
             $sortField->setAttribute('data-ignore-delete-check', 1);
             // NOTE(Jake): Uses array_merge() to prepend the sort field in the $fields associative array.
             //             The sort field is prepended so jQuery.find('.js-multirecordfield-sort-field').first()
             //             finds the related sort field to this, rather than a sort field nested deeply in other
             //             MultiRecordField's.
             $fields = array_merge(array($sortFieldName => $sortField), $fields);
         }
         $sortField->addExtraClass('js-multirecordfield-sort-field');
     }
     // Set heading (ie. 'My Record (Draft)')
     $titleFieldName = $this->getTitleField();
     $status = '';
     if (!$titleFieldName) {
         $recordSectionTitle = $record->MultiRecordEditingTitle;
         if (!$recordSectionTitle) {
             $recordSectionTitle = $record->Title;
             $status = $recordExists ? $record->CMSPublishedState : 'New';
         }
     } else {
         $recordSectionTitle = $record->{$titleFieldName};
     }
     if (!$recordSectionTitle) {
         // NOTE(Jake): Ensures no title'd ToggleCompositeField's have a proper height.
         $recordSectionTitle = ' ';
     }
     $recordSectionTitle .= ' <span class="js-multirecordfield-title-status">';
     $recordSectionTitle .= $status ? '(' . $status . ')' : '';
     $recordSectionTitle .= '</span>';
     // Add heading field / Togglable composite field with heading
     $subRecordField = MultiRecordSubRecordField::create('', $recordSectionTitle, null);
     $subRecordField->setParent($this);
     $subRecordField->setRecord($record);
     if ($this->readonly) {
         $subRecordField = $subRecordField->performReadonlyTransformation();
     }
     // Modify sub-fields to work properly with this field
     $currentFieldListModifying = $subRecordField;
     foreach ($fields as $field) {
         $fieldName = $field->getName();
         if ($recordShouldSetValue) {
             if (isset($record->{$fieldName}) || $record->hasMethod($fieldName) || $record->hasMethod('hasField') && $record->hasField($fieldName)) {
                 $val = $record->__get($fieldName);
                 $field->setValue($val, $record);
             }
         }
         if ($field instanceof MultiRecordField) {
             $field->depth = $this->depth + 1;
             $action = $this->getActionURL($field, $record);
             $field->setAttribute('data-action', $action);
             // NOTE(Jake): Unclear at time of writing (17-06-2016) if nested MultiRecordField should
             //             inherit certain settings or not. Might add flag like 'setRecursiveOptions' later
             //             or something.
             $field->setFieldsFunction($this->getFieldsFunction(), $this->fieldsFunctionFallback);
             //$field->setTitleField($this->getTitleField());
         } else {
             $config = $this->getConfig();
             if ($config === null) {
                 $config = $this->config()->default_config;
             }
             // todo(Jake): Make it walk class hierarchy so that things that extend say 'HtmlEditorField'
             //             will also get the config. Make the '*HtmlEditorField' denote that it's only
             //             for that class, sub-classes.
             if (isset($config[$field->class])) {
                 $fieldConfig = $config[$field->class];
                 $functionCalls = isset($fieldConfig['functions']) ? $fieldConfig['functions'] : array();
                 if ($functionCalls) {
                     foreach ($functionCalls as $methodName => $arguments) {
                         $arguments = (array) $arguments;
                         call_user_func_array(array($field, $methodName), $arguments);
                     }
                 }
             }
             if ($field instanceof FileAttachmentField) {
                 // fix(Jake)
                 // todo(Jake): Fix deletion
                 // Support for Unclecheese's Dropzone module
                 // @see: https://github.com/unclecheese/silverstripe-dropzone/tree/1.2.3
                 $action = $this->getActionURL($field, $record);
                 $field = MultiRecordFileAttachmentField::cast($field);
                 $field->multiRecordAction = $action;
                 // Fix $field->Value()
                 if ($recordShouldSetValue && !$val && isset($record->{$fieldName . 'ID'})) {
                     // NOTE(Jake): This check was added for 'FileAttachmentField'.
                     //             Putting this outside of this 'instanceof' if-statement will break UploadField.
                     $val = $record->__get($fieldName . 'ID');
                     if ($val) {
                         $field->setValue($val, $record);
                     }
                 }
             } else {
                 if (class_exists('MultiRecord' . $field->class)) {
                     // Handle generic case (ie. UploadField)
                     // Where we just want to override value returned from $field->Link()
                     // so FormField actions work.
                     $class = 'MultiRecord' . $field->class;
                     $fieldCopy = $class::create($field->getName(), $field->Title());
                     foreach (get_object_vars($field) as $property => $value) {
                         $fieldCopy->{$property} = $value;
                     }
                     $fieldCopy->multiRecordAction = $this->getActionURL($field, $record);
                     $field = $fieldCopy;
                 }
             }
             // NOTE(Jake): Should probably add an ->extend() so other modules can monkey patch fields.
             //             Will wait to see if its needed.
         }
         // NOTE(Jake): Required to support UploadField. Generic so any field can utilize this functionality.
         if (method_exists($field, 'setRecord') || method_exists($field, 'hasMethod') && $field->hasMethod('setRecord')) {
             $field->setRecord($record);
         }
         $currentFieldListModifying->push($field);
     }
     $resultFieldList = new FieldList();
     $resultFieldList->push($subRecordField);
     $resultFieldList->setForm($this->form);
     return $resultFieldList;
 }