/** * 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; }