├── _config └── config.yml ├── src ├── Extensions │ ├── ElementExtension.php │ ├── ElementalSetSiteTreeExtension.php │ ├── BaseElementDataExtension.php │ └── ElementalAreaExtension.php ├── Admin │ └── ElementalSetAdmin.php └── Model │ └── ElementalSet.php └── composer.json /_config/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: elemental-set-config 3 | --- 4 | Dynamic\ElementalSets\Model\ElementalSet: 5 | extensions: 6 | - DNADesign\Elemental\Extensions\ElementalPageExtension 7 | DNADesign\Elemental\Models\ElementalArea: 8 | extensions: 9 | - Dynamic\ElementalSets\Extensions\ElementalAreaExtension 10 | SilverStripe\CMS\Model\SiteTree: 11 | extensions: 12 | - Dynamic\ElementalSets\Extensions\ElementalSetSiteTreeExtension 13 | DNADesign\Elemental\Models\BaseElement: 14 | extensions: 15 | - Dynamic\ElementalSets\Extensions\BaseElementDataExtension -------------------------------------------------------------------------------- /src/Extensions/ElementExtension.php: -------------------------------------------------------------------------------- 1 | 'Varchar', 19 | ]; 20 | 21 | /** 22 | * @var array 23 | */ 24 | private static $belongs_many_many = [ 25 | 'ElementSets' => ElementalSet::class, 26 | ]; 27 | } 28 | -------------------------------------------------------------------------------- /src/Admin/ElementalSetAdmin.php: -------------------------------------------------------------------------------- 1 | 'Boolean', 23 | ]; 24 | 25 | private static $many_many = [ 26 | 'DisabledSets' => ElementalSet::class, 27 | ]; 28 | 29 | private static $defaults = [ 30 | 'InheritElementalSets' => 1, 31 | ]; 32 | 33 | /** 34 | * @param FieldList $fields 35 | */ 36 | public function updateCMSFields(FieldList $fields) 37 | { 38 | $fields->addFieldsToTab( 39 | 'Root.ElementSets', 40 | [ 41 | CheckboxField::create('InheritElementalSets') 42 | ->setTitle('Inherit Elements from Elemental Sets'), 43 | TreeMultiselectField::create('DisabledSets', 'Disabled Sets', ElementalSet::class), 44 | ] 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Extensions/BaseElementDataExtension.php: -------------------------------------------------------------------------------- 1 | getOwner()->Parent()->getOwnerPage(); 19 | if ($ownerPage instanceof ElementalSet) { 20 | $classes = array_keys($templates); 21 | if (isset($classes[0])) { 22 | $class = $classes[0]; 23 | if ($this->getOwner()->ElementalArea) { 24 | array_unshift($templates[$class], "{$class}_{$this->getOwner()->ElementalArea}"); 25 | } 26 | } 27 | } 28 | } 29 | 30 | /** 31 | * @param string $link 32 | */ 33 | public function updateCMSEditLink(&$link) 34 | { 35 | $owner = $this->getOwner(); 36 | 37 | $relationName = $owner->getAreaRelationName(); 38 | $page = $owner->getPage(); 39 | 40 | if (!$page) { 41 | return; 42 | } 43 | 44 | if ($page instanceof ElementalSet) { 45 | // nested bock - we need to get edit link of parent block 46 | $link = Controller::join_links( 47 | $page->CMSEditLink(), 48 | 'ItemEditForm/field/' . $page->getOwnedAreaRelationName() . '/item/', 49 | $owner->ID 50 | ); 51 | 52 | // remove edit link from parent CMS link 53 | $link = preg_replace('/\/item\/([\d]+)\/edit/', '/item/$1', $link); 54 | } else { 55 | // block is directly under a non-block object - we have reached the top of nesting chain 56 | $link = Controller::join_links( 57 | singleton(CMSPageEditController::class)->Link('EditForm'), 58 | $page->ID, 59 | 'field/' . $relationName . '/item/', 60 | $owner->ID 61 | ); 62 | } 63 | 64 | $link = Controller::join_links( 65 | $link, 66 | 'edit' 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Extensions/ElementalAreaExtension.php: -------------------------------------------------------------------------------- 1 | owner->OwnerClassName == ElementalSet::class) return ArrayList::create(); 32 | 33 | // Don't try and process unsaved lists 34 | if ($this->owner->Elements() instanceof UnsavedRelationList) { 35 | return ArrayList::create(); 36 | } 37 | 38 | $controllers = ArrayList::create(); 39 | $elements = ArrayList::create(); 40 | 41 | $nativeItems = $this->owner->Elements()->filterByCallback(function (BaseElement $item) { 42 | return $item->canView(); 43 | }); 44 | 45 | $elements->merge($nativeItems); 46 | 47 | $currentPage = Controller::curr()->data(); 48 | $blackList = (array)$currentPage->config()->get('black_list_areas'); 49 | $areaCheck = $currentPage->getElementalRelations(); 50 | $activeAreas = []; 51 | 52 | if (count($areaCheck)) { 53 | foreach ($areaCheck as $area) { 54 | if ($currentPage->$area() && !in_array($area, $blackList)) { 55 | $activeAreas[$area] = $currentPage->$area()->ID; 56 | } 57 | } 58 | } 59 | 60 | if ($currentPage->InheritElementalSets) { 61 | $elementsFromSets = $this->getElementsFromAppliedElementalSets($currentPage); 62 | 63 | if ($elementsFromSets->count()) { 64 | foreach ($elementsFromSets as $setElement) { 65 | if (array_key_exists($setElement->ElementalArea, $activeAreas) && $activeAreas[$setElement->ElementalArea] == $this->owner->ID) { 66 | if ($setElement->AboveOrBelow == 'Above') { 67 | $elements->unshift($setElement); 68 | } else { 69 | $elements->push($setElement); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | if ($elements->exists()) { 77 | foreach ($elements as $element) { 78 | $controller = $element->getController(); 79 | $controllers->push($controller); 80 | } 81 | } 82 | 83 | return $controllers; 84 | } 85 | 86 | protected function getElementsFromAppliedElementalSets($page) 87 | { 88 | $elements = ArrayList::create(); 89 | 90 | $sets = $this->getAppliedSets($page); 91 | 92 | if (!$sets->count()) { 93 | return $elements; 94 | } 95 | 96 | foreach ($sets as $set) { 97 | $setElements = $set->ElementalArea()->Elements()->sort('Sort DESC'); 98 | 99 | $this->owner->extend('updateSetElements', $setElements); 100 | 101 | $elements->merge($setElements); 102 | } 103 | 104 | $elements->removeDuplicates(); 105 | 106 | return $elements; 107 | } 108 | 109 | /** 110 | * @param \SilverStripe\CMS\Model\SiteTree|\Dynamic\ElementalSets\Extensions\ElementalSetSiteTreeExtension $page 111 | * @return \SilverStripe\ORM\ArrayList 112 | */ 113 | protected function getAppliedSets($page) 114 | { 115 | $list = ArrayList::create(); 116 | 117 | if (!$page->InheritElementalSets) { 118 | return $list; 119 | } 120 | 121 | $sets = ElementalSet::get()->where("(PageTypesValue IS NULL) OR (PageTypesValue='[]') OR (PageTypesValue LIKE '%:{$page->ClassName}%')"); 122 | $ancestors = $page->getAncestors()->column('ID'); 123 | 124 | foreach ($sets as $set) { 125 | if ($this->isDisabledSet($set, $page)) { 126 | continue; 127 | } 128 | 129 | $restrictedToParentIDs = $set->PageParents()->column('ID'); 130 | if (count($restrictedToParentIDs)) { 131 | if ($set->IncludePageParent && in_array($page->ID, $restrictedToParentIDs)) { 132 | $list->add($set); 133 | } else { 134 | if (count($ancestors)) { 135 | foreach ($ancestors as $ancestor) { 136 | if (in_array($ancestor, $restrictedToParentIDs)) { 137 | $list->add($set); 138 | continue; 139 | } 140 | } 141 | } 142 | } 143 | } 144 | } 145 | 146 | return $list; 147 | } 148 | 149 | /** 150 | * @param $set 151 | * @param $page 152 | * @return bool 153 | */ 154 | protected function isDisabledSet($set, $page) 155 | { 156 | foreach ($page->DisabledSets() as $disabledSet) { 157 | if ($set->ID === $disabledSet->ID) { 158 | return true; 159 | } 160 | } 161 | return false; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/Model/ElementalSet.php: -------------------------------------------------------------------------------- 1 | 'Varchar(255)', 70 | 'PageTypes' => MultiValueField::class, 71 | 'IncludePageParent' => 'Boolean', 72 | ]; 73 | 74 | /** 75 | * @var array 76 | */ 77 | private static $many_many = [ 78 | 'PageParents' => SiteTree::class, 79 | ]; 80 | 81 | /** 82 | * @var array 83 | */ 84 | private static $above_or_below_options = [ 85 | 'Above' => 'Above Page Elements', 86 | 'Below' => 'Below Page Elements', 87 | ]; 88 | 89 | /** 90 | * @var array 91 | */ 92 | private static $summary_fields = [ 93 | 'Title' => 'Title', 94 | 'ElementsCount' => 'Total Elements', 95 | ]; 96 | 97 | /** 98 | * @return \SilverStripe\Forms\FieldList 99 | */ 100 | public function getCMSFields() 101 | { 102 | $this->beforeUpdateCMSFields(function (FieldList $fields) { 103 | $fields->removeFieldFromTab('Root', 'PageParents'); 104 | 105 | $fields->addFieldsToTab( 106 | 'Root.Main', 107 | [ 108 | HeaderField::create('SettingsHeading', _t("{$this->ClassName}.Settings", 'Settings')), 109 | TextField::create('Title') 110 | ->setTitle(_t("{$this->ClassName}.Title", 'Title')), 111 | MultiValueCheckboxField::create('PageTypes') 112 | ->setTitle(_t("{$this->ClassName}.OnlyApplyToThesePageTypes", 'Only apply to these page types')) 113 | ->setDescription(_t("{$this->ClassName}.OnlyApplyToThesePageTypesDescription", 114 | 'Selected Page Types will inherit this Element Set automatically. Leave all unchecked to apply to all page types.')) 115 | ->setSource($this->pageTypeOptions()), 116 | TreeMultiselectField::create('PageParents') 117 | ->setTitle(_t("{$this->ClassName}.OnlyApplyToChildrenOfThesePages", 118 | 'Only apply to children of these Pages:')) 119 | ->setSourceObject(SiteTree::class), 120 | CheckboxField::create('IncludePageParent') 121 | ->setTitle(_t("{$this->ClassName}.ApplyBlockSetToSelectedPageParentsAsWellAsChildren", 122 | 'Apply elemental set to selected page parents as well as children')), 123 | ] 124 | ); 125 | 126 | if (!$this->exists()) { 127 | $fields->addFieldToTab('Root.Main', LiteralField::create('NotSaved', 128 | "

" . _t("{$this->ClassName}.YouCanAddElementsToThisSetOnceYouHaveSavedIt", 129 | 'You can add Elements to this set once you have saved it for the first time') . '

')); 130 | } 131 | 132 | $fields->removeByName('Elements'); 133 | }); 134 | 135 | $fields = parent::getCMSFields(); 136 | 137 | $elements = $fields->dataFieldByName('ElementalArea'); 138 | 139 | if ($elements instanceof GridField) { 140 | $config = $elements->getConfig(); 141 | $addElement = $config->getComponentByType(GridFieldAddNewMultiClass::class); 142 | $addElement = $addElement->setFragment('toolbar-header-right'); 143 | 144 | $config->removeComponentsByType([ 145 | GridFieldAddExistingAutocompleter::class, 146 | GridFieldAddNewMultiClass::class 147 | ]) 148 | ->addComponent(new GridFieldAddExistingSearchButton('toolbar-header-left')) 149 | ->addComponent($addElement); 150 | } 151 | 152 | return $fields; 153 | } 154 | 155 | /** 156 | * @return mixed 157 | */ 158 | public function getElementsCount() 159 | { 160 | return $this->ElementalArea()->Elements()->count(); 161 | } 162 | 163 | /** 164 | * @return array 165 | */ 166 | protected function pageTypeOptions() 167 | { 168 | $pageTypes = []; 169 | $classes = ArrayLib::valuekey(SiteTree::page_type_classes()); 170 | 171 | unset($classes[VirtualPage::class]); 172 | unset($classes[ErrorPage::class]); 173 | unset($classes[RedirectorPage::class]); 174 | 175 | foreach ($classes as $class) { 176 | $pageTypes[$class] = $class::singleton()->i18n_singular_name(); 177 | } 178 | 179 | asort($pageTypes); 180 | 181 | return $pageTypes; 182 | } 183 | 184 | /** 185 | * @return \SilverStripe\ORM\DataList 186 | */ 187 | public function Pages() 188 | { 189 | $pages = SiteTree::get(); 190 | $types = $this->PageTypes->getValue(); 191 | if (count($types)) { 192 | $pages = $pages->filter('ClassName', $types); 193 | } 194 | 195 | $parents = $this->PageParents()->column(); 196 | if (count($parents)) { 197 | $pages = $pages->filter('ParentID', $parents); 198 | } 199 | 200 | return $pages; 201 | } 202 | 203 | /** 204 | * @return string 205 | */ 206 | public function Link() 207 | { 208 | return Controller::curr()->Link(); 209 | } 210 | 211 | /** 212 | * @return string 213 | */ 214 | public function CMSEditLink(): string 215 | { 216 | return Controller::join_links( 217 | 'admin', 218 | 'elemental-sets-admin', 219 | 'Dynamic-ElementalSets-Model-ElementalSet', 220 | 'EditForm', 221 | 'field', 222 | 'Dynamic-ElementalSets-Model-ElementalSet', 223 | 'item', 224 | $this->ID, 225 | 'edit' 226 | ); 227 | } 228 | 229 | /** 230 | * Retrieve a elemental area relation name which this element owns 231 | * 232 | * @return string 233 | */ 234 | public function getOwnedAreaRelationName(): string 235 | { 236 | $has_one = $this->getOwner()->config()->get('has_one'); 237 | 238 | foreach ($has_one as $relationName => $relationClass) { 239 | if ($relationClass === ElementalArea::class && $relationName !== 'Parent') { 240 | return $relationName; 241 | } 242 | } 243 | 244 | return 'Elements'; 245 | } 246 | } 247 | --------------------------------------------------------------------------------