silverstripe /
silverstripe-elemental
| 1 | <?php |
||
| 2 | |||
| 3 | namespace DNADesign\Elemental\Forms; |
||
| 4 | |||
| 5 | use DNADesign\Elemental\Controllers\ElementalAreaController; |
||
| 6 | use DNADesign\Elemental\Models\BaseElement; |
||
| 7 | use DNADesign\Elemental\Models\ElementalArea; |
||
| 8 | use SilverStripe\Control\Controller; |
||
| 9 | use SilverStripe\Core\Injector\Injector; |
||
| 10 | use SilverStripe\Forms\CompositeField; |
||
| 11 | use SilverStripe\Forms\FieldGroup; |
||
| 12 | use SilverStripe\Forms\FieldList; |
||
| 13 | use SilverStripe\Forms\FormField; |
||
| 14 | use SilverStripe\Forms\GridField\GridField; |
||
| 15 | use SilverStripe\Forms\TabSet; |
||
| 16 | use SilverStripe\ORM\DataObjectInterface; |
||
| 17 | use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass; |
||
| 18 | |||
| 19 | class ElementalAreaField extends GridField |
||
| 20 | { |
||
| 21 | /** |
||
| 22 | * @var ElementalArea $area |
||
| 23 | */ |
||
| 24 | protected $area; |
||
| 25 | |||
| 26 | /** |
||
| 27 | * @var array $type |
||
| 28 | */ |
||
| 29 | protected $types = []; |
||
| 30 | |||
| 31 | /** |
||
| 32 | * @var null |
||
| 33 | */ |
||
| 34 | protected $inputType = null; |
||
| 35 | |||
| 36 | protected $modelClassName = BaseElement::class; |
||
| 37 | |||
| 38 | /** |
||
| 39 | * @param string $name |
||
| 40 | * @param ElementalArea $area |
||
| 41 | * @param string[] $blockTypes |
||
| 42 | */ |
||
| 43 | public function __construct($name, ElementalArea $area, array $blockTypes) |
||
| 44 | { |
||
| 45 | $this->setTypes($blockTypes); |
||
| 46 | |||
| 47 | $config = new ElementalAreaConfig(); |
||
| 48 | |||
| 49 | if (!empty($blockTypes)) { |
||
| 50 | /** @var GridFieldAddNewMultiClass $adder */ |
||
| 51 | $adder = Injector::inst()->create(GridFieldAddNewMultiClass::class); |
||
| 52 | $adder->setClasses($blockTypes); |
||
| 53 | $config->addComponent($adder); |
||
| 54 | } |
||
| 55 | |||
| 56 | // By default, no need for a title on the editor. If there is more than one area then use `setTitle` to describe |
||
| 57 | parent::__construct($name, '', $area->Elements(), $config); |
||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 58 | $this->area = $area; |
||
| 59 | |||
| 60 | $this->addExtraClass('element-editor__container no-change-track'); |
||
| 61 | } |
||
| 62 | |||
| 63 | /** |
||
| 64 | * @param array $types |
||
| 65 | * |
||
| 66 | * @return $this |
||
| 67 | */ |
||
| 68 | public function setTypes($types) |
||
| 69 | { |
||
| 70 | $this->types = $types; |
||
| 71 | |||
| 72 | return $this; |
||
| 73 | } |
||
| 74 | |||
| 75 | /** |
||
| 76 | * @return array |
||
| 77 | */ |
||
| 78 | public function getTypes() |
||
| 79 | { |
||
| 80 | $types = $this->types; |
||
| 81 | |||
| 82 | $this->extend('updateGetTypes', $types); |
||
| 83 | |||
| 84 | return $types; |
||
| 85 | } |
||
| 86 | |||
| 87 | /** |
||
| 88 | * @return ElementalArea |
||
| 89 | */ |
||
| 90 | public function getArea() |
||
| 91 | { |
||
| 92 | return $this->area; |
||
| 93 | } |
||
| 94 | |||
| 95 | /** |
||
| 96 | * Overloaded to skip GridField implementation - this is copied from FormField. |
||
| 97 | * |
||
| 98 | * @param array $properties |
||
| 99 | * @return \SilverStripe\ORM\FieldType\DBHTMLText|string |
||
| 100 | */ |
||
| 101 | public function FieldHolder($properties = array()) |
||
| 102 | { |
||
| 103 | $context = $this; |
||
| 104 | |||
| 105 | if (count($properties ?? [])) { |
||
| 106 | $context = $this->customise($properties); |
||
| 107 | } |
||
| 108 | |||
| 109 | return $context->renderWith($this->getFieldHolderTemplates()); |
||
| 110 | } |
||
| 111 | |||
| 112 | public function getSchemaDataDefaults() |
||
| 113 | { |
||
| 114 | $schemaData = parent::getSchemaDataDefaults(); |
||
| 115 | |||
| 116 | $area = $this->getArea(); |
||
| 117 | $pageId = ($area && ($page = $area->getOwnerPage())) ? $page->ID : null; |
||
| 118 | $schemaData['page-id'] = $pageId; |
||
| 119 | $schemaData['elemental-area-id'] = $area ? (int) $area->ID : null; |
||
|
0 ignored issues
–
show
|
|||
| 120 | |||
| 121 | $allowedTypes = $this->getTypes(); |
||
| 122 | $schemaData['allowed-elements'] = array_keys($allowedTypes ?? []); |
||
| 123 | |||
| 124 | return $schemaData; |
||
| 125 | } |
||
| 126 | |||
| 127 | /** |
||
| 128 | * A getter method that seems redundant in that it is a function that returns a function, |
||
| 129 | * however the returned closure is used in an array map function to return a complete FieldList |
||
| 130 | * representing a read only view of the element passed in (to the closure). |
||
| 131 | * |
||
| 132 | * @return callable |
||
| 133 | */ |
||
| 134 | protected function getReadOnlyBlockReducer() |
||
| 135 | { |
||
| 136 | return function (BaseElement $element) { |
||
| 137 | $parentName = 'Element' . $element->ID; |
||
| 138 | $elementFields = $element->getCMSFields(); |
||
| 139 | |||
| 140 | // Obtain highest impact fields for a summary (e.g. Title & Content) |
||
| 141 | foreach ($elementFields as $field) { |
||
| 142 | if (is_object($field) && $field instanceof TabSet) { |
||
| 143 | // Assign the fields of the first Tab in the TabSet - most regularly 'Root.Main' |
||
| 144 | $elementFields = $field->FieldList()->first()->FieldList(); |
||
| 145 | break; |
||
| 146 | } |
||
| 147 | } |
||
| 148 | |||
| 149 | // Set values (before names don't match anymore) |
||
| 150 | $elementFields->setValues($element->getQueriedDatabaseFields()); |
||
| 151 | |||
| 152 | // Combine into an appropriately named group |
||
| 153 | $elementGroup = FieldGroup::create($elementFields); |
||
| 154 | $elementGroup->setForm($this->getForm()); |
||
| 155 | $elementGroup->setName($parentName); |
||
| 156 | $elementGroup->addExtraClass('elemental-area__element--historic'); |
||
| 157 | |||
| 158 | // Also set the important data for the rendering Component |
||
| 159 | $elementGroup->setSchemaData([ |
||
| 160 | 'data' => [ |
||
| 161 | 'ElementID' => $element->ID, |
||
| 162 | 'ElementType' => $element->getType(), |
||
| 163 | 'ElementIcon' => $element->config()->get('icon'), |
||
| 164 | 'ElementTitle' => $element->Title, |
||
| 165 | // @todo: Change this to block history permalink when that functionality becomes available. |
||
| 166 | 'ElementEditLink' => Controller::join_links( |
||
| 167 | // Always get the edit link for the block directly, not the in-line edit form if supported |
||
| 168 | $element->CMSEditLink(true), |
||
| 169 | // @todo make this auto-permalinking work somehow |
||
| 170 | '#Root_History' |
||
| 171 | ), |
||
| 172 | ], |
||
| 173 | ]); |
||
| 174 | |||
| 175 | return $elementGroup; |
||
| 176 | }; |
||
| 177 | } |
||
| 178 | |||
| 179 | /** |
||
| 180 | * Provides a readonly representation of the GridField (superclass) Uses a reducer |
||
| 181 | * {@see ElementalAreaField::getReadOnlyBlockReducer()} to fetch a read only representation of the listed class |
||
| 182 | * {@see GridField::getModelClass()} |
||
| 183 | * |
||
| 184 | * @return CompositeField |
||
| 185 | */ |
||
| 186 | public function performReadonlyTransformation() |
||
| 187 | { |
||
| 188 | /** @var CompositeField $readOnlyField */ |
||
| 189 | $readOnlyField = $this->castedCopy(CompositeField::class); |
||
| 190 | $blockReducer = $this->getReadOnlyBlockReducer(); |
||
| 191 | $readOnlyField->setChildren( |
||
| 192 | FieldList::create(array_map($blockReducer, $this->getArea()->Elements()->toArray() ?? [])) |
||
| 193 | ); |
||
| 194 | |||
| 195 | $readOnlyField = $readOnlyField->performReadonlyTransformation(); |
||
| 196 | |||
| 197 | // Ensure field names are unique between elements on parent form but only after transformations have been |
||
| 198 | // performed |
||
| 199 | /** @var FieldGroup $elementForm */ |
||
| 200 | foreach ($readOnlyField->getChildren() as $elementForm) { |
||
| 201 | $parentName = $elementForm->getName(); |
||
| 202 | $elementForm->getChildren()->recursiveWalk(function (FormField $field) use ($parentName) { |
||
| 203 | $field->setName($parentName . '_' . $field->getName()); |
||
| 204 | }); |
||
| 205 | } |
||
| 206 | |||
| 207 | return $readOnlyField |
||
| 208 | ->setReadOnly(true) |
||
| 209 | ->setName($this->getName()) |
||
| 210 | ->addExtraClass('elemental-area--read-only'); |
||
| 211 | } |
||
| 212 | |||
| 213 | public function setSubmittedValue($value, $data = null) |
||
| 214 | { |
||
| 215 | // Content comes through as a JSON encoded list through a hidden field. |
||
| 216 | return $this->setValue(json_decode($value ?? '', true)); |
||
| 217 | } |
||
| 218 | |||
| 219 | public function saveInto(DataObjectInterface $dataObject) |
||
| 220 | { |
||
| 221 | parent::saveInto($dataObject); |
||
| 222 | |||
| 223 | $elementData = $this->Value(); |
||
| 224 | $idPrefixLength = strlen(sprintf(ElementalAreaController::FORM_NAME_TEMPLATE ?? '', '')); |
||
| 225 | |||
| 226 | if (!$elementData) { |
||
| 227 | return; |
||
| 228 | } |
||
| 229 | |||
| 230 | foreach ($elementData as $form => $data) { |
||
| 231 | // Extract the ID |
||
| 232 | $elementId = (int) substr($form ?? '', $idPrefixLength ?? 0); |
||
| 233 | |||
| 234 | /** @var BaseElement $element */ |
||
| 235 | $element = $this->getArea()->Elements()->byID($elementId); |
||
| 236 | |||
| 237 | if (!$element) { |
||
| 238 | // Ignore invalid elements |
||
| 239 | continue; |
||
| 240 | } |
||
| 241 | |||
| 242 | $data = ElementalAreaController::removeNamespacesFromFields($data, $element->ID); |
||
| 243 | |||
| 244 | $element->updateFromFormData($data); |
||
| 245 | $element->write(); |
||
| 246 | } |
||
| 247 | } |
||
| 248 | } |
||
| 249 |