From cdece4450bf1920123bab0128dbf71176b3e60b4 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Tue, 30 Jan 2018 19:25:56 +0100 Subject: [PATCH 01/82] feat(ss4): Initial commit for ss4 compatibility --- _config/config.yml | 12 +----- code/GridFieldPageSectionsExtension.php | 33 +++++++++++---- code/PageElement.php | 54 +++++++++++++++++++------ code/PageSectionsExtension.php | 46 ++++++++++++++------- 4 files changed, 99 insertions(+), 46 deletions(-) diff --git a/_config/config.yml b/_config/config.yml index f63b73d..331a027 100644 --- a/_config/config.yml +++ b/_config/config.yml @@ -1,14 +1,6 @@ --- Name: pagesections -After: - - 'framework/*' - - 'cms/*' --- -# YAML configuration for SilverStripe -# See http://doc.silverstripe.org/framework/en/topics/configuration -# Caution: Indentation through two spaces, not tabs - -PageElement: +PageSections\PageElement: extensions: - - Versioned - - VersionedRelationsExtension + - SilverStripe\Versioned\Versioned diff --git a/code/GridFieldPageSectionsExtension.php b/code/GridFieldPageSectionsExtension.php index b84c41e..0c3c5c5 100644 --- a/code/GridFieldPageSectionsExtension.php +++ b/code/GridFieldPageSectionsExtension.php @@ -1,5 +1,22 @@ getAllowedPageElements(); $elems = array(); foreach ($classes as $class) { - $elems[$class] = $class::$singular_name; + $elems[$class] = $class::getSingularName(); } return array( @@ -301,7 +318,7 @@ private function getManipulatedItem(&$arr, $opens, $item, $level) { $item->_Open = true; } - public function handleReorder(GridField $gridField, SS_HTTPRequest $request) { + public function handleReorder(GridField $gridField, HTTPRequest $request) { $vars = $request->postVars(); $type = $vars["type"]; @@ -356,7 +373,7 @@ public function handleReorder(GridField $gridField, SS_HTTPRequest $request) { return $gridField->FieldHolder(); } - public function handleAdd(GridField $gridField, SS_HTTPRequest $request) { + public function handleAdd(GridField $gridField, HTTPRequest $request) { $id = intval($request->postVar("id")); $type = $request->postVar("type"); @@ -388,12 +405,12 @@ public function handleAdd(GridField $gridField, SS_HTTPRequest $request) { return $gridField->FieldHolder(); } - public function handleRemove(GridField $gridField, SS_HTTPRequest $request) { + public function handleRemove(GridField $gridField, HTTPRequest $request) { $id = intval($request->postVar("id")); - $obj = DataObject::get_by_id("PageElement", $id); + $obj = DataObject::get_by_id(PageElement::class, $id); $parentId = intval($request->postVar("parentId")); - $parentObj = DataObject::get_by_id("PageElement", $parentId); + $parentObj = DataObject::get_by_id(PageElement::class, $parentId); // Detach it from this parent (from the page if we're top level) if (!$parentObj) { @@ -405,9 +422,9 @@ public function handleRemove(GridField $gridField, SS_HTTPRequest $request) { return $gridField->FieldHolder(); } - public function handleDelete(GridField $gridField, SS_HTTPRequest $request) { + public function handleDelete(GridField $gridField, HTTPRequest $request) { $id = intval($request->postVar("id")); - $obj = DataObject::get_by_id("PageElement", $id); + $obj = DataObject::get_by_id(PageElement::class, $id); // Close the element in case it's open to avoid errors $state = $gridField->getState(true); diff --git a/code/PageElement.php b/code/PageElement.php index dba91d6..462b7cd 100644 --- a/code/PageElement.php +++ b/code/PageElement.php @@ -1,14 +1,38 @@ 'Varchar(255)', ); - private static $versioned_many_many = array( - 'Children' => 'PageElement', + private static $many_many = array( + 'Children' => PageElement::class, ); - private static $versioned_belongs_many_many = array( - 'Parents' => 'PageElement', + private static $belongs_many_many = array( + 'Parents' => PageElement::class, 'Pages' => 'Page', ); @@ -31,22 +55,26 @@ function canCreate($member = null) { return true; } ), ); - public static $summary_fields = array( + private static $owns = [ + "Children", + ]; + + private static $summary_fields = array( 'SingularName', 'ID', 'GridFieldPreview', ); - public static $searchable_fields = array( + private static $searchable_fields = array( 'ClassName', 'Title', 'ID' ); public static function getAllowedPageElements() { - $classes = array_values(ClassInfo::subclassesFor('PageElement')); + $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); // remove - $classes = array_diff($classes, ["PageElement"]); + $classes = array_diff($classes, [PageElement::class]); return $classes; } diff --git a/code/PageSectionsExtension.php b/code/PageSectionsExtension.php index 913d7c3..07b32c1 100644 --- a/code/PageSectionsExtension.php +++ b/code/PageSectionsExtension.php @@ -1,36 +1,52 @@ get($class, "__versioned", Config::EXCLUDE_EXTRA_SOURCES)) { - user_error("VersionedRelationsExtension was loaded before PageSectionsExtension on class '$class'. ". - "Please correct the order in your config file to ensure that PageSectionsExtension is loaded first.", E_USER_ERROR); - } - // Get all the sections that should be added $sections = Config::inst()->get($class, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); if (!$sections) $sections = array("Main"); foreach ($sections as $section) { $name = "PageSection".$section; - $versioned_many_many[$name] = "PageElement"; + $many_many[$name] = PageElement::class; $many_many_extraFields[$name] = array("SortOrder" => "Int"); + $owns[] = $name; } // Create the relations for our sections - Config::inst()->update($class, "versioned_many_many", $versioned_many_many); - Config::inst()->update($class, "many_many_extraFields", $many_many_extraFields); - return array(); + //Config::inst()->update($class, "many_many", $many_many); + //Config::inst()->update($class, "many_many_extraFields", $many_many_extraFields); + Config::modify()->merge($class, "owns", $owns); + return array( + "many_many" => $many_many, + "many_many_extraFields" => $many_many_extraFields, + ); } public static function getAllowedPageElements() { - $classes = array_values(ClassInfo::subclassesFor("PageElement")); - $classes = array_diff($classes, ["PageElement"]); + $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); + $classes = array_diff($classes, [PageElement::class]); return $classes; } @@ -82,7 +98,7 @@ public function updateCMSFields(FieldList $fields) { } public function PageSection($name = "Main") { - $elements = $this->owner->getVersionedRelation("PageSection" . $name); + $elements = $this->owner->{"PageSection" . $name}(); return $this->owner->renderWith( "RenderChildren", array("Elements" => $elements, "ParentList" => strval($this->owner->ID)) From 067e40984f7634fc8ead3892ef2295918393811c Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Thu, 1 Feb 2018 15:45:50 +0100 Subject: [PATCH 02/82] fix(page-sections): Move classes to FlxLabs\PageSections namespace. Apply extension to sitetree, because class Page might not exist. Fix some basic styling issues in gridview. Rename "Title" field to "Name". --- _config/config.yml | 2 +- code/GridFieldPageSectionsExtension.php | 4 +- code/PageElement.php | 68 ++++++++++--------- code/PageSectionsExtension.php | 15 ++-- css/GridFieldPageSectionsExtension.css | 8 ++- .../{ => FlxLabs/PageSections}/PageElement.ss | 2 +- 6 files changed, 53 insertions(+), 46 deletions(-) rename templates/{ => FlxLabs/PageSections}/PageElement.ss (89%) diff --git a/_config/config.yml b/_config/config.yml index 331a027..be14e1b 100644 --- a/_config/config.yml +++ b/_config/config.yml @@ -1,6 +1,6 @@ --- Name: pagesections --- -PageSections\PageElement: +FlxLabs\PageSections\PageElement: extensions: - SilverStripe\Versioned\Versioned diff --git a/code/GridFieldPageSectionsExtension.php b/code/GridFieldPageSectionsExtension.php index 8f9c16a..8c8e797 100644 --- a/code/GridFieldPageSectionsExtension.php +++ b/code/GridFieldPageSectionsExtension.php @@ -1,6 +1,6 @@ Title = "New " . $type; + $child->Name = "New " . $type; $child->write(); $obj->Children()->Add($child); diff --git a/code/PageElement.php b/code/PageElement.php index 23390da..72098b0 100644 --- a/code/PageElement.php +++ b/code/PageElement.php @@ -1,6 +1,6 @@ 'Varchar(255)', + "Name" => "Varchar(255)", ); private static $many_many = array( - 'Children' => PageElement::class, + "Children" => PageElement::class, ); private static $belongs_many_many = array( - 'Parents' => PageElement::class, - 'Pages' => 'Page', + "Parents" => PageElement::class, + "Pages" => SiteTree::class, ); private static $many_many_extraFields = array( - 'Children' => array( - 'SortOrder' => 'Int', + "Children" => array( + "SortOrder" => 'Int', ), ); @@ -60,15 +65,15 @@ function canCreate($member = null, $context = array()) { return true; } ]; private static $summary_fields = array( - 'SingularName', - 'ID', - 'GridFieldPreview', + "SingularName", + "ID", + "GridFieldPreview", ); private static $searchable_fields = array( - 'ClassName', - 'Title', - 'ID' + "ClassName", + "Name", + "ID", ); public static function getAllowedPageElements() { @@ -89,12 +94,24 @@ public function onBeforeWrite() { } } + public function onAfterWrite() { + $stage = Versioned::get_stage(); + + foreach ($this->Parents() as $parent) { + $parent->copyVersionToStage($stage, $stage, true); + } + + foreach ($this->Pages() as $page) { + $page->copyVersionToStage($stage, $stage, true); + } + } + public function getChildrenGridField() { $addNewButton = new GridFieldAddNewMultiClass(); $addNewButton->setClasses($this->getAllowedPageElements()); $autoCompl = new GridFieldAddExistingAutocompleter('buttons-before-right'); - $autoCompl->setResultsFormat('$Title ($ID)'); + $autoCompl->setResultsFormat('$Name ($ID)'); $autoCompl->setSearchList(PageElement::get()->exclude("ID", $this->getParentIDs())); $config = GridFieldConfig::create() @@ -111,7 +128,7 @@ public function getChildrenGridField() { } public function getGridFieldPreview() { - return $this->Title; + return $this->Name; } public function getCMSFields() { @@ -135,7 +152,7 @@ public function getParentIDs() { return $IDArr; } - public function renderChildren($parents) { + public function renderChildren($parents = null) { return $this->renderWith( "RenderChildren", array("Elements" => $this->Children(), "ParentList" => strval($this->ID) . "," . $parents) @@ -156,19 +173,4 @@ public function forTemplate($parentList = "") { array("ParentList" => $parentList, "Parents" => $parents, "Page" => $page) ); } - - public function getBetterButtonsUtils() { - $fieldList = FieldList::create(array( - BetterButtonPrevNextAction::create(), - )); - return $fieldList; - } - - public function getBetterButtonsActions() { - $fieldList = FieldList::create(array( - BetterButton_SaveAndClose::create(), - BetterButton_Save::create(), - )); - return $fieldList; - } } diff --git a/code/PageSectionsExtension.php b/code/PageSectionsExtension.php index 46da018..aa72559 100644 --- a/code/PageSectionsExtension.php +++ b/code/PageSectionsExtension.php @@ -1,6 +1,6 @@ update($class, "many_many", $many_many); - //Config::inst()->update($class, "many_many_extraFields", $many_many_extraFields); - Config::modify()->merge($class, "owns", $owns); return array( "many_many" => $many_many, "many_many_extraFields" => $many_many_extraFields, + "owns" => $owns, ); } @@ -51,6 +50,8 @@ public static function getAllowedPageElements() { } public function onBeforeWrite() { + parent::onBeforeWrite(); + $sections = $this->owner->config()->get("page_sections"); if (!$sections) $sections = array("Main"); @@ -80,7 +81,7 @@ public function updateCMSFields(FieldList $fields) { $addNewButton->setClasses($this->owner->getAllowedPageElements()); $autoCompl = new GridFieldAddExistingAutocompleter('buttons-before-right'); - $autoCompl->setResultsFormat('$Title ($ID)'); + $autoCompl->setResultsFormat('$Name ($ID)'); $config = GridFieldConfig::create() ->addComponent(new GridFieldButtonRow("before")) @@ -98,8 +99,8 @@ public function updateCMSFields(FieldList $fields) { } } - public function PageSection($name = "Main") { - $elements = $this->owner->{"PageSection" . $name}(); + public function RenderPageSection($name = "Main") { + $elements = $this->owner->{"PageSection" . $name}()->Sort("SortOrder"); return $this->owner->renderWith( "RenderChildren", array("Elements" => $elements, "ParentList" => strval($this->owner->ID)) diff --git a/css/GridFieldPageSectionsExtension.css b/css/GridFieldPageSectionsExtension.css index a031998..6ba66ea 100644 --- a/css/GridFieldPageSectionsExtension.css +++ b/css/GridFieldPageSectionsExtension.css @@ -43,15 +43,19 @@ .ss-gridfield-pagesections .col-treenav { height: 100%; - padding: 0 !important; + padding-left: 0; + /*padding: 0 !important;*/ } .ss-gridfield-pagesections .col-treenav > button { - //width: 100% !important; + /*width: 100% !important;*/ height: 100% !important; font-weight: bold !important; /*font-size: 150% !important;*/ color: black !important; + border: none; + background: transparent; + padding-right: 0; } .ss-gridfield-pagesections .col-treenav > button, .ss-gridfield-pagesections .col-treenav > button > span { diff --git a/templates/PageElement.ss b/templates/FlxLabs/PageSections/PageElement.ss similarity index 89% rename from templates/PageElement.ss rename to templates/FlxLabs/PageSections/PageElement.ss index 04c2f2c..346d3d5 100644 --- a/templates/PageElement.ss +++ b/templates/FlxLabs/PageSections/PageElement.ss @@ -1,5 +1,5 @@
-

$Title

+

$Name

$Layout
$RenderChildren($ParentList) From 76dfc5c3f150571bcb41393fb611210d8e08e587 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Thu, 22 Feb 2018 12:25:31 +0100 Subject: [PATCH 03/82] feat(relations): Connect elements with a many_many "through" relation [WIP] --- _config/config.yml | 6 +++ code/GridFieldPageSectionsExtension.php | 4 +- code/PageElement.php | 50 ++++++++++++++----------- code/PageElementSelfRel.php | 19 ++++++++++ code/PageSectionPageElementRel.php | 29 ++++++++++++++ code/PageSectionsExtension.php | 38 ++++++++++--------- composer.json | 3 +- 7 files changed, 106 insertions(+), 43 deletions(-) create mode 100644 code/PageElementSelfRel.php create mode 100644 code/PageSectionPageElementRel.php diff --git a/_config/config.yml b/_config/config.yml index be14e1b..b43d6c9 100644 --- a/_config/config.yml +++ b/_config/config.yml @@ -4,3 +4,9 @@ Name: pagesections FlxLabs\PageSections\PageElement: extensions: - SilverStripe\Versioned\Versioned +FlxLabs\PageSections\PageElementSelfRel: + extensions: + - SilverStripe\Versioned\Versioned +FlxLabs\PageSections\PageSectionPageElementRel: + extensions: + - SilverStripe\Versioned\Versioned diff --git a/code/GridFieldPageSectionsExtension.php b/code/GridFieldPageSectionsExtension.php index 8c8e797..1143f52 100644 --- a/code/GridFieldPageSectionsExtension.php +++ b/code/GridFieldPageSectionsExtension.php @@ -363,10 +363,10 @@ public function handleReorder(GridField $gridField, HTTPRequest $request) { } else { if ($newParent) { $newParent->Children()->Add($item, $sortArr); - $newParent->write(); + $newParent->writeWithoutVersion(); } else { $gridField->getList()->Add($item, $sortArr); - $this->getPage()->write(); + $this->getPage()->writeWithoutVersion(); } } diff --git a/code/PageElement.php b/code/PageElement.php index 72098b0..50bb9ce 100644 --- a/code/PageElement.php +++ b/code/PageElement.php @@ -23,7 +23,8 @@ use UncleCheese\BetterButtons\Buttons\BetterButton_Save; class PageElement extends DataObject { - + + private static $table_name = "FLXLabs_PageElement"; protected static $singularName = "Element"; protected static $pluralName = "Elements"; @@ -46,12 +47,16 @@ function canCreate($member = null, $context = array()) { return true; } ); private static $many_many = array( - "Children" => PageElement::class, + "Children" => array( + "through" => PageElementSelfRel::class, + "from" => "Parent", + "to" => "Child", + ) ); private static $belongs_many_many = array( - "Parents" => PageElement::class, - "Pages" => SiteTree::class, + "Parents" => PageElement::class . ".Children", + "Pages" => "Page.PageSectionMain", ); private static $many_many_extraFields = array( @@ -59,7 +64,7 @@ function canCreate($member = null, $context = array()) { return true; } "SortOrder" => 'Int', ), ); - + private static $owns = [ "Children", ]; @@ -83,28 +88,29 @@ public static function getAllowedPageElements() { return $classes; } - public function onBeforeWrite() { - parent::onBeforeWrite(); + // public function onBeforeWrite() { + // parent::onBeforeWrite(); - $list = $this->Children()->Sort("SortOrder")->toArray(); - $count = count($list); + // $list = $this->Children()->Sort("SortOrder")->toArray(); + // $this->Children()->RemoveAll(); + // $count = count($list); - for ($i = 1; $i <= $count; $i++) { - $this->Children()->Add($list[$i - 1], array("SortOrder" => $i * 2)); - } - } + // for ($i = 1; $i <= $count; $i++) { + // $this->Children()->Add($list[$i - 1], array("SortOrder" => $i * 2)); + // } + // } - public function onAfterWrite() { - $stage = Versioned::get_stage(); + // public function onAfterWrite() { + // $stage = Versioned::get_stage(); - foreach ($this->Parents() as $parent) { - $parent->copyVersionToStage($stage, $stage, true); - } + // foreach ($this->Parents() as $parent) { + // $parent->copyVersionToStage($stage, $stage, true); + // } - foreach ($this->Pages() as $page) { - $page->copyVersionToStage($stage, $stage, true); - } - } + // foreach ($this->Pages() as $page) { + // $page->copyVersionToStage($stage, $stage, true); + // } + // } public function getChildrenGridField() { $addNewButton = new GridFieldAddNewMultiClass(); diff --git a/code/PageElementSelfRel.php b/code/PageElementSelfRel.php new file mode 100644 index 0000000..b2c3755 --- /dev/null +++ b/code/PageElementSelfRel.php @@ -0,0 +1,19 @@ + "Int", + ); + + private static $has_one = array( + "Parent" => PageElement::class, + "Child" => PageElement::class, + ); +} diff --git a/code/PageSectionPageElementRel.php b/code/PageSectionPageElementRel.php new file mode 100644 index 0000000..d31e95f --- /dev/null +++ b/code/PageSectionPageElementRel.php @@ -0,0 +1,29 @@ + "Int", + ); + + private static $has_one = array( + "PageSection" => "Page", + "Element" => PageElement::class, + ); + + + public function onBeforeWrite() { + parent::onBeforeWrite(); + + if (!$this->ID && !$this->SortOrder) { + $this->SortOrder = 1337; + } + } +} diff --git a/code/PageSectionsExtension.php b/code/PageSectionsExtension.php index aa72559..81a5114 100644 --- a/code/PageSectionsExtension.php +++ b/code/PageSectionsExtension.php @@ -22,7 +22,6 @@ class PageSectionsExtension extends DataExtension { // Generate the needed relations on the class public static function get_extra_config($class = null, $extensionClass = null) { $many_many = array(); - $many_many_extraFields = array(); // Get all the sections that should be added $sections = Config::inst()->get($class, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); @@ -30,15 +29,19 @@ public static function get_extra_config($class = null, $extensionClass = null) { foreach ($sections as $section) { $name = "PageSection".$section; - $many_many[$name] = PageElement::class; - $many_many_extraFields[$name] = array("SortOrder" => "Int"); + // $many_many[$name] = PageElement::class; + $many_many[$name] = array( + "through" => PageSectionPageElementRel::class, + "from" => "PageSection", + "to" => "Element", + ); + $owns[] = $name; } // Create the relations for our sections return array( "many_many" => $many_many, - "many_many_extraFields" => $many_many_extraFields, "owns" => $owns, ); } @@ -49,23 +52,24 @@ public static function getAllowedPageElements() { return $classes; } - public function onBeforeWrite() { - parent::onBeforeWrite(); + // public function onBeforeWrite() { + // parent::onBeforeWrite(); - $sections = $this->owner->config()->get("page_sections"); - if (!$sections) $sections = array("Main"); + // $sections = $this->owner->config()->get("page_sections"); + // if (!$sections) $sections = array("Main"); - foreach ($sections as $section) { - $name = "PageSection".$section; + // foreach ($sections as $section) { + // $name = "PageSection".$section; - $list = $this->owner->$name()->Sort("SortOrder")->toArray(); - $count = count($list); + // $list = $this->owner->$name()->Sort("SortOrder")->toArray(); + // $this->owner->$name()->RemoveAll(); + // $count = count($list); - for ($i = 1; $i <= $count; $i++) { - $this->owner->$name()->Add($list[$i - 1], array("SortOrder" => $i * 2)); - } - } - } + // for ($i = 1; $i <= $count; $i++) { + // $this->owner->$name()->Add($list[$i - 1], array("SortOrder" => $i * 2)); + // } + // } + // } public function updateCMSFields(FieldList $fields) { $sections = $this->owner->config()->get("page_sections"); diff --git a/composer.json b/composer.json index 107ef7e..e0579ee 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,7 @@ "issues": "http://github.com/flxlabs/silverstripe-pagesections/issues" }, "require": { - "silverstripe/framework": "~3.6", - "flxlabs/silverstripe-versionedrelations": "^0.1.7" + "silverstripe/framework": "^4.0.1" }, "extra": { "installer-name": "pagesections" From 2250174138f0d772f1e72e9360aad5be7ff1e7dc Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Thu, 31 May 2018 18:06:48 +0200 Subject: [PATCH 04/82] feat(ss4.1): Update to SilverStripe 4.1 --- LICENSE | 0 README.md | 0 _config/config.yml | 6 +- composer.json | 47 +- composer.lock | 1903 +++++++++++++++++ css/GridFieldPageSectionsExtension.css | 0 examples/ImageElement.php_ | 0 examples/TextElement.php_ | 0 javascript/GridFieldPageSectionsExtension.js | 0 resources/.htaccess | 11 + resources/.method | 1 + .../silverstripe/framework/client/images | 1 + .../silverstripe/framework/client/styles | 1 + .../framework/src/Dev/Install/client | 1 + .../silverstripe-gridfieldextensions/css | 1 + .../javascript | 1 + .../GridFieldPageSectionsExtension.php | 54 +- {code => src}/PageElement.php | 56 +- {code => src}/PageElementSelfRel.php | 4 +- {code => src}/PageSectionPageElementRel.php | 12 +- {code => src}/PageSectionsExtension.php | 30 +- .../PageSections/PageElement.ss | 0 templates/GridFieldDragHandle.ss | 0 templates/GridFieldPageElement.ss | 0 templates/Layout/PageElement.ss | 0 templates/RenderChildren.ss | 0 26 files changed, 2028 insertions(+), 101 deletions(-) mode change 100644 => 100755 LICENSE mode change 100644 => 100755 README.md mode change 100644 => 100755 _config/config.yml mode change 100644 => 100755 composer.json create mode 100644 composer.lock mode change 100644 => 100755 css/GridFieldPageSectionsExtension.css mode change 100644 => 100755 examples/ImageElement.php_ mode change 100644 => 100755 examples/TextElement.php_ mode change 100644 => 100755 javascript/GridFieldPageSectionsExtension.js create mode 100644 resources/.htaccess create mode 100644 resources/.method create mode 120000 resources/silverstripe/framework/client/images create mode 120000 resources/silverstripe/framework/client/styles create mode 120000 resources/silverstripe/framework/src/Dev/Install/client create mode 120000 resources/symbiote/silverstripe-gridfieldextensions/css create mode 120000 resources/symbiote/silverstripe-gridfieldextensions/javascript rename {code => src}/GridFieldPageSectionsExtension.php (91%) mode change 100644 => 100755 rename {code => src}/PageElement.php (81%) mode change 100644 => 100755 rename {code => src}/PageElementSelfRel.php (67%) rename {code => src}/PageSectionPageElementRel.php (64%) rename {code => src}/PageSectionsExtension.php (84%) mode change 100644 => 100755 rename templates/{FlxLabs => FLXLabs}/PageSections/PageElement.ss (100%) mode change 100644 => 100755 mode change 100644 => 100755 templates/GridFieldDragHandle.ss mode change 100644 => 100755 templates/GridFieldPageElement.ss mode change 100644 => 100755 templates/Layout/PageElement.ss mode change 100644 => 100755 templates/RenderChildren.ss diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/_config/config.yml b/_config/config.yml old mode 100644 new mode 100755 index b43d6c9..3fcddeb --- a/_config/config.yml +++ b/_config/config.yml @@ -1,12 +1,12 @@ --- Name: pagesections --- -FlxLabs\PageSections\PageElement: +FLXLabs\PageSections\PageElement: extensions: - SilverStripe\Versioned\Versioned -FlxLabs\PageSections\PageElementSelfRel: +FLXLabs\PageSections\PageElementSelfRel: extensions: - SilverStripe\Versioned\Versioned -FlxLabs\PageSections\PageSectionPageElementRel: +FLXLabs\PageSections\PageSectionPageElementRel: extensions: - SilverStripe\Versioned\Versioned diff --git a/composer.json b/composer.json old mode 100644 new mode 100755 index e0579ee..72bfafe --- a/composer.json +++ b/composer.json @@ -1,24 +1,27 @@ { - "name": "flxlabs/silverstripe-pagesections", - "version": "0.1.2", - "description": "Adds configurable page sections and elements to your SilverStripe project.", - "type": "silverstripe-module", - "homepage": "http://github.com/flxlabs/silverstripe-pagesections", - "keywords": ["silverstripe", "sections", "elements", "page sections", "page elements"], - "license": "MIT", - "authors": [ - { - "name": "Marco Crespi", - "email": "mrc@flxlabs.com" - } - ], - "support": { - "issues": "http://github.com/flxlabs/silverstripe-pagesections/issues" - }, - "require": { - "silverstripe/framework": "^4.0.1" - }, - "extra": { - "installer-name": "pagesections" - } + "name": "flxlabs/silverstripe-pagesections", + "version": "0.1.2", + "description": "Adds configurable page sections and elements to your SilverStripe project.", + "type": "silverstripe-module", + "homepage": "http://github.com/flxlabs/silverstripe-pagesections", + "keywords": ["silverstripe", "sections", "elements", "page sections", "page elements"], + "license": "MIT", + "authors": [{ + "name": "Marco Crespi", + "email": "mrc@flxlabs.com" + }], + "support": { + "issues": "http://github.com/flxlabs/silverstripe-pagesections/issues" + }, + "require": { + "silverstripe/framework": "^4.0.1", + "symbiote/silverstripe-gridfieldextensions": "^3" + }, + "extra": { + "installer-name": "pagesections", + "expose": [ + "css", + "javascript" + ] + } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..00113c6 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1903 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "40aba9751a6b54c74a81a32dd1fd00ae", + "packages": [ + { + "name": "composer/ca-bundle", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169", + "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0 || ^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "time": "2018-03-29T19:57:20+00:00" + }, + { + "name": "composer/installers", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "049797d727261bf27f2690430d935067710049c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/049797d727261bf27f2690430d935067710049c2", + "reference": "049797d727261bf27f2690430d935067710049c2", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "replace": { + "roundcube/plugin-installer": "*", + "shama/baton": "*" + }, + "require-dev": { + "composer/composer": "1.0.*@dev", + "phpunit/phpunit": "^4.8.36" + }, + "type": "composer-plugin", + "extra": { + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Installers\\": "src/Composer/Installers" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "description": "A multi-framework Composer library installer", + "homepage": "https://composer.github.io/installers/", + "keywords": [ + "Craft", + "Dolibarr", + "Eliasis", + "Hurad", + "ImageCMS", + "Kanboard", + "Lan Management System", + "MODX Evo", + "Mautic", + "Maya", + "OXID", + "Plentymarkets", + "Porto", + "RadPHP", + "SMF", + "Thelia", + "WolfCMS", + "agl", + "aimeos", + "annotatecms", + "attogram", + "bitrix", + "cakephp", + "chef", + "cockpit", + "codeigniter", + "concrete5", + "croogo", + "dokuwiki", + "drupal", + "eZ Platform", + "elgg", + "expressionengine", + "fuelphp", + "grav", + "installer", + "itop", + "joomla", + "kohana", + "laravel", + "lavalite", + "lithium", + "magento", + "majima", + "mako", + "mediawiki", + "modulework", + "modx", + "moodle", + "osclass", + "phpbb", + "piwik", + "ppi", + "puppet", + "pxcms", + "reindex", + "roundcube", + "shopware", + "silverstripe", + "sydes", + "symfony", + "typo3", + "wordpress", + "yawik", + "zend", + "zikula" + ], + "time": "2017-12-29T09:13:20+00:00" + }, + { + "name": "embed/embed", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/oscarotero/Embed.git", + "reference": "e02f6f2f590cccf13c6cb64c975c32ddc88ae4b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/oscarotero/Embed/zipball/e02f6f2f590cccf13c6cb64c975c32ddc88ae4b9", + "reference": "e02f6f2f590cccf13c6cb64c975c32ddc88ae4b9", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "ext-curl": "*", + "ext-mbstring": "*", + "php": "^5.5|^7.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "phpunit/phpunit": "^4.8|^5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Embed\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oscar Otero", + "email": "oom@oscarotero.com", + "homepage": "http://oscarotero.com", + "role": "Developer" + } + ], + "description": "PHP library to retrieve page info using oembed, opengraph, etc", + "homepage": "https://github.com/oscarotero/Embed", + "keywords": [ + "embed", + "embedly", + "oembed", + "opengraph", + "twitter cards" + ], + "time": "2018-05-22T21:00:13+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" + }, + { + "name": "intervention/image", + "version": "2.4.2", + "source": { + "type": "git", + "url": "https://github.com/Intervention/image.git", + "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Intervention/image/zipball/e82d274f786e3d4b866a59b173f42e716f0783eb", + "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "guzzlehttp/psr7": "~1.1", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9.2", + "phpunit/phpunit": "^4.8 || ^5.7" + }, + "suggest": { + "ext-gd": "to use GD library based image processing.", + "ext-imagick": "to use Imagick based image processing.", + "intervention/imagecache": "Caching extension for the Intervention Image library" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": { + "Image": "Intervention\\Image\\Facades\\Image" + } + } + }, + "autoload": { + "psr-4": { + "Intervention\\Image\\": "src/Intervention/Image" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@olivervogel.com", + "homepage": "http://olivervogel.com/" + } + ], + "description": "Image handling and manipulation library with support for Laravel integration", + "homepage": "http://image.intervention.io/", + "keywords": [ + "gd", + "image", + "imagick", + "laravel", + "thumbnail", + "watermark" + ], + "time": "2018-05-29T14:19:03+00:00" + }, + { + "name": "league/flysystem", + "version": "1.0.45", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "a99f94e63b512d75f851b181afcdf0ee9ebef7e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a99f94e63b512d75f851b181afcdf0ee9ebef7e6", + "reference": "a99f94e63b512d75f851b181afcdf0ee9ebef7e6", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "ext-fileinfo": "*", + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "time": "2018-05-07T08:44:23+00:00" + }, + { + "name": "m1/env", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/m1/Env.git", + "reference": "3589eae8e40d40be96de39222a6ca19c3af8eae4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/m1/Env/zipball/3589eae8e40d40be96de39222a6ca19c3af8eae4", + "reference": "3589eae8e40d40be96de39222a6ca19c3af8eae4", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "scrutinizer/ocular": "~1.1", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "josegonzalez/dotenv": "For loading of .env", + "m1/vars": "For loading of configs" + }, + "type": "library", + "autoload": { + "psr-4": { + "M1\\Env\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Miles Croxford", + "email": "hello@milescroxford.com", + "homepage": "http://milescroxford.com", + "role": "Developer" + } + ], + "description": "Env is a lightweight library bringing .env file parser compatibility to PHP. In short - it enables you to read .env files with PHP.", + "homepage": "https://github.com/m1/Env", + "keywords": [ + ".env", + "config", + "dotenv", + "env", + "loader", + "m1", + "parser", + "support" + ], + "time": "2018-05-16T22:40:58+00:00" + }, + { + "name": "marcj/topsort", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/marcj/topsort.php.git", + "reference": "387086c2db60ee0a27ac5df588c0f0b30c6bdc4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marcj/topsort.php/zipball/387086c2db60ee0a27ac5df588c0f0b30c6bdc4b", + "reference": "387086c2db60ee0a27ac5df588c0f0b30c6bdc4b", + "shasum": "" + }, + "require": { + "php": ">=5.4" + }, + "require-dev": { + "codeclimate/php-test-reporter": "dev-master", + "phpunit/phpunit": "~4.0", + "symfony/console": "~2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "MJS\\TopSort\\": "src/", + "MJS\\TopSort\\Tests\\": "tests/Tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marc J. Schmidt", + "email": "marc@marcjschmidt.de" + } + ], + "description": "High-Performance TopSort/Dependency resolving algorithm", + "keywords": [ + "dependency resolving", + "topological sort", + "topsort" + ], + "time": "2016-11-19T14:58:11+00:00" + }, + { + "name": "monolog/monolog", + "version": "1.23.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2017-06-19T01:22:40+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2018-02-28T20:30:58+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.12", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb", + "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2018-04-04T21:24:14+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10T12:19:37+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "silverstripe/assets", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/silverstripe/silverstripe-assets.git", + "reference": "b0d44023353254758d35d0e7ee26f634e7493fab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silverstripe/silverstripe-assets/zipball/b0d44023353254758d35d0e7ee26f634e7493fab", + "reference": "b0d44023353254758d35d0e7ee26f634e7493fab", + "shasum": "" + }, + "require": { + "intervention/image": "^2.3", + "php": ">=5.6.0", + "silverstripe/framework": "^4.1", + "silverstripe/vendor-plugin": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "silverstripe/versioned": "^1@dev" + }, + "type": "silverstripe-vendormodule", + "extra": { + "branch-alias": { + "1.x-dev": "1.2.x-dev", + "dev-master": "2.x-dev" + }, + "installer-name": "silverstripe-assets" + }, + "autoload": { + "psr-4": { + "SilverStripe\\Assets\\": "src/", + "SilverStripe\\Assets\\Tests\\": "tests/php/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "SilverStripe", + "homepage": "http://silverstripe.com" + }, + { + "name": "The SilverStripe Community", + "homepage": "http://silverstripe.org" + } + ], + "description": "SilverStripe Assets component", + "homepage": "http://silverstripe.org", + "keywords": [ + "assets", + "silverstripe" + ], + "time": "2018-05-24T03:24:57+00:00" + }, + { + "name": "silverstripe/config", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/silverstripe/silverstripe-config.git", + "reference": "65cc33edc4d88d20cab5b7ad4b6f52bd20747e98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silverstripe/silverstripe-config/zipball/65cc33edc4d88d20cab5b7ad4b6f52bd20747e98", + "reference": "65cc33edc4d88d20cab5b7ad4b6f52bd20747e98", + "shasum": "" + }, + "require": { + "marcj/topsort": "^1.0", + "psr/simple-cache": "^1.0", + "symfony/finder": "^2.8 || ^3.2", + "symfony/yaml": "^2.8 || ^3.2" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "phpspec/prophecy": "^1.0", + "phpunit/phpunit": "^5.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "SilverStripe\\Config\\": "src/", + "SilverStripe\\Config\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "SilverStripe configuration based on YAML and class statics", + "time": "2018-02-14T20:07:02+00:00" + }, + { + "name": "silverstripe/framework", + "version": "4.1.1", + "source": { + "type": "git", + "url": "https://github.com/silverstripe/silverstripe-framework.git", + "reference": "01ed8a316b65c7d2dddecec39c0195a0e3de07b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silverstripe/silverstripe-framework/zipball/01ed8a316b65c7d2dddecec39c0195a0e3de07b2", + "reference": "01ed8a316b65c7d2dddecec39c0195a0e3de07b2", + "shasum": "" + }, + "require": { + "composer/installers": "~1.0", + "embed/embed": "^3.0", + "ext-ctype": "*", + "ext-dom": "*", + "ext-hash": "*", + "ext-intl": "*", + "ext-mbstring": "*", + "ext-session": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "league/flysystem": "~1.0.12", + "m1/env": "^2.1", + "monolog/monolog": "~1.11", + "nikic/php-parser": "^2 || ^3", + "paragonie/random_compat": "^2.0", + "php": ">=5.6.0", + "psr/container": "1.0.0", + "psr/container-implementation": "1.0.0", + "silverstripe/assets": "^1@dev", + "silverstripe/config": "^1@dev", + "silverstripe/vendor-plugin": "^1.0", + "swiftmailer/swiftmailer": "~5.4", + "symfony/cache": "^3.3@dev", + "symfony/config": "^3.2", + "symfony/translation": "^2.8", + "symfony/yaml": "~3.2" + }, + "provide": { + "psr/container-implementation": "1.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "se/selenium-server-standalone": "2.41.0", + "silverstripe/behat-extension": "^3", + "silverstripe/serve": "^2@dev", + "silverstripe/versioned": "^1@dev" + }, + "bin": [ + "sake" + ], + "type": "silverstripe-vendormodule", + "extra": { + "branch-alias": { + "4.x-dev": "4.2.x-dev", + "dev-master": "5.x-dev" + }, + "expose": [ + "client/images", + "client/styles", + "src/Dev/Install/client" + ] + }, + "autoload": { + "psr-4": { + "SilverStripe\\Control\\": "src/Control/", + "SilverStripe\\Control\\Tests\\": "tests/php/Control/", + "SilverStripe\\Core\\": "src/Core/", + "SilverStripe\\Core\\Tests\\": "tests/php/Core/", + "SilverStripe\\Dev\\": "src/Dev/", + "SilverStripe\\Dev\\Tests\\": "tests/php/Dev/", + "SilverStripe\\Forms\\": "src/Forms/", + "SilverStripe\\Forms\\Tests\\": "tests/php/Forms/", + "SilverStripe\\i18n\\": "src/i18n/", + "SilverStripe\\i18n\\Tests\\": "tests/php/i18n/", + "SilverStripe\\Logging\\": "src/Logging/", + "SilverStripe\\Logging\\Tests\\": "tests/php/Logging/", + "SilverStripe\\ORM\\": "src/ORM/", + "SilverStripe\\ORM\\Tests\\": "tests/php/ORM/", + "SilverStripe\\Security\\": "src/Security/", + "SilverStripe\\Security\\Tests\\": "tests/php/Security/", + "SilverStripe\\View\\": "src/View/", + "SilverStripe\\View\\Tests\\": "tests/php/View/", + "SilverStripe\\Framework\\Tests\\Behaviour\\": "tests/behat/src/" + }, + "files": [ + "src/includes/constants.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "src/", + "src/includes/", + "thirdparty/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "SilverStripe", + "homepage": "http://silverstripe.com" + }, + { + "name": "The SilverStripe Community", + "homepage": "http://silverstripe.org" + } + ], + "description": "The SilverStripe framework", + "homepage": "http://silverstripe.org", + "keywords": [ + "framework", + "silverstripe" + ], + "time": "2018-05-24T04:52:15+00:00" + }, + { + "name": "silverstripe/vendor-plugin", + "version": "1.3.3", + "source": { + "type": "git", + "url": "https://github.com/silverstripe/vendor-plugin.git", + "reference": "ec27b75cc67adc31c458e8ccd91e787a9534c15c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silverstripe/vendor-plugin/zipball/ec27b75cc67adc31c458e8ccd91e787a9534c15c", + "reference": "ec27b75cc67adc31c458e8ccd91e787a9534c15c", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1", + "composer/installers": "^1.4" + }, + "require-dev": { + "composer/composer": "^1.5", + "phpunit/phpunit": "^5.7" + }, + "type": "composer-plugin", + "extra": { + "class": "SilverStripe\\VendorPlugin\\VendorPlugin", + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "SilverStripe\\VendorPlugin\\": "src/", + "SilverStripe\\VendorPlugin\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Damian Mooyman", + "email": "damian@silverstripe.com" + } + ], + "description": "Allows vendor modules to expose directories to the webroot", + "time": "2018-03-25T21:50:30+00:00" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v5.4.9", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "7ffc1ea296ed14bf8260b6ef11b80208dbadba91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/7ffc1ea296ed14bf8260b6ef11b80208dbadba91", + "reference": "7ffc1ea296ed14bf8260b6ef11b80208dbadba91", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "https://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ], + "time": "2018-01-23T07:37:21+00:00" + }, + { + "name": "symbiote/silverstripe-gridfieldextensions", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/symbiote/silverstripe-gridfieldextensions.git", + "reference": "6d8b41ee5a0b0f1b07215f5bf146c07a78ef61fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symbiote/silverstripe-gridfieldextensions/zipball/6d8b41ee5a0b0f1b07215f5bf146c07a78ef61fe", + "reference": "6d8b41ee5a0b0f1b07215f5bf146c07a78ef61fe", + "shasum": "" + }, + "require": { + "silverstripe/framework": "~4.0", + "silverstripe/vendor-plugin": "^1.0" + }, + "replace": { + "ajshort/silverstripe-gridfieldextensions": "self.version", + "silverstripe-australia/gridfieldextensions": "self.version" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "silverstripe/versioned": "^1@dev", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "silverstripe-vendormodule", + "extra": { + "installer-name": "gridfieldextensions", + "screenshots": [ + "docs/en/_images/editable-rows.png", + "docs/en/_images/add-existing-search.png" + ], + "expose": [ + "css", + "javascript" + ] + }, + "autoload": { + "psr-4": { + "Symbiote\\GridFieldExtensions\\": "src/", + "Symbiote\\GridFieldExtensions\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Andrew Short", + "email": "andrewjshort@gmail.com" + }, + { + "name": "Marcus Nyeholt", + "email": "marcus@symbiote.com.au" + } + ], + "description": "A collection of useful grid field components", + "homepage": "http://github.com/symbiote/silverstripe-gridfieldextensions", + "keywords": [ + "gridfield", + "silverstripe" + ], + "time": "2018-02-11T22:08:47+00:00" + }, + { + "name": "symfony/cache", + "version": "v3.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "b6f157d4529a3484f60ebc40661b5232526fb432" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/b6f157d4529a3484f60ebc40661b5232526fb432", + "reference": "b6f157d4529a3484f60ebc40661b5232526fb432", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/cache": "~1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "symfony/polyfill-apcu": "~1.1" + }, + "conflict": { + "symfony/var-dumper": "<3.3" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.4", + "predis/predis": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "time": "2018-05-16T12:49:49+00:00" + }, + { + "name": "symfony/config", + "version": "v3.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "73e055cf2e6467715f187724a0347ea32079967c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/73e055cf2e6467715f187724a0347ea32079967c", + "reference": "73e055cf2e6467715f187724a0347ea32079967c", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/filesystem": "~2.8|~3.0|~4.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3", + "symfony/finder": "<3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/event-dispatcher": "~3.3|~4.0", + "symfony/finder": "~3.3|~4.0", + "symfony/yaml": "~3.0|~4.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2018-05-14T16:49:53+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v3.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "8e03ca3fa52a0f56b87506f38cf7bd3f9442b3a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/8e03ca3fa52a0f56b87506f38cf7bd3f9442b3a0", + "reference": "8e03ca3fa52a0f56b87506f38cf7bd3f9442b3a0", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2018-05-16T08:49:21+00:00" + }, + { + "name": "symfony/finder", + "version": "v3.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "472a92f3df8b247b49ae364275fb32943b9656c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/472a92f3df8b247b49ae364275fb32943b9656c6", + "reference": "472a92f3df8b247b49ae364275fb32943b9656c6", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2018-05-16T08:49:21+00:00" + }, + { + "name": "symfony/polyfill-apcu", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-apcu.git", + "reference": "9b83bd010112ec196410849e840d9b9fefcb15ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/9b83bd010112ec196410849e840d9b9fefcb15ad", + "reference": "9b83bd010112ec196410849e840d9b9fefcb15ad", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Apcu\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "apcu", + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2018-04-26T10:06:28+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2018-04-30T19:57:29+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "3296adf6a6454a050679cde90f95350ad604b171" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", + "reference": "3296adf6a6454a050679cde90f95350ad604b171", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2018-04-26T10:06:28+00:00" + }, + { + "name": "symfony/translation", + "version": "v2.8.41", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "c6a27966a92fa361bf2c3a938abc6dee91f7ad67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/c6a27966a92fa361bf2c3a938abc6dee91f7ad67", + "reference": "c6a27966a92fa361bf2c3a938abc6dee91f7ad67", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.7" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8", + "symfony/intl": "~2.7.25|^2.8.18|~3.2.5", + "symfony/yaml": "~2.2|~3.0.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2018-05-21T09:59:10+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "c5010cc1692ce1fa328b1fb666961eb3d4a85bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c5010cc1692ce1fa328b1fb666961eb3d4a85bb0", + "reference": "c5010cc1692ce1fa328b1fb666961eb3d4a85bb0", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-05-03T23:18:14+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/css/GridFieldPageSectionsExtension.css b/css/GridFieldPageSectionsExtension.css old mode 100644 new mode 100755 diff --git a/examples/ImageElement.php_ b/examples/ImageElement.php_ old mode 100644 new mode 100755 diff --git a/examples/TextElement.php_ b/examples/TextElement.php_ old mode 100644 new mode 100755 diff --git a/javascript/GridFieldPageSectionsExtension.js b/javascript/GridFieldPageSectionsExtension.js old mode 100644 new mode 100755 diff --git a/resources/.htaccess b/resources/.htaccess new file mode 100644 index 0000000..ad0b01d --- /dev/null +++ b/resources/.htaccess @@ -0,0 +1,11 @@ +# Block .method file + + Order Allow,Deny + Deny from all + + +# Block 404s + + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule .* - [R=404,L] + diff --git a/resources/.method b/resources/.method new file mode 100644 index 0000000..4d18c3e --- /dev/null +++ b/resources/.method @@ -0,0 +1 @@ +auto \ No newline at end of file diff --git a/resources/silverstripe/framework/client/images b/resources/silverstripe/framework/client/images new file mode 120000 index 0000000..e06f810 --- /dev/null +++ b/resources/silverstripe/framework/client/images @@ -0,0 +1 @@ +../../../../vendor/silverstripe/framework/client/images \ No newline at end of file diff --git a/resources/silverstripe/framework/client/styles b/resources/silverstripe/framework/client/styles new file mode 120000 index 0000000..8ecbb77 --- /dev/null +++ b/resources/silverstripe/framework/client/styles @@ -0,0 +1 @@ +../../../../vendor/silverstripe/framework/client/styles \ No newline at end of file diff --git a/resources/silverstripe/framework/src/Dev/Install/client b/resources/silverstripe/framework/src/Dev/Install/client new file mode 120000 index 0000000..66216fe --- /dev/null +++ b/resources/silverstripe/framework/src/Dev/Install/client @@ -0,0 +1 @@ +../../../../../../vendor/silverstripe/framework/src/Dev/Install/client \ No newline at end of file diff --git a/resources/symbiote/silverstripe-gridfieldextensions/css b/resources/symbiote/silverstripe-gridfieldextensions/css new file mode 120000 index 0000000..0c11010 --- /dev/null +++ b/resources/symbiote/silverstripe-gridfieldextensions/css @@ -0,0 +1 @@ +../../../vendor/symbiote/silverstripe-gridfieldextensions/css \ No newline at end of file diff --git a/resources/symbiote/silverstripe-gridfieldextensions/javascript b/resources/symbiote/silverstripe-gridfieldextensions/javascript new file mode 120000 index 0000000..66f251e --- /dev/null +++ b/resources/symbiote/silverstripe-gridfieldextensions/javascript @@ -0,0 +1 @@ +../../../vendor/symbiote/silverstripe-gridfieldextensions/javascript \ No newline at end of file diff --git a/code/GridFieldPageSectionsExtension.php b/src/GridFieldPageSectionsExtension.php old mode 100644 new mode 100755 similarity index 91% rename from code/GridFieldPageSectionsExtension.php rename to src/GridFieldPageSectionsExtension.php index 1143f52..731e66a --- a/code/GridFieldPageSectionsExtension.php +++ b/src/GridFieldPageSectionsExtension.php @@ -1,6 +1,6 @@ "handleAdd", "POST remove" => "handleRemove", "POST delete" => "handleDelete", "POST reorder" => "handleReorder", "POST movetopage" => "handleMoveToPage" - ); + ]; } public static function getModuleDir() { @@ -77,7 +77,7 @@ public function getHTMLFragments($field) { $field->setAttribute("data-url-reorder", $field->Link("reorder")); $field->setAttribute("data-url-movetopage", $field->Link("movetopage")); - return array(); + return []; } public function augmentColumns($gridField, &$columns) { @@ -96,53 +96,53 @@ public function augmentColumns($gridField, &$columns) { // Insert grid state initial data $state = $gridField->getState(); if (!isset($state->open)) { - $state->open = array(); + $state->open = []; } } public function getColumnsHandled($gridField) { - return array( + return [ "Reorder", "TreeNav", "Actions", - ); + ]; } public function getColumnMetadata($gridField, $columnName) { - return array("title" => ""); + return ["title" => ""]; } public function getColumnAttributes($gridField, $record, $columnName) { // Handle reorder column if ($columnName == "Reorder") { - return array( + return [ "class" => "col-reorder", "data-sort" => $record->getField($this->getSortField()), - ); + ]; } // Handle tree nav column else if ($columnName == "TreeNav") { $classes = $record->getAllowedPageElements(); - $elems = array(); + $elems = []; foreach ($classes as $class) { $elems[$class] = $class::getSingularName(); } - return array( + return [ "class" => "col-treenav", "data-class" => $record->ClassName, "data-level" => strval($record->_Level), "data-parent" => $record->_Parent ? strval($record->_Parent->ID) : "", "data-allowed-elements" => json_encode($elems, JSON_UNESCAPED_UNICODE), - ); + ]; } // Handle the actions column else if ($columnName == "Actions") { - return array( + return [ "class" => "col-actions", - ); + ]; } } @@ -173,16 +173,16 @@ public function getColumnContent($gridField, $record, $columnName) { "TreeNavAction".$record->ID, null, "dotreenav", - array("element" => $record) + ["element" => $record] ); $field->addExtraClass("level".$level . ($open ? " is-open" : " is-closed")); $field->setButtonContent($icon); $field->setForm($gridField->getForm()); - return ViewableData::create()->customise(array( + return ViewableData::create()->customise([ "ButtonField" => $field, "Title" => $record->i18n_singular_name(), - ))->renderWith("GridFieldPageElement"); + ])->renderWith("GridFieldPageElement"); } else if ($columnName == "Actions") { @@ -200,7 +200,7 @@ public function getColumnContent($gridField, $record, $columnName) { } public function getActions($gridField) { - return array("dotreenav"); + return ["dotreenav"]; } public function handleAction(GridField $gridField, $actionName, $arguments, $data) { @@ -225,7 +225,7 @@ public function handleAction(GridField $gridField, $actionName, $arguments, $dat } private function isOpen($state, $element) { - $list = array(); + $list = []; $base = $element; while ($base) { $list[] = $base; @@ -245,7 +245,7 @@ private function isOpen($state, $element) { return true; } private function openElement($state, $element) { - $list = array(); + $list = []; $base = $element; while ($base) { $list[] = $base; @@ -257,14 +257,14 @@ private function openElement($state, $element) { $opens = $state->open; foreach ($list as $item) { if (!isset($opens->{$item->ID})) { - $opens->{$item->ID} = array(); + $opens->{$item->ID} = []; } $opens = $opens->{$item->ID}; } } private function closeElement($state, $element) { - $list = array(); + $list = []; $base = $element->_Parent; while ($base) { $list[] = $base; @@ -286,7 +286,7 @@ public function getManipulatedData(GridField $gridField, SS_List $dataList) { $state = $gridField->getState(true); $opens = $state->open; - $arr = array(); + $arr = []; foreach ($list as $item) { $item->_Level = 0; $item->_Open = false; @@ -352,7 +352,7 @@ public function handleReorder(GridField $gridField, HTTPRequest $request) { $num = $type == "before" ? -1 : 1; $sortBy = $this->getSortField(); - $sortArr = array($sortBy => $sort + $num); + $sortArr = [$sortBy => $sort + $num]; if ($type == "child") { if ($newParent) { diff --git a/code/PageElement.php b/src/PageElement.php old mode 100644 new mode 100755 similarity index 81% rename from code/PageElement.php rename to src/PageElement.php index 50bb9ce..e511efc --- a/code/PageElement.php +++ b/src/PageElement.php @@ -1,6 +1,6 @@ "Varchar(255)", - ); + ]; - private static $many_many = array( - "Children" => array( + private static $many_many = [ + "Children" => [ "through" => PageElementSelfRel::class, "from" => "Parent", "to" => "Child", - ) - ); + ] + ]; - private static $belongs_many_many = array( - "Parents" => PageElement::class . ".Children", - "Pages" => "Page.PageSectionMain", - ); + private static $belongs_many_many = [ + "Parents" => PageElement::class . ".Children" + ]; - private static $many_many_extraFields = array( - "Children" => array( + private static $many_many_extraFields = [ + "Children" => [ "SortOrder" => 'Int', - ), - ); + ], + ]; private static $owns = [ - "Children", - ]; + "Children", + ]; - private static $summary_fields = array( + private static $summary_fields = [ "SingularName", "ID", "GridFieldPreview", - ); + ]; - private static $searchable_fields = array( + private static $searchable_fields = [ "ClassName", "Name", "ID", - ); + ]; public static function getAllowedPageElements() { $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); @@ -96,7 +96,7 @@ public static function getAllowedPageElements() { // $count = count($list); // for ($i = 1; $i <= $count; $i++) { - // $this->Children()->Add($list[$i - 1], array("SortOrder" => $i * 2)); + // $this->Children()->Add($list[$i - 1], ["SortOrder" => $i * 2]); // } // } @@ -128,7 +128,7 @@ public function getChildrenGridField() { ->addComponent($addNewButton) ->addComponent(new GridFieldPageSectionsExtension($this->owner)) ->addComponent(new GridFieldDetailForm()); - $dataColumns->setFieldCasting(array("GridFieldPreview" => "HTMLText->RAW")); + $dataColumns->setFieldCasting(["GridFieldPreview" => "HTMLText->RAW"]); return new GridField("Children", "Children", $this->Children(), $config); } @@ -151,7 +151,7 @@ public function getCMSFields() { } public function getParentIDs() { - $IDArr = array($this->ID); + $IDArr = [$this->ID]; foreach($this->Parents() as $parent) { $IDArr = array_merge($IDArr, $parent->getParentIDs()); } @@ -161,7 +161,7 @@ public function getParentIDs() { public function renderChildren($parents = null) { return $this->renderWith( "RenderChildren", - array("Elements" => $this->Children(), "ParentList" => strval($this->ID) . "," . $parents) + ["Elements" => $this->Children(), "ParentList" => strval($this->ID) . "," . $parents] ); } @@ -176,7 +176,7 @@ public function forTemplate($parentList = "") { return $this->renderWith( array_reverse($this->getClassAncestry()), - array("ParentList" => $parentList, "Parents" => $parents, "Page" => $page) + ["ParentList" => $parentList, "Parents" => $parents, "Page" => $page] ); } } diff --git a/code/PageElementSelfRel.php b/src/PageElementSelfRel.php similarity index 67% rename from code/PageElementSelfRel.php rename to src/PageElementSelfRel.php index b2c3755..f9300e9 100644 --- a/code/PageElementSelfRel.php +++ b/src/PageElementSelfRel.php @@ -1,12 +1,12 @@ "Int", diff --git a/code/PageSectionPageElementRel.php b/src/PageSectionPageElementRel.php similarity index 64% rename from code/PageSectionPageElementRel.php rename to src/PageSectionPageElementRel.php index d31e95f..7f1d590 100644 --- a/code/PageSectionPageElementRel.php +++ b/src/PageSectionPageElementRel.php @@ -1,22 +1,22 @@ "Int", - ); + ]; - private static $has_one = array( + private static $has_one = [ "PageSection" => "Page", "Element" => PageElement::class, - ); + ]; public function onBeforeWrite() { diff --git a/code/PageSectionsExtension.php b/src/PageSectionsExtension.php old mode 100644 new mode 100755 similarity index 84% rename from code/PageSectionsExtension.php rename to src/PageSectionsExtension.php index 81a5114..64bb297 --- a/code/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -1,6 +1,6 @@ get($class, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); - if (!$sections) $sections = array("Main"); + if (!$sections) $sections = ["Main"]; foreach ($sections as $section) { $name = "PageSection".$section; - // $many_many[$name] = PageElement::class; - $many_many[$name] = array( + $many_many[$name] = [ "through" => PageSectionPageElementRel::class, "from" => "PageSection", "to" => "Element", - ); + ]; $owns[] = $name; + $cascade_deletes[] = $name; } // Create the relations for our sections - return array( + return [ "many_many" => $many_many, "owns" => $owns, - ); + "cascade_deletes" => $cascade_deletes, + ]; } public static function getAllowedPageElements() { @@ -56,7 +60,7 @@ public static function getAllowedPageElements() { // parent::onBeforeWrite(); // $sections = $this->owner->config()->get("page_sections"); - // if (!$sections) $sections = array("Main"); + // if (!$sections) $sections = ["Main"]; // foreach ($sections as $section) { // $name = "PageSection".$section; @@ -66,14 +70,14 @@ public static function getAllowedPageElements() { // $count = count($list); // for ($i = 1; $i <= $count; $i++) { - // $this->owner->$name()->Add($list[$i - 1], array("SortOrder" => $i * 2)); + // $this->owner->$name()->Add($list[$i - 1], ["SortOrder" => $i * 2]); // } // } // } public function updateCMSFields(FieldList $fields) { $sections = $this->owner->config()->get("page_sections"); - if (!$sections) $sections = array("Main"); + if (!$sections) $sections = ["Main"]; foreach ($sections as $section) { $name = "PageSection".$section; @@ -95,7 +99,7 @@ public function updateCMSFields(FieldList $fields) { ->addComponent($addNewButton) ->addComponent(new GridFieldPageSectionsExtension($this->owner)) ->addComponent(new GridFieldDetailForm()); - $dataColumns->setFieldCasting(array("GridFieldPreview" => "HTMLText->RAW")); + $dataColumns->setFieldCasting(["GridFieldPreview" => "HTMLText->RAW"]); $f = new GridField($name, $section, $this->owner->$name(), $config); $fields->addFieldToTab("Root.PageSections", $f); @@ -107,7 +111,7 @@ public function RenderPageSection($name = "Main") { $elements = $this->owner->{"PageSection" . $name}()->Sort("SortOrder"); return $this->owner->renderWith( "RenderChildren", - array("Elements" => $elements, "ParentList" => strval($this->owner->ID)) + ["Elements" => $elements, "ParentList" => strval($this->owner->ID)] ); } } diff --git a/templates/FlxLabs/PageSections/PageElement.ss b/templates/FLXLabs/PageSections/PageElement.ss old mode 100644 new mode 100755 similarity index 100% rename from templates/FlxLabs/PageSections/PageElement.ss rename to templates/FLXLabs/PageSections/PageElement.ss diff --git a/templates/GridFieldDragHandle.ss b/templates/GridFieldDragHandle.ss old mode 100644 new mode 100755 diff --git a/templates/GridFieldPageElement.ss b/templates/GridFieldPageElement.ss old mode 100644 new mode 100755 diff --git a/templates/Layout/PageElement.ss b/templates/Layout/PageElement.ss old mode 100644 new mode 100755 diff --git a/templates/RenderChildren.ss b/templates/RenderChildren.ss old mode 100644 new mode 100755 From 53d48de835d60aedefe0da1bc0c718629f8d740a Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Mon, 4 Jun 2018 16:32:08 +0200 Subject: [PATCH 05/82] feat(page-sections): Add page section class to fix polymorphic issues --- _config/config.yml | 3 + src/GridFieldPageSectionsExtension.php | 4 +- src/PageElement.php | 55 ++++++++++------ src/PageElementSelfRel.php | 30 +++++++++ src/PageSection.php | 88 ++++++++++++++++++++++++++ src/PageSectionPageElementRel.php | 78 ++++++++++++++--------- src/PageSectionsExtension.php | 57 +++++++++-------- 7 files changed, 238 insertions(+), 77 deletions(-) create mode 100644 src/PageSection.php diff --git a/_config/config.yml b/_config/config.yml index 3fcddeb..43046fb 100755 --- a/_config/config.yml +++ b/_config/config.yml @@ -4,6 +4,9 @@ Name: pagesections FLXLabs\PageSections\PageElement: extensions: - SilverStripe\Versioned\Versioned +FLXLabs\PageSections\PageSection: + extensions: + - SilverStripe\Versioned\Versioned FLXLabs\PageSections\PageElementSelfRel: extensions: - SilverStripe\Versioned\Versioned diff --git a/src/GridFieldPageSectionsExtension.php b/src/GridFieldPageSectionsExtension.php index 731e66a..0a849c2 100755 --- a/src/GridFieldPageSectionsExtension.php +++ b/src/GridFieldPageSectionsExtension.php @@ -357,16 +357,16 @@ public function handleReorder(GridField $gridField, HTTPRequest $request) { if ($type == "child") { if ($newParent) { $newParent->Children()->Add($item, $sortArr); + $newParent->write(); } else { $gridField->getList()->Add($item, $sortArr); } } else { if ($newParent) { $newParent->Children()->Add($item, $sortArr); - $newParent->writeWithoutVersion(); + $newParent->write(); } else { $gridField->getList()->Add($item, $sortArr); - $this->getPage()->writeWithoutVersion(); } } diff --git a/src/PageElement.php b/src/PageElement.php index e511efc..a46adc7 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -45,6 +45,7 @@ function canCreate($member = null, $context = []) { return true; } private static $db = [ "Name" => "Varchar(255)", + "__Counter" => "Int" ]; private static $many_many = [ @@ -56,7 +57,8 @@ function canCreate($member = null, $context = []) { return true; } ]; private static $belongs_many_many = [ - "Parents" => PageElement::class . ".Children" + "Parents" => PageElement::class . ".Children", + "PageSections" => PageSection::class . ".Elements", ]; private static $many_many_extraFields = [ @@ -69,6 +71,10 @@ function canCreate($member = null, $context = []) { return true; } "Children", ]; + private static $cascade_deletes = [ + "Children", + ]; + private static $summary_fields = [ "SingularName", "ID", @@ -88,29 +94,37 @@ public static function getAllowedPageElements() { return $classes; } - // public function onBeforeWrite() { - // parent::onBeforeWrite(); + public function onBeforeWrite() { + parent::onBeforeWrite(); - // $list = $this->Children()->Sort("SortOrder")->toArray(); - // $this->Children()->RemoveAll(); - // $count = count($list); - - // for ($i = 1; $i <= $count; $i++) { - // $this->Children()->Add($list[$i - 1], ["SortOrder" => $i * 2]); - // } - // } + $elems = $this->Children()->Sort("SortOrder")->Column("ID"); + $count = count($elems); + for ($i = 0; $i < $count; $i++) { + $this->Children()->Add($elems[$i], [ "SortOrder" => ($i + 1) * 2, "__NewOrder" => true ]); + } + } - // public function onAfterWrite() { - // $stage = Versioned::get_stage(); + public function onAfterWrite() { + parent::onAfterWrite(); - // foreach ($this->Parents() as $parent) { - // $parent->copyVersionToStage($stage, $stage, true); - // } + if (Versioned::get_stage() == Versioned::DRAFT) { + foreach ($this->PageSections() as $section) { + $section->__Counter++; + $section->write(); + } + } + } - // foreach ($this->Pages() as $page) { - // $page->copyVersionToStage($stage, $stage, true); - // } - // } + public function onAfterDelete() { + parent::onAfterDelete(); + + if (Versioned::get_stage() == Versioned::DRAFT) { + foreach ($this->PageSections() as $section) { + $section->__Counter++; + $section->write(); + } + } + } public function getChildrenGridField() { $addNewButton = new GridFieldAddNewMultiClass(); @@ -141,6 +155,7 @@ public function getCMSFields() { $fields = parent::getCMSFields(); $fields->removeByName('Pages'); $fields->removeByName('Parents'); + $fields->removeByName('__Counter'); $fields->removeByName("Children"); if ($this->ID && count(static::getAllowedPageElements())) { diff --git a/src/PageElementSelfRel.php b/src/PageElementSelfRel.php index f9300e9..dd67661 100644 --- a/src/PageElementSelfRel.php +++ b/src/PageElementSelfRel.php @@ -3,6 +3,7 @@ namespace FLXLabs\PageSections; use SilverStripe\ORM\DataObject; +use SilverStripe\Versioned\Versioned; class PageElementSelfRel extends DataObject { @@ -16,4 +17,33 @@ class PageElementSelfRel extends DataObject { "Parent" => PageElement::class, "Child" => PageElement::class, ); + + public function onBeforeWrite() { + parent::onBeforeWrite(); + + if (!$this->ID) { + if (!$this->SortOrder) { + // Add new elements at the end (highest SortOrder) + $this->SortOrder = ($this->Parent()->Children()->Count() + 1) * 2; + } + } + } + + public function onAfterWrite() { + parent::onAfterWrite(); + + if (!$this->__NewOrder && Versioned::get_stage() == Versioned::DRAFT) { + $this->Parent()->__Counter++; + $this->Parent()->write(); + } + } + + public function onAfterDelete() { + parent::onAfterDelete(); + + if (Versioned::get_stage() == Versioned::DRAFT) { + $this->Parent()->__Counter++; + $this->Parent()->write(); + } + } } diff --git a/src/PageSection.php b/src/PageSection.php new file mode 100644 index 0000000..dd3e489 --- /dev/null +++ b/src/PageSection.php @@ -0,0 +1,88 @@ + "Int", + ]; + + private static $has_one = [ + "Page" => SiteTree::class, + ]; + + private static $owns = [ + "Elements", + ]; + + private static $cascade_deletes = [ + "Elements", + ]; + + private static $many_many = [ + "Elements" => [ + "through" => PageSectionPageElementRel::class, + "from" => "PageSection", + "to" => "Element" + ] + ]; + + public function onBeforeWrite() { + parent::onBeforeWrite(); + + $elems = $this->Elements()->Sort("SortOrder")->Column("ID"); + $count = count($elems); + for ($i = 0; $i < $count; $i++) { + $this->Elements()->Add($elems[$i], [ "SortOrder" => ($i + 1) * 2, "__NewOrder" => true ]); + } + } + + public function onAfterWrite() { + parent::onAfterWrite(); + + if (Versioned::get_stage() == Versioned::DRAFT) { + $this->Page()->__PageSectionCounter++; + $this->Page()->write(); + } + } + + public function forTemplate() { + return $this->Elements()->Count(); + + $actions = FieldList::create(); + $fields = FieldList::create(); + $form = Form::create(null, "Form", $fields, $actions); + + $config = GridFieldConfig::create() + ->addComponent(new GridFieldToolbarHeader()) + ->addComponent($dataColumns = new GridFieldDataColumns()) + ->addComponent(new GridFieldPageSectionsExtension($this->owner)); + $dataColumns->setFieldCasting(["GridFieldPreview" => "HTMLText->RAW"]); + + $grid = GridField::create("Elements", "Elements", $this->Elements(), $config); + $grid->setForm($form); + $fields->add($grid); + + $form->setFields($fields); + + return $form->forTemplate(); + } +} diff --git a/src/PageSectionPageElementRel.php b/src/PageSectionPageElementRel.php index 7f1d590..bd4a867 100644 --- a/src/PageSectionPageElementRel.php +++ b/src/PageSectionPageElementRel.php @@ -1,29 +1,49 @@ - "Int", - ]; - - private static $has_one = [ - "PageSection" => "Page", - "Element" => PageElement::class, - ]; - - - public function onBeforeWrite() { - parent::onBeforeWrite(); - - if (!$this->ID && !$this->SortOrder) { - $this->SortOrder = 1337; - } - } -} + "Int", + ); + + private static $has_one = array( + "PageSection" => PageSection::class, + "Element" => PageElement::class, + ); + + public function onBeforeWrite() { + parent::onBeforeWrite(); + + if (!$this->ID) { + if (!$this->SortOrder) { + // Add new elements at the end (highest SortOrder) + $this->SortOrder = ($this->PageSection()->Elements()->Count() + 1) * 2; + } + } + } + + public function onAfterWrite() { + parent::onAfterWrite(); + + if (!$this->__NewOrder && Versioned::get_stage() == Versioned::DRAFT) { + $this->PageSection()->__Counter++; + $this->PageSection()->write(); + } + } + + public function onAfterDelete() { + parent::onAfterDelete(); + + if (Versioned::get_stage() == Versioned::DRAFT) { + $this->PageSection()->__Counter++; + $this->PageSection()->write(); + } + } +} diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 64bb297..ecf3c46 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -22,21 +22,19 @@ class PageSectionsExtension extends DataExtension { // Generate the needed relations on the class public static function get_extra_config($class = null, $extensionClass = null) { - $many_many = []; + $has_one = []; $owns = []; $cascade_deletes = []; // Get all the sections that should be added $sections = Config::inst()->get($class, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); - if (!$sections) $sections = ["Main"]; + if (!$sections) { + $sections = ["Main"]; + } foreach ($sections as $section) { $name = "PageSection".$section; - $many_many[$name] = [ - "through" => PageSectionPageElementRel::class, - "from" => "PageSection", - "to" => "Element", - ]; + $has_one[$name] = PageSection::class; $owns[] = $name; $cascade_deletes[] = $name; @@ -44,7 +42,8 @@ public static function get_extra_config($class = null, $extensionClass = null) { // Create the relations for our sections return [ - "many_many" => $many_many, + "db" => ["__PageSectionCounter" => "Int"], + "has_one" => $has_one, "owns" => $owns, "cascade_deletes" => $cascade_deletes, ]; @@ -56,28 +55,34 @@ public static function getAllowedPageElements() { return $classes; } - // public function onBeforeWrite() { - // parent::onBeforeWrite(); - - // $sections = $this->owner->config()->get("page_sections"); - // if (!$sections) $sections = ["Main"]; + public function onBeforeWrite() { + parent::onBeforeWrite(); - // foreach ($sections as $section) { - // $name = "PageSection".$section; + if ($this->owner->ID) { + $sections = $this->owner->config()->get("page_sections"); + if (!$sections) { + $sections = ["Main"]; + } - // $list = $this->owner->$name()->Sort("SortOrder")->toArray(); - // $this->owner->$name()->RemoveAll(); - // $count = count($list); + foreach ($sections as $section) { + $name = "PageSection".$section; - // for ($i = 1; $i <= $count; $i++) { - // $this->owner->$name()->Add($list[$i - 1], ["SortOrder" => $i * 2]); - // } - // } - // } + // Create a page section if we don't have one yet + if (!$this->owner->$name()->ID) { + $section = PageSection::create(); + $section->PageID = $this->owner->ID; + $section->write(); + $this->owner->$name = $section; + } + } + } + } public function updateCMSFields(FieldList $fields) { $sections = $this->owner->config()->get("page_sections"); - if (!$sections) $sections = ["Main"]; + if (!$sections) { + $sections = ["Main"]; + } foreach ($sections as $section) { $name = "PageSection".$section; @@ -101,14 +106,14 @@ public function updateCMSFields(FieldList $fields) { ->addComponent(new GridFieldDetailForm()); $dataColumns->setFieldCasting(["GridFieldPreview" => "HTMLText->RAW"]); - $f = new GridField($name, $section, $this->owner->$name(), $config); + $f = new GridField($name, $section, $this->owner->$name()->Elements(), $config); $fields->addFieldToTab("Root.PageSections", $f); } } } public function RenderPageSection($name = "Main") { - $elements = $this->owner->{"PageSection" . $name}()->Sort("SortOrder"); + $elements = $this->owner->{"PageSection" . $name}()->Elements()->Sort("SortOrder"); return $this->owner->renderWith( "RenderChildren", ["Elements" => $elements, "ParentList" => strval($this->owner->ID)] From a886bb20f778f6d0524b702cd5d90b2a78e7da9d Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Mon, 2 Jul 2018 16:05:04 +0200 Subject: [PATCH 06/82] fix(gridfield): Fix dragging with multiple sections not working --- examples/ImageElement.php_ | 38 ++++++++++---------- examples/TextElement.php_ | 34 +++++++++--------- javascript/GridFieldPageSectionsExtension.js | 4 +-- src/GridFieldPageSectionsExtension.php | 2 +- src/PageElement.php | 6 ++-- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/examples/ImageElement.php_ b/examples/ImageElement.php_ index 1e0cf13..ffe665a 100755 --- a/examples/ImageElement.php_ +++ b/examples/ImageElement.php_ @@ -7,23 +7,23 @@ use FLXLabs\PageSections\PageElement; class ImageElement extends PageElement { protected static $singularName = 'Image'; protected static $pluralName = 'Images'; - - private static $has_one = array( - 'Image' => Image::class, - ); - - public function getCMSFields(){ - $fields = parent::getCMSFields(); - $fields->removeByName('Children'); - - $fields->addFieldToTab( - 'Root.Main', - $uploadField = new UploadField( - $name = 'Image', - $title = 'Upload an image' - ) - ); - - return $fields; - } + + private static $has_one = array( + 'Image' => Image::class, + ); + + public function getCMSFields(){ + $fields = parent::getCMSFields(); + $fields->removeByName('Children'); + + $fields->addFieldToTab( + 'Root.Main', + $uploadField = new UploadField( + $name = 'Image', + $title = 'Upload an image' + ) + ); + + return $fields; + } } diff --git a/examples/TextElement.php_ b/examples/TextElement.php_ index 3ceaecf..9f69ed1 100755 --- a/examples/TextElement.php_ +++ b/examples/TextElement.php_ @@ -5,23 +5,23 @@ use FLXLabs\PageSections\PageElement; class TextElement extends PageElement { protected static $singularName = 'Text'; protected static $pluralName = 'Texts'; - - private static $db = array( - 'Content' => 'HTMLText', - ); - - public function getCMSFields() { - $fields = parent::getCMSFields(); - $fields->removeByName('Children'); - - return $fields; - } + + private static $db = array( + 'Content' => 'HTMLText', + ); + + public function getCMSFields() { + $fields = parent::getCMSFields(); + $fields->removeByName('Children'); + + return $fields; + } - public static function getAllowedPageElements() { - return array(); - } + public static function getAllowedPageElements() { + return array(); + } - public function getGridFieldPreview() { - return $this->Content; - } + public function getGridFieldPreview() { + return $this->Content; + } } diff --git a/javascript/GridFieldPageSectionsExtension.js b/javascript/GridFieldPageSectionsExtension.js index fdd9234..c6f9332 100755 --- a/javascript/GridFieldPageSectionsExtension.js +++ b/javascript/GridFieldPageSectionsExtension.js @@ -67,8 +67,9 @@ var grid = this.getGridField(); var thisGrid = this; - $("tr.ss-gridfield-item").each(function () { + this.find("tr.ss-gridfield-item").each(function () { var $this = $(this); + // actions $this.find(".col-actions .add-button").click(function (event) { event.preventDefault(); @@ -215,7 +216,6 @@ tolerance: "pointer", greedy: true, helper: function () { - var $tr = $this.parents("tr.ss-gridfield-item"); var $helper = $( "
" + $this.find(".col-treenav__title").text() + diff --git a/src/GridFieldPageSectionsExtension.php b/src/GridFieldPageSectionsExtension.php index 65739d6..2f2b4f5 100755 --- a/src/GridFieldPageSectionsExtension.php +++ b/src/GridFieldPageSectionsExtension.php @@ -69,7 +69,7 @@ public function getHTMLFragments($field) { Requirements::add_i18n_javascript($moduleDir . '/javascript/lang', false, true); $id = rand(1000000, 9999999); - $field->addExtraClass("ss-gridfield-pagesections"); + $field->addExtraClass("ss-gridfield-pagesections pagesection-" . $id); $field->setAttribute("data-id", $id); $field->setAttribute("data-url-add", $field->Link("add")); $field->setAttribute("data-url-remove", $field->Link("remove")); diff --git a/src/PageElement.php b/src/PageElement.php index 1ed8066..ab7d5e2 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -174,11 +174,11 @@ public function getAllPages() { $page = $section->Page(); $stage = Versioned::get_stage(); Versioned::set_stage(Versioned::LIVE); - $publishedElem = DataObject::get_by_id($section->ClassName, $section->ID) - ->Elements()->filter("ID", $this->ID)->First(); + $pubSection = DataObject::get_by_id($section->ClassName, $section->ID); + $pubElem = $pubSection ? $pubSection->Elements()->filter("ID", $this->ID)->First() : null; $page->__PageSection = $section; $page->__PageElementVersion = $section->Elements()->filter("ID", $this->ID)->First()->Version; - $page->__PageElementPublishedVersion = $publishedElem ? $publishedElem->Version : "Not published"; + $page->__PageElementPublishedVersion = $pubElem ? $pubElem->Version : "Not published"; Versioned::set_stage($stage); $pages->add($page); } From e2bdb0a67b4efd76884a0734877908d2c5e65718 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Mon, 2 Jul 2018 17:10:31 +0200 Subject: [PATCH 07/82] fix(page-element): Fix children gridfield not showing --- src/PageElement.php | 12 ++++++------ src/PageSectionsExtension.php | 7 ++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/PageElement.php b/src/PageElement.php index ab7d5e2..da6f48a 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -6,7 +6,6 @@ use SilverStripe\Core\ClassInfo; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\ReadonlyField; -use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter; use SilverStripe\Forms\GridField\GridFieldConfig; use SilverStripe\Forms\GridField\GridFieldConfig_Base; use SilverStripe\Forms\GridField\GridFieldButtonRow; @@ -22,10 +21,12 @@ use SilverStripe\Versioned\Versioned; use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass; +use Symbiote\GridFieldExtensions\GridFieldAddExistingSearchButton; use UncleCheese\BetterButtons\Actions\PrevNext; use UncleCheese\BetterButtons\Actions\CustomAction; use UncleCheese\BetterButtons\Buttons\Save; use UncleCheese\BetterButtons\Buttons\SaveAndClose; +use SilverStripe\Forms\Tab; class PageElement extends DataObject { @@ -143,8 +144,7 @@ public function getChildrenGridField() { $addNewButton = new GridFieldAddNewMultiClass(); $addNewButton->setClasses($this->getAllowedPageElements()); - $autoCompl = new GridFieldAddExistingAutocompleter('buttons-before-right'); - $autoCompl->setResultsFormat('$Name ($ID)'); + $autoCompl = new GridFieldAddExistingSearchButton('buttons-before-right'); $autoCompl->setSearchList(PageElement::get()->exclude("ID", $this->getParentIDs())); $config = GridFieldConfig::create() @@ -158,7 +158,7 @@ public function getChildrenGridField() { ->addComponent(new GridFieldFooter()); $dataColumns->setFieldCasting(["GridFieldPreview" => "HTMLText->RAW"]); - return new GridField("Children", "Children", $this->Children(), $config); + return GridField::create("Child", "Children", $this->Children(), $config); } public function getGridFieldPreview() { @@ -193,8 +193,8 @@ public function getCMSFields() { $fields->removeByName('__Counter'); $fields->removeByName("Children"); - if ($this->ID && count(static::getAllowedPageElements())) { - $fields->addFieldToTab('Root.PageSections', $this->getChildrenGridField()); + if ($this->ID && count(static::getAllowedPageElements()) > 0) { + $fields->insertAfter("Main", Tab::create("Child", "Children", $this->getChildrenGridField())); } // Add our newest version as a readonly field diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 82734ab..fbfd6a4 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -5,7 +5,6 @@ use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Config; use SilverStripe\Forms\FieldList; -use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter; use SilverStripe\Forms\GridField\GridFieldConfig; use SilverStripe\Forms\GridField\GridFieldButtonRow; use SilverStripe\Forms\GridField\GridFieldToolbarHeader; @@ -18,6 +17,7 @@ use SilverStripe\Versioned\Versioned; use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass; +use Symbiote\GridFieldExtensions\GridFieldAddExistingSearchButton; class PageSectionsExtension extends DataExtension { @@ -108,14 +108,11 @@ public function updateCMSFields(FieldList $fields) { $addNewButton = new GridFieldAddNewMultiClass(); $addNewButton->setClasses($this->owner->getAllowedPageElements()); - $autoCompl = new GridFieldAddExistingAutocompleter('buttons-before-right'); - $autoCompl->setResultsFormat('$Name ($ID)'); - $config = GridFieldConfig::create() ->addComponent(new GridFieldButtonRow("before")) ->addComponent(new GridFieldToolbarHeader()) ->addComponent($dataColumns = new GridFieldDataColumns()) - ->addComponent($autoCompl) + ->addComponent(new GridFieldAddExistingSearchButton('buttons-before-right')) ->addComponent($addNewButton) ->addComponent(new GridFieldPageSectionsExtension($this->owner)) ->addComponent(new GridFieldDetailForm()); From 348478f66964a07060dd17d741768b3f42d0a238 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Thu, 5 Jul 2018 10:45:43 +0200 Subject: [PATCH 08/82] fix(betterbuttons): Use our own better buttons repo for temporary fix --- composer.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/composer.json b/composer.json index 234bea9..4bc3698 100755 --- a/composer.json +++ b/composer.json @@ -13,6 +13,10 @@ "support": { "issues": "http://github.com/flxlabs/silverstripe-pagesections/issues" }, + "repositories": [{ + "type": "vcs", + "url": "https://github.com/flxlabs/silverstripe-gridfield-betterbuttons" + }], "require": { "silverstripe/framework": "^4.0.1", "symbiote/silverstripe-gridfieldextensions": "^3", From 996e0828c17e972dc361abe72980f956067c1ce8 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Thu, 5 Jul 2018 10:45:43 +0200 Subject: [PATCH 09/82] fix(betterbuttons): Use our own better buttons repo for temporary fix --- composer.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 234bea9..b0854a5 100755 --- a/composer.json +++ b/composer.json @@ -13,10 +13,14 @@ "support": { "issues": "http://github.com/flxlabs/silverstripe-pagesections/issues" }, + "repositories": [{ + "type": "vcs", + "url": "https://github.com/flxlabs/silverstripe-gridfield-betterbuttons" + }], "require": { "silverstripe/framework": "^4.0.1", "symbiote/silverstripe-gridfieldextensions": "^3", - "unclecheese/betterbuttons": "dev-feature/ss4-upgrade" + "flxlabs/betterbuttons": "dev-feature/ss4-upgrade" }, "extra": { "installer-name": "pagesections", From 2bc5588851b30928f76e6d1afe688af15ab5d93b Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Thu, 5 Jul 2018 11:44:26 +0200 Subject: [PATCH 10/82] chore(composer): Fix composer file --- composer.json | 2 +- composer.lock | 1903 ------------------------------------------------- 2 files changed, 1 insertion(+), 1904 deletions(-) delete mode 100644 composer.lock diff --git a/composer.json b/composer.json index b0854a5..4bc3698 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "require": { "silverstripe/framework": "^4.0.1", "symbiote/silverstripe-gridfieldextensions": "^3", - "flxlabs/betterbuttons": "dev-feature/ss4-upgrade" + "unclecheese/betterbuttons": "dev-feature/ss4-upgrade" }, "extra": { "installer-name": "pagesections", diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 00113c6..0000000 --- a/composer.lock +++ /dev/null @@ -1,1903 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "40aba9751a6b54c74a81a32dd1fd00ae", - "packages": [ - { - "name": "composer/ca-bundle", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/composer/ca-bundle.git", - "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169", - "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169", - "shasum": "" - }, - "require": { - "ext-openssl": "*", - "ext-pcre": "*", - "php": "^5.3.2 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", - "psr/log": "^1.0", - "symfony/process": "^2.5 || ^3.0 || ^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\CaBundle\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", - "keywords": [ - "cabundle", - "cacert", - "certificate", - "ssl", - "tls" - ], - "time": "2018-03-29T19:57:20+00:00" - }, - { - "name": "composer/installers", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/composer/installers.git", - "reference": "049797d727261bf27f2690430d935067710049c2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/049797d727261bf27f2690430d935067710049c2", - "reference": "049797d727261bf27f2690430d935067710049c2", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0" - }, - "replace": { - "roundcube/plugin-installer": "*", - "shama/baton": "*" - }, - "require-dev": { - "composer/composer": "1.0.*@dev", - "phpunit/phpunit": "^4.8.36" - }, - "type": "composer-plugin", - "extra": { - "class": "Composer\\Installers\\Plugin", - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Installers\\": "src/Composer/Installers" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kyle Robinson Young", - "email": "kyle@dontkry.com", - "homepage": "https://github.com/shama" - } - ], - "description": "A multi-framework Composer library installer", - "homepage": "https://composer.github.io/installers/", - "keywords": [ - "Craft", - "Dolibarr", - "Eliasis", - "Hurad", - "ImageCMS", - "Kanboard", - "Lan Management System", - "MODX Evo", - "Mautic", - "Maya", - "OXID", - "Plentymarkets", - "Porto", - "RadPHP", - "SMF", - "Thelia", - "WolfCMS", - "agl", - "aimeos", - "annotatecms", - "attogram", - "bitrix", - "cakephp", - "chef", - "cockpit", - "codeigniter", - "concrete5", - "croogo", - "dokuwiki", - "drupal", - "eZ Platform", - "elgg", - "expressionengine", - "fuelphp", - "grav", - "installer", - "itop", - "joomla", - "kohana", - "laravel", - "lavalite", - "lithium", - "magento", - "majima", - "mako", - "mediawiki", - "modulework", - "modx", - "moodle", - "osclass", - "phpbb", - "piwik", - "ppi", - "puppet", - "pxcms", - "reindex", - "roundcube", - "shopware", - "silverstripe", - "sydes", - "symfony", - "typo3", - "wordpress", - "yawik", - "zend", - "zikula" - ], - "time": "2017-12-29T09:13:20+00:00" - }, - { - "name": "embed/embed", - "version": "v3.3.2", - "source": { - "type": "git", - "url": "https://github.com/oscarotero/Embed.git", - "reference": "e02f6f2f590cccf13c6cb64c975c32ddc88ae4b9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/oscarotero/Embed/zipball/e02f6f2f590cccf13c6cb64c975c32ddc88ae4b9", - "reference": "e02f6f2f590cccf13c6cb64c975c32ddc88ae4b9", - "shasum": "" - }, - "require": { - "composer/ca-bundle": "^1.0", - "ext-curl": "*", - "ext-mbstring": "*", - "php": "^5.5|^7.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "phpunit/phpunit": "^4.8|^5.7" - }, - "type": "library", - "autoload": { - "psr-4": { - "Embed\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Oscar Otero", - "email": "oom@oscarotero.com", - "homepage": "http://oscarotero.com", - "role": "Developer" - } - ], - "description": "PHP library to retrieve page info using oembed, opengraph, etc", - "homepage": "https://github.com/oscarotero/Embed", - "keywords": [ - "embed", - "embedly", - "oembed", - "opengraph", - "twitter cards" - ], - "time": "2018-05-22T21:00:13+00:00" - }, - { - "name": "guzzlehttp/psr7", - "version": "1.4.2", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Schultze", - "homepage": "https://github.com/Tobion" - } - ], - "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": [ - "http", - "message", - "request", - "response", - "stream", - "uri", - "url" - ], - "time": "2017-03-20T17:10:46+00:00" - }, - { - "name": "intervention/image", - "version": "2.4.2", - "source": { - "type": "git", - "url": "https://github.com/Intervention/image.git", - "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/e82d274f786e3d4b866a59b173f42e716f0783eb", - "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb", - "shasum": "" - }, - "require": { - "ext-fileinfo": "*", - "guzzlehttp/psr7": "~1.1", - "php": ">=5.4.0" - }, - "require-dev": { - "mockery/mockery": "~0.9.2", - "phpunit/phpunit": "^4.8 || ^5.7" - }, - "suggest": { - "ext-gd": "to use GD library based image processing.", - "ext-imagick": "to use Imagick based image processing.", - "intervention/imagecache": "Caching extension for the Intervention Image library" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - }, - "laravel": { - "providers": [ - "Intervention\\Image\\ImageServiceProvider" - ], - "aliases": { - "Image": "Intervention\\Image\\Facades\\Image" - } - } - }, - "autoload": { - "psr-4": { - "Intervention\\Image\\": "src/Intervention/Image" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Oliver Vogel", - "email": "oliver@olivervogel.com", - "homepage": "http://olivervogel.com/" - } - ], - "description": "Image handling and manipulation library with support for Laravel integration", - "homepage": "http://image.intervention.io/", - "keywords": [ - "gd", - "image", - "imagick", - "laravel", - "thumbnail", - "watermark" - ], - "time": "2018-05-29T14:19:03+00:00" - }, - { - "name": "league/flysystem", - "version": "1.0.45", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "a99f94e63b512d75f851b181afcdf0ee9ebef7e6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a99f94e63b512d75f851b181afcdf0ee9ebef7e6", - "reference": "a99f94e63b512d75f851b181afcdf0ee9ebef7e6", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "conflict": { - "league/flysystem-sftp": "<1.0.6" - }, - "require-dev": { - "ext-fileinfo": "*", - "phpspec/phpspec": "^3.4", - "phpunit/phpunit": "^5.7" - }, - "suggest": { - "ext-fileinfo": "Required for MimeType", - "ext-ftp": "Allows you to use FTP server storage", - "ext-openssl": "Allows you to use FTPS server storage", - "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", - "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", - "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", - "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", - "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", - "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", - "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", - "league/flysystem-webdav": "Allows you to use WebDAV storage", - "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", - "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", - "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Flysystem\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } - ], - "description": "Filesystem abstraction: Many filesystems, one API.", - "keywords": [ - "Cloud Files", - "WebDAV", - "abstraction", - "aws", - "cloud", - "copy.com", - "dropbox", - "file systems", - "files", - "filesystem", - "filesystems", - "ftp", - "rackspace", - "remote", - "s3", - "sftp", - "storage" - ], - "time": "2018-05-07T08:44:23+00:00" - }, - { - "name": "m1/env", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/m1/Env.git", - "reference": "3589eae8e40d40be96de39222a6ca19c3af8eae4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/m1/Env/zipball/3589eae8e40d40be96de39222a6ca19c3af8eae4", - "reference": "3589eae8e40d40be96de39222a6ca19c3af8eae4", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*", - "scrutinizer/ocular": "~1.1", - "squizlabs/php_codesniffer": "^2.3" - }, - "suggest": { - "josegonzalez/dotenv": "For loading of .env", - "m1/vars": "For loading of configs" - }, - "type": "library", - "autoload": { - "psr-4": { - "M1\\Env\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Miles Croxford", - "email": "hello@milescroxford.com", - "homepage": "http://milescroxford.com", - "role": "Developer" - } - ], - "description": "Env is a lightweight library bringing .env file parser compatibility to PHP. In short - it enables you to read .env files with PHP.", - "homepage": "https://github.com/m1/Env", - "keywords": [ - ".env", - "config", - "dotenv", - "env", - "loader", - "m1", - "parser", - "support" - ], - "time": "2018-05-16T22:40:58+00:00" - }, - { - "name": "marcj/topsort", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/marcj/topsort.php.git", - "reference": "387086c2db60ee0a27ac5df588c0f0b30c6bdc4b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/marcj/topsort.php/zipball/387086c2db60ee0a27ac5df588c0f0b30c6bdc4b", - "reference": "387086c2db60ee0a27ac5df588c0f0b30c6bdc4b", - "shasum": "" - }, - "require": { - "php": ">=5.4" - }, - "require-dev": { - "codeclimate/php-test-reporter": "dev-master", - "phpunit/phpunit": "~4.0", - "symfony/console": "~2.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "MJS\\TopSort\\": "src/", - "MJS\\TopSort\\Tests\\": "tests/Tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marc J. Schmidt", - "email": "marc@marcjschmidt.de" - } - ], - "description": "High-Performance TopSort/Dependency resolving algorithm", - "keywords": [ - "dependency resolving", - "topological sort", - "topsort" - ], - "time": "2016-11-19T14:58:11+00:00" - }, - { - "name": "monolog/monolog", - "version": "1.23.0", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" - }, - "provide": { - "psr/log-implementation": "1.0.0" - }, - "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", - "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "jakub-onderka/php-parallel-lint": "0.9", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpunit/phpunit": "~4.5", - "phpunit/phpunit-mock-objects": "2.3.0", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "^5.3|^6.0" - }, - "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Monolog\\": "src/Monolog" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", - "keywords": [ - "log", - "logging", - "psr-3" - ], - "time": "2017-06-19T01:22:40+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v3.1.5", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", - "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "~4.0|~5.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "time": "2018-02-28T20:30:58+00:00" - }, - { - "name": "paragonie/random_compat", - "version": "v2.0.12", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb", - "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb", - "shasum": "" - }, - "require": { - "php": ">=5.2.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "type": "library", - "autoload": { - "files": [ - "lib/random.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "pseudorandom", - "random" - ], - "time": "2018-04-04T21:24:14+00:00" - }, - { - "name": "psr/cache", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "time": "2016-08-06T20:24:11+00:00" - }, - { - "name": "psr/container", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "time": "2017-02-14T16:28:37+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/log", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "time": "2016-10-10T12:19:37+00:00" - }, - { - "name": "psr/simple-cache", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\SimpleCache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for simple caching", - "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" - ], - "time": "2017-10-23T01:57:42+00:00" - }, - { - "name": "silverstripe/assets", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/silverstripe/silverstripe-assets.git", - "reference": "b0d44023353254758d35d0e7ee26f634e7493fab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silverstripe/silverstripe-assets/zipball/b0d44023353254758d35d0e7ee26f634e7493fab", - "reference": "b0d44023353254758d35d0e7ee26f634e7493fab", - "shasum": "" - }, - "require": { - "intervention/image": "^2.3", - "php": ">=5.6.0", - "silverstripe/framework": "^4.1", - "silverstripe/vendor-plugin": "^1.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7", - "silverstripe/versioned": "^1@dev" - }, - "type": "silverstripe-vendormodule", - "extra": { - "branch-alias": { - "1.x-dev": "1.2.x-dev", - "dev-master": "2.x-dev" - }, - "installer-name": "silverstripe-assets" - }, - "autoload": { - "psr-4": { - "SilverStripe\\Assets\\": "src/", - "SilverStripe\\Assets\\Tests\\": "tests/php/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "SilverStripe", - "homepage": "http://silverstripe.com" - }, - { - "name": "The SilverStripe Community", - "homepage": "http://silverstripe.org" - } - ], - "description": "SilverStripe Assets component", - "homepage": "http://silverstripe.org", - "keywords": [ - "assets", - "silverstripe" - ], - "time": "2018-05-24T03:24:57+00:00" - }, - { - "name": "silverstripe/config", - "version": "1.0.4", - "source": { - "type": "git", - "url": "https://github.com/silverstripe/silverstripe-config.git", - "reference": "65cc33edc4d88d20cab5b7ad4b6f52bd20747e98" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silverstripe/silverstripe-config/zipball/65cc33edc4d88d20cab5b7ad4b6f52bd20747e98", - "reference": "65cc33edc4d88d20cab5b7ad4b6f52bd20747e98", - "shasum": "" - }, - "require": { - "marcj/topsort": "^1.0", - "psr/simple-cache": "^1.0", - "symfony/finder": "^2.8 || ^3.2", - "symfony/yaml": "^2.8 || ^3.2" - }, - "require-dev": { - "mikey179/vfsstream": "^1.6", - "phpspec/prophecy": "^1.0", - "phpunit/phpunit": "^5.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "SilverStripe\\Config\\": "src/", - "SilverStripe\\Config\\Tests\\": "tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "SilverStripe configuration based on YAML and class statics", - "time": "2018-02-14T20:07:02+00:00" - }, - { - "name": "silverstripe/framework", - "version": "4.1.1", - "source": { - "type": "git", - "url": "https://github.com/silverstripe/silverstripe-framework.git", - "reference": "01ed8a316b65c7d2dddecec39c0195a0e3de07b2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silverstripe/silverstripe-framework/zipball/01ed8a316b65c7d2dddecec39c0195a0e3de07b2", - "reference": "01ed8a316b65c7d2dddecec39c0195a0e3de07b2", - "shasum": "" - }, - "require": { - "composer/installers": "~1.0", - "embed/embed": "^3.0", - "ext-ctype": "*", - "ext-dom": "*", - "ext-hash": "*", - "ext-intl": "*", - "ext-mbstring": "*", - "ext-session": "*", - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xml": "*", - "league/flysystem": "~1.0.12", - "m1/env": "^2.1", - "monolog/monolog": "~1.11", - "nikic/php-parser": "^2 || ^3", - "paragonie/random_compat": "^2.0", - "php": ">=5.6.0", - "psr/container": "1.0.0", - "psr/container-implementation": "1.0.0", - "silverstripe/assets": "^1@dev", - "silverstripe/config": "^1@dev", - "silverstripe/vendor-plugin": "^1.0", - "swiftmailer/swiftmailer": "~5.4", - "symfony/cache": "^3.3@dev", - "symfony/config": "^3.2", - "symfony/translation": "^2.8", - "symfony/yaml": "~3.2" - }, - "provide": { - "psr/container-implementation": "1.0.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7", - "se/selenium-server-standalone": "2.41.0", - "silverstripe/behat-extension": "^3", - "silverstripe/serve": "^2@dev", - "silverstripe/versioned": "^1@dev" - }, - "bin": [ - "sake" - ], - "type": "silverstripe-vendormodule", - "extra": { - "branch-alias": { - "4.x-dev": "4.2.x-dev", - "dev-master": "5.x-dev" - }, - "expose": [ - "client/images", - "client/styles", - "src/Dev/Install/client" - ] - }, - "autoload": { - "psr-4": { - "SilverStripe\\Control\\": "src/Control/", - "SilverStripe\\Control\\Tests\\": "tests/php/Control/", - "SilverStripe\\Core\\": "src/Core/", - "SilverStripe\\Core\\Tests\\": "tests/php/Core/", - "SilverStripe\\Dev\\": "src/Dev/", - "SilverStripe\\Dev\\Tests\\": "tests/php/Dev/", - "SilverStripe\\Forms\\": "src/Forms/", - "SilverStripe\\Forms\\Tests\\": "tests/php/Forms/", - "SilverStripe\\i18n\\": "src/i18n/", - "SilverStripe\\i18n\\Tests\\": "tests/php/i18n/", - "SilverStripe\\Logging\\": "src/Logging/", - "SilverStripe\\Logging\\Tests\\": "tests/php/Logging/", - "SilverStripe\\ORM\\": "src/ORM/", - "SilverStripe\\ORM\\Tests\\": "tests/php/ORM/", - "SilverStripe\\Security\\": "src/Security/", - "SilverStripe\\Security\\Tests\\": "tests/php/Security/", - "SilverStripe\\View\\": "src/View/", - "SilverStripe\\View\\Tests\\": "tests/php/View/", - "SilverStripe\\Framework\\Tests\\Behaviour\\": "tests/behat/src/" - }, - "files": [ - "src/includes/constants.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "src/", - "src/includes/", - "thirdparty/" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "SilverStripe", - "homepage": "http://silverstripe.com" - }, - { - "name": "The SilverStripe Community", - "homepage": "http://silverstripe.org" - } - ], - "description": "The SilverStripe framework", - "homepage": "http://silverstripe.org", - "keywords": [ - "framework", - "silverstripe" - ], - "time": "2018-05-24T04:52:15+00:00" - }, - { - "name": "silverstripe/vendor-plugin", - "version": "1.3.3", - "source": { - "type": "git", - "url": "https://github.com/silverstripe/vendor-plugin.git", - "reference": "ec27b75cc67adc31c458e8ccd91e787a9534c15c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silverstripe/vendor-plugin/zipball/ec27b75cc67adc31c458e8ccd91e787a9534c15c", - "reference": "ec27b75cc67adc31c458e8ccd91e787a9534c15c", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1", - "composer/installers": "^1.4" - }, - "require-dev": { - "composer/composer": "^1.5", - "phpunit/phpunit": "^5.7" - }, - "type": "composer-plugin", - "extra": { - "class": "SilverStripe\\VendorPlugin\\VendorPlugin", - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-4": { - "SilverStripe\\VendorPlugin\\": "src/", - "SilverStripe\\VendorPlugin\\Tests\\": "tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Damian Mooyman", - "email": "damian@silverstripe.com" - } - ], - "description": "Allows vendor modules to expose directories to the webroot", - "time": "2018-03-25T21:50:30+00:00" - }, - { - "name": "swiftmailer/swiftmailer", - "version": "v5.4.9", - "source": { - "type": "git", - "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "7ffc1ea296ed14bf8260b6ef11b80208dbadba91" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/7ffc1ea296ed14bf8260b6ef11b80208dbadba91", - "reference": "7ffc1ea296ed14bf8260b6ef11b80208dbadba91", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "mockery/mockery": "~0.9.1", - "symfony/phpunit-bridge": "~3.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.4-dev" - } - }, - "autoload": { - "files": [ - "lib/swift_required.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Chris Corbyn" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "https://swiftmailer.symfony.com", - "keywords": [ - "email", - "mail", - "mailer" - ], - "time": "2018-01-23T07:37:21+00:00" - }, - { - "name": "symbiote/silverstripe-gridfieldextensions", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/symbiote/silverstripe-gridfieldextensions.git", - "reference": "6d8b41ee5a0b0f1b07215f5bf146c07a78ef61fe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symbiote/silverstripe-gridfieldextensions/zipball/6d8b41ee5a0b0f1b07215f5bf146c07a78ef61fe", - "reference": "6d8b41ee5a0b0f1b07215f5bf146c07a78ef61fe", - "shasum": "" - }, - "require": { - "silverstripe/framework": "~4.0", - "silverstripe/vendor-plugin": "^1.0" - }, - "replace": { - "ajshort/silverstripe-gridfieldextensions": "self.version", - "silverstripe-australia/gridfieldextensions": "self.version" - }, - "require-dev": { - "phpunit/phpunit": "^5.7", - "silverstripe/versioned": "^1@dev", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "silverstripe-vendormodule", - "extra": { - "installer-name": "gridfieldextensions", - "screenshots": [ - "docs/en/_images/editable-rows.png", - "docs/en/_images/add-existing-search.png" - ], - "expose": [ - "css", - "javascript" - ] - }, - "autoload": { - "psr-4": { - "Symbiote\\GridFieldExtensions\\": "src/", - "Symbiote\\GridFieldExtensions\\Tests\\": "tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Andrew Short", - "email": "andrewjshort@gmail.com" - }, - { - "name": "Marcus Nyeholt", - "email": "marcus@symbiote.com.au" - } - ], - "description": "A collection of useful grid field components", - "homepage": "http://github.com/symbiote/silverstripe-gridfieldextensions", - "keywords": [ - "gridfield", - "silverstripe" - ], - "time": "2018-02-11T22:08:47+00:00" - }, - { - "name": "symfony/cache", - "version": "v3.4.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "b6f157d4529a3484f60ebc40661b5232526fb432" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/b6f157d4529a3484f60ebc40661b5232526fb432", - "reference": "b6f157d4529a3484f60ebc40661b5232526fb432", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "psr/cache": "~1.0", - "psr/log": "~1.0", - "psr/simple-cache": "^1.0", - "symfony/polyfill-apcu": "~1.1" - }, - "conflict": { - "symfony/var-dumper": "<3.3" - }, - "provide": { - "psr/cache-implementation": "1.0", - "psr/simple-cache-implementation": "1.0" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/cache": "~1.6", - "doctrine/dbal": "~2.4", - "predis/predis": "~1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Cache\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Cache component with PSR-6, PSR-16, and tags", - "homepage": "https://symfony.com", - "keywords": [ - "caching", - "psr6" - ], - "time": "2018-05-16T12:49:49+00:00" - }, - { - "name": "symfony/config", - "version": "v3.4.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "73e055cf2e6467715f187724a0347ea32079967c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/73e055cf2e6467715f187724a0347ea32079967c", - "reference": "73e055cf2e6467715f187724a0347ea32079967c", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/filesystem": "~2.8|~3.0|~4.0", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/dependency-injection": "<3.3", - "symfony/finder": "<3.3" - }, - "require-dev": { - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/event-dispatcher": "~3.3|~4.0", - "symfony/finder": "~3.3|~4.0", - "symfony/yaml": "~3.0|~4.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Config Component", - "homepage": "https://symfony.com", - "time": "2018-05-14T16:49:53+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v3.4.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "8e03ca3fa52a0f56b87506f38cf7bd3f9442b3a0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/8e03ca3fa52a0f56b87506f38cf7bd3f9442b3a0", - "reference": "8e03ca3fa52a0f56b87506f38cf7bd3f9442b3a0", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Filesystem Component", - "homepage": "https://symfony.com", - "time": "2018-05-16T08:49:21+00:00" - }, - { - "name": "symfony/finder", - "version": "v3.4.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "472a92f3df8b247b49ae364275fb32943b9656c6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/472a92f3df8b247b49ae364275fb32943b9656c6", - "reference": "472a92f3df8b247b49ae364275fb32943b9656c6", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Finder Component", - "homepage": "https://symfony.com", - "time": "2018-05-16T08:49:21+00:00" - }, - { - "name": "symfony/polyfill-apcu", - "version": "v1.8.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-apcu.git", - "reference": "9b83bd010112ec196410849e840d9b9fefcb15ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/9b83bd010112ec196410849e840d9b9fefcb15ad", - "reference": "9b83bd010112ec196410849e840d9b9fefcb15ad", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Apcu\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "apcu", - "compatibility", - "polyfill", - "portable", - "shim" - ], - "time": "2018-04-26T10:06:28+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.8.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "time": "2018-04-30T19:57:29+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.8.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "3296adf6a6454a050679cde90f95350ad604b171" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", - "reference": "3296adf6a6454a050679cde90f95350ad604b171", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "time": "2018-04-26T10:06:28+00:00" - }, - { - "name": "symfony/translation", - "version": "v2.8.41", - "source": { - "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "c6a27966a92fa361bf2c3a938abc6dee91f7ad67" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/c6a27966a92fa361bf2c3a938abc6dee91f7ad67", - "reference": "c6a27966a92fa361bf2c3a938abc6dee91f7ad67", - "shasum": "" - }, - "require": { - "php": ">=5.3.9", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/config": "<2.7" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.8", - "symfony/intl": "~2.7.25|^2.8.18|~3.2.5", - "symfony/yaml": "~2.2|~3.0.0" - }, - "suggest": { - "psr/log-implementation": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Translation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Translation Component", - "homepage": "https://symfony.com", - "time": "2018-05-21T09:59:10+00:00" - }, - { - "name": "symfony/yaml", - "version": "v3.4.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "c5010cc1692ce1fa328b1fb666961eb3d4a85bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/c5010cc1692ce1fa328b1fb666961eb3d4a85bb0", - "reference": "c5010cc1692ce1fa328b1fb666961eb3d4a85bb0", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/console": "<3.4" - }, - "require-dev": { - "symfony/console": "~3.4|~4.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2018-05-03T23:18:14+00:00" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": [], - "platform-dev": [] -} From aed0e3a41fc328c20d907f9409947e69c717bb64 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Thu, 5 Jul 2018 12:17:58 +0200 Subject: [PATCH 11/82] fix(sections): Minor fixes for page sections --- src/PageElement.php | 4 +--- src/PageSection.php | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/PageElement.php b/src/PageElement.php index da6f48a..04592b4 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -190,6 +190,7 @@ public function getCMSFields() { $fields = parent::getCMSFields(); $fields->removeByName('Pages'); $fields->removeByName('Parents'); + $fields->removeByName("PageSections"); $fields->removeByName('__Counter'); $fields->removeByName("Children"); @@ -208,9 +209,6 @@ public function getCMSFields() { $pages = $this->getAllPages(); if ($pages->Count() > 0) { - // Remove default field - $fields->removeByName("PageSections"); - $config = GridFieldConfig_Base::create() ->removeComponentsByType(GridFieldDataColumns::class) ->addComponent($dataColumns = new GridFieldDataColumns()) diff --git a/src/PageSection.php b/src/PageSection.php index e47e718..c1973d0 100644 --- a/src/PageSection.php +++ b/src/PageSection.php @@ -89,6 +89,10 @@ public function forTemplate() { // Gets the name of this section from the page it is on public function getName() { $page = $this->Page(); + // TODO: Find out why this happens + if (!method_exists($page, "getPageSectionNames")) { + return null; + } foreach ($page->getPageSectionNames() as $sectionName) { if ($page->{"PageSection" . $sectionName . "ID"} === $this->ID) { return $sectionName; From 87cba11307e0253e5798f4d999ee86a80cfc744a Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Thu, 5 Jul 2018 14:46:53 +0200 Subject: [PATCH 12/82] fix(buttons): Correctly override better buttons --- src/PageElement.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PageElement.php b/src/PageElement.php index 04592b4..b3e32b8 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -36,6 +36,8 @@ class PageElement extends DataObject { protected static $pluralName = "Elements"; protected static $defaultIsOpen = true; + public static $overrideBetterButtons = true; + public static function getSingularName() { return static::$singularName; } From 2eb3e0613f3c4a01378c8dfc430e808e7b618abe Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Thu, 5 Jul 2018 15:19:56 +0200 Subject: [PATCH 13/82] fix(buttons): Properly replace default buttons --- src/PageElement.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/PageElement.php b/src/PageElement.php index b3e32b8..9ee4e1d 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -36,8 +36,6 @@ class PageElement extends DataObject { protected static $pluralName = "Elements"; protected static $defaultIsOpen = true; - public static $overrideBetterButtons = true; - public static function getSingularName() { return static::$singularName; } @@ -262,6 +260,10 @@ public function forTemplate($parentList = "") { ); } + public function replaceDefaultButtons() { + return true; + } + public function getBetterButtonsUtils() { $fieldList = FieldList::create([ PrevNext::create(), From 40f7c901fcce1181c691660cba54e4f0872804e9 Mon Sep 17 00:00:00 2001 From: Felix Eggmann Date: Sat, 7 Jul 2018 14:30:04 +0200 Subject: [PATCH 14/82] fix(gridfield): fix detail edit link, use icon font for button icons, remove unused columns --- css/GridFieldPageSectionsExtension.css | 153 ++++++----- javascript/GridFieldPageSectionsExtension.js | 249 +++++++++++------- src/GridFieldPageSectionsExtension.php | 46 ++-- src/PageElement.php | 8 +- src/PageSectionsExtension.php | 11 +- templates/GridFieldDragHandle.ss | 2 +- .../GridFieldPageSectionsActionColumn.ss | 2 +- 7 files changed, 264 insertions(+), 207 deletions(-) diff --git a/css/GridFieldPageSectionsExtension.css b/css/GridFieldPageSectionsExtension.css index acfd1d1..a9ef2a0 100755 --- a/css/GridFieldPageSectionsExtension.css +++ b/css/GridFieldPageSectionsExtension.css @@ -24,7 +24,7 @@ } .treenav-menu li:hover { - background-color: #FEFAD5; + background-color: #e9f0f4; } .treenav-menu li.header { @@ -44,116 +44,148 @@ /* hierarchical gridfield */ -.cms table.ss-gridfield-table tr { +.ss-gridfield-pagesections .ss-gridfield-item { height: 100%; } -.cms table.ss-gridfield-table tbody td.col-treenav { - padding: 0; +.col-treenav { height: 100%; - vertical-align: middle; + padding-left: 0!important; } -.cms table.ss-gridfield-table tbody td.col-treenav .col-treenav__inner { +.col-treenav__inner { display: flex; height: 100%; flex-flow: row nowrap; justify-content: flex-start; - align-items: center; + align-items: flex-start; } -.cms table.ss-gridfield-table tbody td.col-treenav .col-treenav__text { - padding: 8px 8px 8px 0; +.col-treenav__text { + margin-left: 1em; } -.cms table.ss-gridfield-table tbody td.col-treenav .col-treenav__classname { +.col-treenav__classname { font-size: 87%; } -.cms table.ss-gridfield-table tbody td.col-treenav .col-treenav__title { +.col-treenav__title { font-weight: bold; } -.cms table.ss-gridfield-table tbody td.col-treenav button { - width: 2em; - height: 100%; - display: block; - float: left; - margin: 0 0.5em 0 0; +.col-treenav button { + appearance: none; + background: transparent; + border: 2px solid #43536d; + color: #43536d; + border-radius: 0.5em; + width: 1em; + height: 1em; padding: 0; - border-radius: 0; + margin: .25em 0 0 0; + box-sizing: border-box;: + display: block; position: relative; + cursor: pointer; + font-size: 114%; } -.cms table.ss-gridfield-table tbody td.col-treenav button.ui-state-disabled { - opacity: 1; - filter: Alpha(Opacity=100); - background-image: none; - background: none; - box-shadow: none; +.col-treenav button.is-end { + opacity: 0; } -.cms table.ss-gridfield-table tbody td.col-treenav button.ui-state-disabled:active { - border: none; - background: none; - box-shadow: none; +.col-treenav button:hover { + color: #005489; + border-color: #005489; } -.cms table.ss-gridfield-table tbody td.col-treenav button svg { +.col-treenav button:before { position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); + left: -.08em; + top: -.08em; + font-size: 90%; } -.cms table.ss-gridfield-table tbody td.col-treenav button.level1 { +.col-treenav button .btn__title { + display: none; +} + +.col-treenav button.level1 { margin-left: 2em; } -.cms table.ss-gridfield-table tbody td.col-treenav button.level2 { +.col-treenav button.level2 { margin-left: 4em; } -.cms table.ss-gridfield-table tbody td.col-treenav button.level3 { +.col-treenav button.level3 { margin-left: 6em; } -.cms table.ss-gridfield-table tbody td.col-treenav button.level4 { +.col-treenav button.level4 { margin-left: 8em; } -.cms table.ss-gridfield-table tbody td.col-treenav button.level5 { +.col-treenav button.level5 { margin-left: 10em; } -.cms table.ss-gridfield-table tbody td.col-treenav button.level6 { +.col-treenav button.level6 { margin-left: 12em; } /** * action col */ - -.cms .ss-gridfield-pagesections table.ss-gridfield-table tr td.col-actions { - width: 68px; +.ss-gridfield-pagesections .col-actions { + padding-right: 0; + padding-left: 0; + width: 2em; } -.cms .ss-gridfield-pagesections table.ss-gridfield-table tbody td.col-actions a.view-link, -.cms .ss-gridfield-pagesections table.ss-gridfield-table tbody td.col-actions a.edit-link { - margin-top: -5px; +.ss-gridfield-pagesections-actions { + position: absolute; + height: 100%; + display: flex; + flex-flow: column nowrap; } -.cms .ss-gridfield-pagesections table.ss-gridfield-table tbody td button.col-actions__button { - margin: 2px; +.ss-gridfield-pagesections-actions .edit-link { + display: none; } -.cms .ss-gridfield-pagesections table.ss-gridfield-table tbody td button.col-actions__button .ui-button-text { - line-height: 0; +.ss-gridfield-pagesections-actions .col-actions__button { + opacity: 0; + cursor: pointer; + appearance: none; + width: 2em; + height: 2em; + margin: 0; padding: 0; + border: 0; + background: transparent; + margin: 1px; + color: #43536d; } -.cms .ss-gridfield-pagesections table.ss-gridfield-table tbody td button.col-actions__button .col-actions__button__icon svg { - display: block; +.ss-gridfield-pagesections-actions .col-actions__button:before { + font-size: 140%; +} + +.ss-gridfield-pagesections-actions .col-actions__button:hover { + color: #005489; +} + +.ss-gridfield-pagesections-actions .col-actions__button:disabled { + display: none; +} + +.ss-gridfield-pagesections-actions .col-actions__button .btn__title { + display: none; +} + +.ss-gridfield-item:hover .ss-gridfield-pagesections-actions .col-actions__button { + opacity: 1; } /** @@ -184,18 +216,8 @@ box-sizing: border-box; } -.ss-gridfield-pagesections thead tr th.col-Reorder span { +.ss-gridfield-pagesections .col-reorder { padding: 0 !important; - margin-left: 8px; -} - -.cms .ss-gridfield-pagesections table.ss-gridfield-table tr td.col-reorder { - position: relative; - padding: 0; - width: 16px; -} - -.ss-gridfield-pagesections .col-reorder { position: relative; } @@ -249,16 +271,11 @@ } .ss-gridfield-pagesections .col-reorder .ui-droppable.middle { - left: auto; - right: 0; - top: 0; - height: 100%; + top: 25%; + bottom: 25%; } .ss-gridfield-pagesections .col-reorder .ui-droppable.middle svg { - left: auto; - right: 0; - transform: scale(-1) translate(0, 50%); } .ss-gridfield-pagesections .col-reorder .ui-droppable.after { diff --git a/javascript/GridFieldPageSectionsExtension.js b/javascript/GridFieldPageSectionsExtension.js index c6f9332..563ed79 100755 --- a/javascript/GridFieldPageSectionsExtension.js +++ b/javascript/GridFieldPageSectionsExtension.js @@ -1,17 +1,18 @@ -(function ($) { - $.entwine("ss", function ($) { - +(function($) { + $.entwine("ss", function($) { // Recursively hide a data-grid row and it's children - var hideRow = function ($row) { + var hideRow = function($row) { var id = $row.data("id"); - $("tr.ss-gridfield-item > .col-treenav[data-parent=" + id + "]").each(function () { - hideRow($(this).parent()); - }); + $("tr.ss-gridfield-item > .col-treenav[data-parent=" + id + "]").each( + function() { + hideRow($(this).parent()); + } + ); $row.hide(); }; // Hide our custom context menu when not needed - $(document).on("mousedown", function (event) { + $(document).on("mousedown", function(event) { $parents = $(event.target).parents(".treenav-menu"); if ($parents.length == 0) { $(".treenav-menu").remove(); @@ -20,58 +21,62 @@ // Show context menu $(".ss-gridfield-pagesections tbody").entwine({ - addElement: function (id, elemType) { + addElement: function(id, elemType) { var grid = this.getGridField(); grid.reload({ url: grid.data("url-add"), - data: [{ + data: [ + { name: "id", value: id }, { name: "type", value: elemType - }, + } ] }); }, - removeElement: function (id, parentId) { + removeElement: function(id, parentId) { var grid = this.getGridField(); grid.reload({ url: grid.data("url-remove"), - data: [{ + data: [ + { name: "id", value: id }, { name: "parentId", value: parentId - }, + } ] }); }, - deleteElement: function (id) { + deleteElement: function(id) { var grid = this.getGridField(); grid.reload({ url: grid.data("url-delete"), - data: [{ - name: "id", - value: id - }, ] + data: [ + { + name: "id", + value: id + } + ] }); }, - onadd: function () { + onadd: function() { var grid = this.getGridField(); var thisGrid = this; - this.find("tr.ss-gridfield-item").each(function () { + this.find("tr.ss-gridfield-item").each(function() { var $this = $(this); // actions - $this.find(".col-actions .add-button").click(function (event) { + $this.find(".col-actions .add-button").click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); $target = $(event.target); @@ -80,27 +85,36 @@ var id = grid.data("id"); var rowId = $target.parents(".ss-gridfield-item").data("id"); - - var $menu = $("
    "); + var $menu = $( + "
      " + ); $menu.css({ top: event.pageY + "px", left: event.pageX + "px" }); $(document.body).append($menu); - $menu.append("
    • " + ss.i18n._t('PageSections.GridField.AddAChild', 'Add a child') + "
    • "); - $.each(elems, function (key, value) { - var $li = $("
    • " + value + "
    • ") - $li.click(function () { + $menu.append( + "
    • " + + ss.i18n._t("PageSections.GridField.AddAChild", "Add a child") + + "
    • " + ); + $.each(elems, function(key, value) { + var $li = $("
    • " + value + "
    • "); + $li.click(function() { thisGrid.addElement(rowId, key); $menu.remove(); - }) + }); $menu.append($li); }); $menu.show(); }); - $this.find(".col-actions .delete-button").click(function (event) { + $this.find(".col-actions .delete-button").click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -110,27 +124,48 @@ var rowId = $target.parents(".ss-gridfield-item").data("id"); var parentId = $target.data("parent-id"); - var $menu = $("
        "); + var $menu = $( + "
          " + ); $menu.css({ top: event.pageY + "px", left: event.pageX + "px" }); $(document.body).append($menu); - $menu.append("
        • " + ss.i18n._t('PageSections.GridField.Delete', 'Delete') + "
        • "); - - var $li = $("
        • " + ss.i18n._t('PageSections.GridField.RemoveAChild', 'Remove') + "
        • ") - $li.click(function () { + $menu.append( + "
        • " + + ss.i18n._t("PageSections.GridField.Delete", "Delete") + + "
        • " + ); + + var $li = $( + "
        • " + + ss.i18n._t("PageSections.GridField.RemoveAChild", "Remove") + + "
        • " + ); + $li.click(function() { thisGrid.removeElement(rowId, parentId); $menu.remove(); - }) + }); $menu.append($li); if ($target.data("used-count") < 2) { - var $li = $("
        • " + ss.i18n._t('PageSections.GridField.DeleteAChild', 'Finally delete') + "
        • ") - $li.click(function () { + var $li = $( + "
        • " + + ss.i18n._t( + "PageSections.GridField.DeleteAChild", + "Finally delete" + ) + + "
        • " + ); + $li.click(function() { thisGrid.deleteElement(rowId, $menu.data("parent-id")); $menu.remove(); - }) + }); $menu.append($li); } @@ -138,15 +173,24 @@ }); // reorder - var icon = "" + var arrowIcon = + ""; $col = $this.find(".col-reorder"); - $col.append("
          " + icon + "
          " + icon + "
          " + icon + "
          "); - $col.find("div").each(function () { + $col.append( + "
          " + + arrowIcon + + "
          " + + arrowIcon + + "
          " + + arrowIcon + + "
          " + ); + $col.find("div").each(function() { $(this).droppable({ hoverClass: "state-active", tolerance: "pointer", - drop: function (event, ui) { + drop: function(event, ui) { $drop = $(this); var type = "before"; @@ -172,35 +216,46 @@ var id = ui.draggable.data("id"); var parent = ui.draggable.find(".col-treenav").data("parent"); - var newParent = type === "child" ? $this.data("id") : $treenav.data("parent"); - var sort = type === "child" ? childOrder : $reorder.data("sort"); + var newParent = + type === "child" ? $this.data("id") : $treenav.data("parent"); + var sort = + type === "child" ? childOrder : $reorder.data("sort"); grid.reload({ url: grid.data("url-reorder"), - data: [{ - name: "type", - value: type, - }, { - name: "id", - value: id, - }, { - name: "parent", - value: parent, - }, { - name: "newParent", - value: newParent, - }, { - name: "sort", - value: sort, - }], + data: [ + { + name: "type", + value: type + }, + { + name: "id", + value: id + }, + { + name: "parent", + value: parent + }, + { + name: "newParent", + value: newParent + }, + { + name: "sort", + value: sort + } + ] }); // we alter the state of the published / saved buttons - $('.cms-edit-form .Actions #Form_EditForm_action_publish').button({ + $( + ".cms-edit-form .Actions #Form_EditForm_action_publish" + ).button({ showingAlternate: true }); - $('.cms-preview').entwine('.ss.preview').changeState('StageLink'); - - }, + $(".cms-preview") + .entwine(".ss.preview") + .changeState("StageLink"); + } }); }); @@ -215,19 +270,19 @@ hoverClass: "state-active", tolerance: "pointer", greedy: true, - helper: function () { + helper: function() { var $helper = $( "
          " + - $this.find(".col-treenav__title").text() + - "
          " - ) - $this.css("opacity", 0.6) + $this.find(".col-treenav__title").text() + + "
          " + ); + $this.css("opacity", 0.6); return $helper; }, - start: function () { + start: function() { var element = $this.data("class"); - $(".ui-droppable").each(function () { + $(".ui-droppable").each(function() { var $drop = $(this); var $treenav = $drop.parent().siblings(".col-treenav"); var isOpen = $treenav.find("button").hasClass("is-open"); @@ -235,28 +290,24 @@ // Check if we're allowed to drop the element on the specified drop point. // dont enable dropping on itself - if ($tr.data("id") == $this.data("id")) return + if ($tr.data("id") == $this.data("id")) return; // dont enable dropping on .before of itself - if ($drop.hasClass("before") && $tr.prev().data("id") == $this.data("id")) return + if ( + $drop.hasClass("before") && + $tr.prev().data("id") == $this.data("id") + ) + return; // Depending on where we drop it (before, middle or after) we have to either // don't show middle if open - if ( - $drop.hasClass("middle") && - isOpen - ) { + if ($drop.hasClass("middle") && isOpen) { return; } // let's handle level 0 if not open else if ( $treenav.data("level") == 0 && - ( - $drop.hasClass("before") || - ( - $drop.hasClass("after") && - !isOpen - ) - ) + ($drop.hasClass("before") || + ($drop.hasClass("after") && !isOpen)) ) { var allowed = $treenav.data("allowed-parent-elements"); if (!allowed[element]) return; @@ -264,16 +315,16 @@ // check our allowed children, or the allowed children of our parent row. else if ( $drop.hasClass("before") || - ( - $drop.hasClass("after") && - !isOpen - ) + ($drop.hasClass("after") && !isOpen) ) { - var $parent = $treenav.parent().siblings( - "[data-id=" + $treenav.data("parent") + "]" - ).first(); - - var allowed = $parent.find(".col-treenav").data("allowed-elements"); + var $parent = $treenav + .parent() + .siblings("[data-id=" + $treenav.data("parent") + "]") + .first(); + + var allowed = $parent + .find(".col-treenav") + .data("allowed-elements"); if (allowed && !allowed[element]) return; } else { var allowed = $treenav.data("allowed-elements"); @@ -283,18 +334,18 @@ $(this).show(); }); }, - stop: function (event, ui) { + stop: function(event, ui) { $(".ui-droppable").hide(); // Show the previous elements. If the user made an invalid movement then // we want this to show anyways. If he did something valid the grid will // refresh so we don't care if it's visible behind the loading icon. - $("tr.ss-gridfield-item").css("opacity", "") - }, + $("tr.ss-gridfield-item").css("opacity", ""); + } }); }); }, - onremove: function () { - if (this.data('sortable')) { + onremove: function() { + if (this.data("sortable")) { this.sortable("destroy"); } } diff --git a/src/GridFieldPageSectionsExtension.php b/src/GridFieldPageSectionsExtension.php index 2f2b4f5..7fc1282 100755 --- a/src/GridFieldPageSectionsExtension.php +++ b/src/GridFieldPageSectionsExtension.php @@ -84,12 +84,12 @@ public function augmentColumns($gridField, &$columns) { array_splice($columns, 0, 0, "Reorder"); } - if (!in_array("TreeNav", $columns)) { - array_splice($columns, 1, 0, "TreeNav"); + if (!in_array("Actions", $columns)) { + array_splice($columns, 1, 0, "Actions"); } - if (!in_array("Actions", $columns)) { - array_splice($columns, 2, 0, "Actions"); + if (!in_array("TreeNav", $columns)) { + array_splice($columns, 2, 0, "TreeNav"); } // Insert grid state initial data @@ -184,11 +184,9 @@ public function getColumnContent($gridField, $record, $columnName) { $level = $record->_Level; $field = null; + $icon = ''; if ($record->Children() && $record->Children()->Count() > 0) { - $icon = ($open === true ? '' - : ''); - } else { - $icon = ''; + $icon = ($open === true ? 'font-icon-down-open' : 'font-icon-right-open'); } $field = GridField_FormAction::create( @@ -203,7 +201,8 @@ public function getColumnContent($gridField, $record, $columnName) { $field->addExtraClass(" is-end"); $field->setDisabled(true); } - $field->setButtonContent($icon); + $field->addExtraClass($icon); + $field->setButtonContent(' '); $field->setForm($gridField->getForm()); return ViewableData::create()->customise([ @@ -221,9 +220,10 @@ public function getColumnContent($gridField, $record, $columnName) { while ($temp->_Parent) { $temp = $temp->_Parent; $link = Controller::join_links("item", $temp->ID, - "ItemEditForm", "field", "Children", $link + "ItemEditForm", "field", "Child", $link ); } + // /admin/pages/edit/EditForm/1/field/PageSectionBottom/item/3/ItemEditForm/field/Child/item/4/edit $link = Controller::join_links($gridField->link(), $link); $data = new ArrayData([ 'Link' => $link @@ -233,7 +233,7 @@ public function getColumnContent($gridField, $record, $columnName) { $classes = $record->getAllowedPageElements(); $elems = []; foreach ($classes as $class) { - $elems[$class] = $class::getSingularName(); + $elems[$class] = singleton($class)->singular_name(); } $addButton = GridField_FormAction::create( $gridField, @@ -243,16 +243,11 @@ public function getColumnContent($gridField, $record, $columnName) { null ); $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); - $addButton->addExtraClass("col-actions__button add-button"); + $addButton->addExtraClass("col-actions__button add-button font-icon-plus"); if (!count($elems)) { $addButton->setDisabled(true); } - $addButton->setButtonContent(' - - - - - '); + $addButton->setButtonContent('Add'); $deleteButton = GridField_FormAction::create( $gridField, @@ -262,29 +257,22 @@ public function getColumnContent($gridField, $record, $columnName) { null ); $deleteButton->setAttribute( - "data-used-count", + "data-used-count", $record->Parents()->Count() + $record->getAllPages()->Count() ); $deleteButton->setAttribute( - "data-parent-id", + "data-parent-id", $record->_Parent ? $record->_Parent->ID : $this->page->ID ); - $deleteButton->addExtraClass("col-actions__button delete-button"); + $deleteButton->addExtraClass("col-actions__button delete-button font-icon-trash-bin"); - $deleteButton->setButtonContent(' - - - - - '); + $deleteButton->setButtonContent('Delete'); return ViewableData::create()->customise([ "EditButton" => $editButton, "AddButton" => $addButton, "DeleteButton" => $deleteButton, ])->renderWith("GridFieldPageSectionsActionColumn"); - - return $ret; } } diff --git a/src/PageElement.php b/src/PageElement.php index 9ee4e1d..a317fba 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -86,8 +86,6 @@ function canCreate($member = null, $context = []) { return true; } ]; private static $summary_fields = [ - "SingularName", - "ID", "GridFieldPreview", ]; @@ -131,7 +129,7 @@ public function onAfterWrite() { public function onAfterDelete() { parent::onAfterDelete(); - + if (Versioned::get_stage() == Versioned::DRAFT) { foreach ($this->PageSections() as $section) { $section->__Counter++; @@ -200,7 +198,7 @@ public function getCMSFields() { // Add our newest version as a readonly field $fields->addFieldsToTab( - "Root.Main", + "Root.Main", ReadonlyField::create("Version", "Version", $this->Version), "Title" ); @@ -226,7 +224,7 @@ public function getCMSFields() { $gridField = GridField::create("Pages", "Pages", $pages, $config); $fields->addFieldToTab("Root.Pages", $gridField); } - + return $fields; } diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index fbfd6a4..f4d1fcd 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -116,10 +116,13 @@ public function updateCMSFields(FieldList $fields) { ->addComponent($addNewButton) ->addComponent(new GridFieldPageSectionsExtension($this->owner)) ->addComponent(new GridFieldDetailForm()); - $dataColumns->setFieldCasting(["GridFieldPreview" => "HTMLText->RAW"]); - + $dataColumns + ->setFieldCasting(["GridFieldPreview" => "HTMLText->RAW"]) + ->setDisplayFields([ + "GridFieldPreview" => "Preview", + ]); $f = new GridField($name, $section, $this->owner->$name()->Elements(), $config); - $fields->addFieldToTab("Root.PageSections", $f); + $fields->addFieldToTab("Root.PageSections.{$section}", $f); } } } @@ -132,7 +135,7 @@ public function RenderPageSection($name = "Main") { ); } - public function getPublishState() { + public function getPublishState() { return DBField::create_field("HTMLText", $this->owner->latestPublished() ? "Published" : "Draft"); } } diff --git a/templates/GridFieldDragHandle.ss b/templates/GridFieldDragHandle.ss index a7028c0..b59a1bf 100755 --- a/templates/GridFieldDragHandle.ss +++ b/templates/GridFieldDragHandle.ss @@ -1 +1 @@ - +
          diff --git a/templates/GridFieldPageSectionsActionColumn.ss b/templates/GridFieldPageSectionsActionColumn.ss index 75edf74..6c45bc9 100644 --- a/templates/GridFieldPageSectionsActionColumn.ss +++ b/templates/GridFieldPageSectionsActionColumn.ss @@ -1,4 +1,4 @@ -
          +
          $AddButton $DeleteButton $EditButton From 755669631e1d38f151321676c0d6d27835c4e913 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Mon, 9 Jul 2018 12:20:36 +0200 Subject: [PATCH 15/82] fix(allowed-elements): Fix inconsistent allowed elements in gridfield --- javascript/GridFieldPageSectionsExtension.js | 122 +++++++++---------- src/GridFieldPageSectionsExtension.php | 66 +++++----- src/PageElement.php | 17 ++- src/PageSection.php | 5 + src/PageSectionsExtension.php | 8 +- 5 files changed, 119 insertions(+), 99 deletions(-) diff --git a/javascript/GridFieldPageSectionsExtension.js b/javascript/GridFieldPageSectionsExtension.js index 563ed79..cfa1ecb 100755 --- a/javascript/GridFieldPageSectionsExtension.js +++ b/javascript/GridFieldPageSectionsExtension.js @@ -1,10 +1,10 @@ -(function($) { - $.entwine("ss", function($) { +(function ($) { + $.entwine("ss", function ($) { // Recursively hide a data-grid row and it's children - var hideRow = function($row) { + var hideRow = function ($row) { var id = $row.data("id"); $("tr.ss-gridfield-item > .col-treenav[data-parent=" + id + "]").each( - function() { + function () { hideRow($(this).parent()); } ); @@ -12,7 +12,7 @@ }; // Hide our custom context menu when not needed - $(document).on("mousedown", function(event) { + $(document).on("mousedown", function (event) { $parents = $(event.target).parents(".treenav-menu"); if ($parents.length == 0) { $(".treenav-menu").remove(); @@ -21,13 +21,12 @@ // Show context menu $(".ss-gridfield-pagesections tbody").entwine({ - addElement: function(id, elemType) { + addElement: function (id, elemType) { var grid = this.getGridField(); grid.reload({ url: grid.data("url-add"), - data: [ - { + data: [{ name: "id", value: id }, @@ -38,13 +37,12 @@ ] }); }, - removeElement: function(id, parentId) { + removeElement: function (id, parentId) { var grid = this.getGridField(); grid.reload({ url: grid.data("url-remove"), - data: [ - { + data: [{ name: "id", value: id }, @@ -55,28 +53,26 @@ ] }); }, - deleteElement: function(id) { + deleteElement: function (id) { var grid = this.getGridField(); grid.reload({ url: grid.data("url-delete"), - data: [ - { - name: "id", - value: id - } - ] + data: [{ + name: "id", + value: id + }] }); }, - onadd: function() { + onadd: function () { var grid = this.getGridField(); var thisGrid = this; - this.find("tr.ss-gridfield-item").each(function() { + this.find("tr.ss-gridfield-item").each(function () { var $this = $(this); // actions - $this.find(".col-actions .add-button").click(function(event) { + $this.find(".col-actions .add-button").click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); $target = $(event.target); @@ -87,10 +83,10 @@ var $menu = $( "
            " + id + + "' class='treenav-menu' data-id='" + + id + + "'>" ); $menu.css({ top: event.pageY + "px", @@ -100,12 +96,12 @@ $menu.append( "
          • " + - ss.i18n._t("PageSections.GridField.AddAChild", "Add a child") + - "
          • " + ss.i18n._t("PageSections.GridField.AddAChild", "Add a child") + + "" ); - $.each(elems, function(key, value) { + $.each(elems, function (key, value) { var $li = $("
          • " + value + "
          • "); - $li.click(function() { + $li.click(function () { thisGrid.addElement(rowId, key); $menu.remove(); }); @@ -114,7 +110,7 @@ $menu.show(); }); - $this.find(".col-actions .delete-button").click(function(event) { + $this.find(".col-actions .delete-button").click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -126,10 +122,10 @@ var $menu = $( "
              " + id + + "' class='treenav-menu' data-id='" + + id + + "'>" ); $menu.css({ top: event.pageY + "px", @@ -139,16 +135,16 @@ $menu.append( "
            • " + - ss.i18n._t("PageSections.GridField.Delete", "Delete") + - "
            • " + ss.i18n._t("PageSections.GridField.Delete", "Delete") + + "" ); var $li = $( "
            • " + - ss.i18n._t("PageSections.GridField.RemoveAChild", "Remove") + - "
            • " + ss.i18n._t("PageSections.GridField.RemoveAChild", "Remove") + + "" ); - $li.click(function() { + $li.click(function () { thisGrid.removeElement(rowId, parentId); $menu.remove(); }); @@ -156,13 +152,13 @@ if ($target.data("used-count") < 2) { var $li = $( "
            • " + - ss.i18n._t( - "PageSections.GridField.DeleteAChild", - "Finally delete" - ) + - "
            • " + ss.i18n._t( + "PageSections.GridField.DeleteAChild", + "Finally delete" + ) + + "" ); - $li.click(function() { + $li.click(function () { thisGrid.deleteElement(rowId, $menu.data("parent-id")); $menu.remove(); }); @@ -179,18 +175,18 @@ $col = $this.find(".col-reorder"); $col.append( "
              " + - arrowIcon + - "
              " + - arrowIcon + - "
              " + - arrowIcon + - "
              " + arrowIcon + + "
              " + + arrowIcon + + "
              " + + arrowIcon + + "
              " ); - $col.find("div").each(function() { + $col.find("div").each(function () { $(this).droppable({ hoverClass: "state-active", tolerance: "pointer", - drop: function(event, ui) { + drop: function (event, ui) { $drop = $(this); var type = "before"; @@ -223,8 +219,7 @@ grid.reload({ url: grid.data("url-reorder"), - data: [ - { + data: [{ name: "type", value: type }, @@ -270,19 +265,19 @@ hoverClass: "state-active", tolerance: "pointer", greedy: true, - helper: function() { + helper: function () { var $helper = $( "
              " + - $this.find(".col-treenav__title").text() + - "
              " + $this.find(".col-treenav__title").text() + + "
              " ); $this.css("opacity", 0.6); return $helper; }, - start: function() { + start: function () { var element = $this.data("class"); - $(".ui-droppable").each(function() { + $(".ui-droppable").each(function () { var $drop = $(this); var $treenav = $drop.parent().siblings(".col-treenav"); var isOpen = $treenav.find("button").hasClass("is-open"); @@ -309,8 +304,7 @@ ($drop.hasClass("before") || ($drop.hasClass("after") && !isOpen)) ) { - var allowed = $treenav.data("allowed-parent-elements"); - if (!allowed[element]) return; + if (!$treenav.data("allowed-root")) return; } // check our allowed children, or the allowed children of our parent row. else if ( @@ -334,7 +328,7 @@ $(this).show(); }); }, - stop: function(event, ui) { + stop: function (event, ui) { $(".ui-droppable").hide(); // Show the previous elements. If the user made an invalid movement then // we want this to show anyways. If he did something valid the grid will @@ -344,7 +338,7 @@ }); }); }, - onremove: function() { + onremove: function () { if (this.data("sortable")) { this.sortable("destroy"); } diff --git a/src/GridFieldPageSectionsExtension.php b/src/GridFieldPageSectionsExtension.php index 7fc1282..19fe150 100755 --- a/src/GridFieldPageSectionsExtension.php +++ b/src/GridFieldPageSectionsExtension.php @@ -26,7 +26,8 @@ class GridFieldPageSectionsExtension implements GridField_URLHandler { - protected $page; + // Parent is either a Page or a PageElement + protected $parent; protected $sortField; protected static $allowed_actions = [ @@ -37,13 +38,13 @@ class GridFieldPageSectionsExtension implements ]; - public function __construct($page, $sortField = "SortOrder") { - $this->page = $page; + public function __construct($parent, $sortField = "SortOrder") { + $this->parent = $parent; $this->sortField = $sortField; } - public function getPage() { - return $this->page; + public function getParent() { + return $this->parent; } public function getSortField() { return $this->sortField; @@ -97,7 +98,8 @@ public function augmentColumns($gridField, &$columns) { if (!isset($state->open)) { $state->open = []; - // Open all elements by default if has children + // Open all elements by default if they have children + // and the method ->isOpenByDefault() returns true $list = []; $newList = $gridField->getManipulatedList(); while (count($list) < count($newList)) { @@ -135,29 +137,26 @@ public function getColumnAttributes($gridField, $record, $columnName) { // Handle tree nav column else if ($columnName == "TreeNav") { + // Construct the array of all allowed child elements $classes = $record->getAllowedPageElements(); $elems = []; foreach ($classes as $class) { $elems[$class] = $class::getSingularName(); } - // if element has no parent we need to - // know the allowed elements of the page - if (!$record->_Parent) { - $parentClasses = $this->page->getAllowedPageElements(); - $parentElems = []; - foreach ($parentClasses as $class) { - $parentElems[$class] = $class::getSingularName(); - } - } + // Find out if this record is allowed as a root record + // There are two cases, either this GridField is on a page, + // or it is on a PageElement and we're looking that the children + $parentClasses = $this->parent->getAllowedPageElements(); + $isAllowedRoot = in_array($record->ClassName, $parentClasses); return [ - "class" => "col-treenav", - "data-class" => $record->ClassName, - "data-level" => strval($record->_Level), - "data-parent" => $record->_Parent ? strval($record->_Parent->ID) : "", - "data-allowed-parent-elements" => !$record->_Parent ? json_encode($parentElems, JSON_UNESCAPED_UNICODE) : "", - "data-allowed-elements" => json_encode($elems, JSON_UNESCAPED_UNICODE), + "class" => "col-treenav", + "data-class" => $record->ClassName, + "data-level" => strval($record->_Level), + "data-parent" => $record->_Parent ? strval($record->_Parent->ID) : "", + "data-allowed-root" => $isAllowedRoot, + "data-allowed-elements" => json_encode($elems, JSON_UNESCAPED_UNICODE), ]; } @@ -184,11 +183,13 @@ public function getColumnContent($gridField, $record, $columnName) { $level = $record->_Level; $field = null; + // Create the tree icon $icon = ''; if ($record->Children() && $record->Children()->Count() > 0) { $icon = ($open === true ? 'font-icon-down-open' : 'font-icon-right-open'); } + // Create the tree field $field = GridField_FormAction::create( $gridField, "TreeNavAction".$record->ID, @@ -215,21 +216,22 @@ public function getColumnContent($gridField, $record, $columnName) { } else if ($columnName == "Actions") { + // Create a direct link to edit the item $link = Controller::join_links("item", $record->ID, "edit"); $temp = $record; + // We need to traverse through all the parents to build the link while ($temp->_Parent) { $temp = $temp->_Parent; - $link = Controller::join_links("item", $temp->ID, - "ItemEditForm", "field", "Child", $link - ); + $link = Controller::join_links("item", $temp->ID, "ItemEditForm", "field", "Child", $link); } - // /admin/pages/edit/EditForm/1/field/PageSectionBottom/item/3/ItemEditForm/field/Child/item/4/edit $link = Controller::join_links($gridField->link(), $link); $data = new ArrayData([ 'Link' => $link ]); $editButton = $data->renderWith('SilverStripe\Forms\GridField\GridFieldEditButton'); + // Create a button to add a new child element + // and save the allowed child classes on the button $classes = $record->getAllowedPageElements(); $elems = []; foreach ($classes as $class) { @@ -249,6 +251,7 @@ public function getColumnContent($gridField, $record, $columnName) { } $addButton->setButtonContent('Add'); + // Create a button to delete and/or remove the element from the parent $deleteButton = GridField_FormAction::create( $gridField, "DeleteAction".$record->ID, @@ -262,7 +265,7 @@ public function getColumnContent($gridField, $record, $columnName) { ); $deleteButton->setAttribute( "data-parent-id", - $record->_Parent ? $record->_Parent->ID : $this->page->ID + $record->_Parent ? $record->_Parent->ID : $this->parent->ID ); $deleteButton->addExtraClass("col-actions__button delete-button font-icon-trash-bin"); @@ -284,6 +287,7 @@ public function handleAction(GridField $gridField, $actionName, $arguments, $dat if ($actionName == "dotreenav") { $elem = $arguments["element"]; + // Check if we have children to show if ($elem->Children()->Count() == 0) { Controller::curr()->getResponse()->setStatusCode( 200, @@ -292,6 +296,7 @@ public function handleAction(GridField $gridField, $actionName, $arguments, $dat return; } + // Change the internal GridField state to show the children $state = $gridField->getState(true); if ($this->isOpen($state, $elem)) { $this->closeElement($state, $elem); @@ -301,6 +306,7 @@ public function handleAction(GridField $gridField, $actionName, $arguments, $dat } } + // Check if an element is currently opened private function isOpen($state, $element) { $list = []; $base = $element; @@ -321,6 +327,7 @@ private function isOpen($state, $element) { return true; } + // Open a specific element in the grid private function openElement($state, $element) { $list = []; $base = $element; @@ -340,6 +347,7 @@ private function openElement($state, $element) { $opens = $opens->{$item->ID}; } } + // Close a specific element in the grid private function closeElement($state, $element) { $list = []; $base = $element->_Parent; @@ -357,6 +365,7 @@ private function closeElement($state, $element) { unset($opens->{$element->ID}); } + // Return the data list of the GridField with all opened elements filled in public function getManipulatedData(GridField $gridField, SS_List $dataList) { $list = $dataList->sort($this->getSortField())->toArray(); @@ -405,13 +414,14 @@ public function handleReorder(GridField $gridField, HTTPRequest $request) { $parent = PageElement::get()->byID($vars["parent"]); $newParent = PageElement::get()->byID($vars["newParent"]); + // First check if the parent accepts the PageElement we're trying to move $parentClass = ""; if ($newParent) { $allowed = in_array($item->ClassName, $newParent->getAllowedPageElements()); $parentClass = $newParent->ClassName; } else { - $allowed = in_array($item->ClassName, $this->getPage()->getAllowedPageElements()); - $parentClass = $this->getPage()->ClassName; + $allowed = in_array($item->ClassName, $this->parent->getAllowedPageElements()); + $parentClass = $this->parent->ClassName; } if (!$allowed) { Controller::curr()->getResponse()->setStatusCode( diff --git a/src/PageElement.php b/src/PageElement.php index a317fba..1a47d27 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -99,13 +99,17 @@ function canCreate($member = null, $context = []) { return true; } 'publishOnAllPages', ); - public static function getAllowedPageElements() { + // Returns all page element classes, without the base class + public static function getAllPageElementClasses() { $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); - // remove $classes = array_diff($classes, [PageElement::class]); return $classes; } + public function getAllowedPageElements() { + return self::getAllPageElementClasses(); + } + public function onBeforeWrite() { parent::onBeforeWrite(); @@ -142,8 +146,11 @@ public function getChildrenGridField() { $addNewButton = new GridFieldAddNewMultiClass(); $addNewButton->setClasses($this->getAllowedPageElements()); + $list = PageElement::get() + ->exclude("ID", $this->getParentIDs()) + ->filter("ClassName", $this->owner->getAllowedPageElements()); $autoCompl = new GridFieldAddExistingSearchButton('buttons-before-right'); - $autoCompl->setSearchList(PageElement::get()->exclude("ID", $this->getParentIDs())); + $autoCompl->setSearchList($list); $config = GridFieldConfig::create() ->addComponent(new GridFieldButtonRow("before")) @@ -151,7 +158,7 @@ public function getChildrenGridField() { ->addComponent($dataColumns = new GridFieldDataColumns()) ->addComponent($autoCompl) ->addComponent($addNewButton) - ->addComponent(new GridFieldPageSectionsExtension($this->owner)) + ->addComponent(new GridFieldPageSectionsExtension($this)) ->addComponent(new GridFieldDetailForm()) ->addComponent(new GridFieldFooter()); $dataColumns->setFieldCasting(["GridFieldPreview" => "HTMLText->RAW"]); @@ -192,7 +199,7 @@ public function getCMSFields() { $fields->removeByName('__Counter'); $fields->removeByName("Children"); - if ($this->ID && count(static::getAllowedPageElements()) > 0) { + if ($this->ID && count($this->getAllowedPageElements()) > 0) { $fields->insertAfter("Main", Tab::create("Child", "Children", $this->getChildrenGridField())); } diff --git a/src/PageSection.php b/src/PageSection.php index c1973d0..a5deefd 100644 --- a/src/PageSection.php +++ b/src/PageSection.php @@ -3,6 +3,7 @@ namespace FLXLabs\PageSections; use SilverStripe\CMS\Model\SiteTree; +use SilverStripe\Core\ClassInfo; use SilverStripe\ORM\DataObject; use SilverStripe\Forms\Form; use SilverStripe\Forms\FieldList; @@ -100,4 +101,8 @@ public function getName() { } return null; } + + public function getAllowedPageElements($section = "Main") { + return $this->Page()->getAllowedPageElements($section); + } } diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index f4d1fcd..31550b4 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -55,7 +55,7 @@ public static function get_extra_config($class = null, $extensionClass = null) { ]; } - public static function getAllowedPageElements() { + public function getAllowedPageElements($section = "Main") { $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); $classes = array_diff($classes, [PageElement::class]); return $classes; @@ -108,11 +108,15 @@ public function updateCMSFields(FieldList $fields) { $addNewButton = new GridFieldAddNewMultiClass(); $addNewButton->setClasses($this->owner->getAllowedPageElements()); + $list = PageElement::get()->filter("ClassName", $this->owner->getAllowedPageElements()); + $addExistingButton = new GridFieldAddExistingSearchButton('buttons-before-right'); + $addExistingButton->setSearchList($list); + $config = GridFieldConfig::create() ->addComponent(new GridFieldButtonRow("before")) ->addComponent(new GridFieldToolbarHeader()) ->addComponent($dataColumns = new GridFieldDataColumns()) - ->addComponent(new GridFieldAddExistingSearchButton('buttons-before-right')) + ->addComponent($addExistingButton) ->addComponent($addNewButton) ->addComponent(new GridFieldPageSectionsExtension($this->owner)) ->addComponent(new GridFieldDetailForm()); From b1873f54c570a90ab0313ba3f5984212dc67daf1 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Mon, 9 Jul 2018 12:34:47 +0200 Subject: [PATCH 16/82] fix(gridfield): Fix some dnd arrows --- javascript/GridFieldPageSectionsExtension.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/javascript/GridFieldPageSectionsExtension.js b/javascript/GridFieldPageSectionsExtension.js index cfa1ecb..21715c6 100755 --- a/javascript/GridFieldPageSectionsExtension.js +++ b/javascript/GridFieldPageSectionsExtension.js @@ -291,20 +291,28 @@ if ( $drop.hasClass("before") && $tr.prev().data("id") == $this.data("id") - ) + ) { + return; + } + // dont enable dropping on .after if we are open and the next element is us + else if ( + $drop.hasClass("after") && isOpen && + $tr.next().data("id") == $this.data("id") + ) { return; + } // Depending on where we drop it (before, middle or after) we have to either // don't show middle if open - if ($drop.hasClass("middle") && isOpen) { + else if ($drop.hasClass("middle") && isOpen) { return; } - // let's handle level 0 if not open + // let's handle level 0 else if ( $treenav.data("level") == 0 && ($drop.hasClass("before") || ($drop.hasClass("after") && !isOpen)) ) { - if (!$treenav.data("allowed-root")) return; + if (!$this.data("allowed-root")) return; } // check our allowed children, or the allowed children of our parent row. else if ( From bf2d6556b2720e38a1432a9e62a14d2b9f65a435 Mon Sep 17 00:00:00 2001 From: Felix Eggmann Date: Mon, 9 Jul 2018 18:17:31 +0200 Subject: [PATCH 17/82] fix(tree-nav): fix unremoveable elements --- javascript/GridFieldPageSectionsExtension.js | 130 ++++++++++--------- src/GridFieldPageSectionsExtension.php | 10 +- 2 files changed, 78 insertions(+), 62 deletions(-) diff --git a/javascript/GridFieldPageSectionsExtension.js b/javascript/GridFieldPageSectionsExtension.js index 21715c6..f20d509 100755 --- a/javascript/GridFieldPageSectionsExtension.js +++ b/javascript/GridFieldPageSectionsExtension.js @@ -1,10 +1,10 @@ -(function ($) { - $.entwine("ss", function ($) { +(function($) { + $.entwine("ss", function($) { // Recursively hide a data-grid row and it's children - var hideRow = function ($row) { + var hideRow = function($row) { var id = $row.data("id"); $("tr.ss-gridfield-item > .col-treenav[data-parent=" + id + "]").each( - function () { + function() { hideRow($(this).parent()); } ); @@ -12,7 +12,7 @@ }; // Hide our custom context menu when not needed - $(document).on("mousedown", function (event) { + $(document).on("mousedown", function(event) { $parents = $(event.target).parents(".treenav-menu"); if ($parents.length == 0) { $(".treenav-menu").remove(); @@ -21,12 +21,13 @@ // Show context menu $(".ss-gridfield-pagesections tbody").entwine({ - addElement: function (id, elemType) { + addElement: function(id, elemType) { var grid = this.getGridField(); grid.reload({ url: grid.data("url-add"), - data: [{ + data: [ + { name: "id", value: id }, @@ -37,42 +38,48 @@ ] }); }, - removeElement: function (id, parentId) { + removeElement: function(id, parentId, parentType) { var grid = this.getGridField(); - grid.reload({ url: grid.data("url-remove"), - data: [{ + data: [ + { name: "id", value: id }, { name: "parentId", value: parentId + }, + { + name: "parentType", + value: parentType } ] }); }, - deleteElement: function (id) { + deleteElement: function(id) { var grid = this.getGridField(); grid.reload({ url: grid.data("url-delete"), - data: [{ - name: "id", - value: id - }] + data: [ + { + name: "id", + value: id + } + ] }); }, - onadd: function () { + onadd: function() { var grid = this.getGridField(); var thisGrid = this; - this.find("tr.ss-gridfield-item").each(function () { + this.find("tr.ss-gridfield-item").each(function() { var $this = $(this); // actions - $this.find(".col-actions .add-button").click(function (event) { + $this.find(".col-actions .add-button").click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); $target = $(event.target); @@ -83,10 +90,10 @@ var $menu = $( "
                " + id + + "' class='treenav-menu' data-id='" + + id + + "'>" ); $menu.css({ top: event.pageY + "px", @@ -96,12 +103,12 @@ $menu.append( "
              • " + - ss.i18n._t("PageSections.GridField.AddAChild", "Add a child") + - "
              • " + ss.i18n._t("PageSections.GridField.AddAChild", "Add a child") + + "" ); - $.each(elems, function (key, value) { + $.each(elems, function(key, value) { var $li = $("
              • " + value + "
              • "); - $li.click(function () { + $li.click(function() { thisGrid.addElement(rowId, key); $menu.remove(); }); @@ -110,7 +117,7 @@ $menu.show(); }); - $this.find(".col-actions .delete-button").click(function (event) { + $this.find(".col-actions .delete-button").click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -119,13 +126,14 @@ var id = grid.data("id"); var rowId = $target.parents(".ss-gridfield-item").data("id"); var parentId = $target.data("parent-id"); + var parentType = $target.data("parent-type"); var $menu = $( "
                  " + id + + "' class='treenav-menu' data-id='" + + id + + "'>" ); $menu.css({ top: event.pageY + "px", @@ -135,30 +143,30 @@ $menu.append( "
                • " + - ss.i18n._t("PageSections.GridField.Delete", "Delete") + - "
                • " + ss.i18n._t("PageSections.GridField.Delete", "Delete") + + "" ); var $li = $( "
                • " + - ss.i18n._t("PageSections.GridField.RemoveAChild", "Remove") + - "
                • " + ss.i18n._t("PageSections.GridField.RemoveAChild", "Remove") + + "" ); - $li.click(function () { - thisGrid.removeElement(rowId, parentId); + $li.click(function() { + thisGrid.removeElement(rowId, parentId, parentType); $menu.remove(); }); $menu.append($li); if ($target.data("used-count") < 2) { var $li = $( "
                • " + - ss.i18n._t( - "PageSections.GridField.DeleteAChild", - "Finally delete" - ) + - "
                • " + ss.i18n._t( + "PageSections.GridField.DeleteAChild", + "Finally delete" + ) + + "" ); - $li.click(function () { + $li.click(function() { thisGrid.deleteElement(rowId, $menu.data("parent-id")); $menu.remove(); }); @@ -175,18 +183,18 @@ $col = $this.find(".col-reorder"); $col.append( "
                  " + - arrowIcon + - "
                  " + - arrowIcon + - "
                  " + - arrowIcon + - "
                  " + arrowIcon + + "
                  " + + arrowIcon + + "
                  " + + arrowIcon + + "
                  " ); - $col.find("div").each(function () { + $col.find("div").each(function() { $(this).droppable({ hoverClass: "state-active", tolerance: "pointer", - drop: function (event, ui) { + drop: function(event, ui) { $drop = $(this); var type = "before"; @@ -219,7 +227,8 @@ grid.reload({ url: grid.data("url-reorder"), - data: [{ + data: [ + { name: "type", value: type }, @@ -265,19 +274,19 @@ hoverClass: "state-active", tolerance: "pointer", greedy: true, - helper: function () { + helper: function() { var $helper = $( "
                  " + - $this.find(".col-treenav__title").text() + - "
                  " + $this.find(".col-treenav__title").text() + + "
                  " ); $this.css("opacity", 0.6); return $helper; }, - start: function () { + start: function() { var element = $this.data("class"); - $(".ui-droppable").each(function () { + $(".ui-droppable").each(function() { var $drop = $(this); var $treenav = $drop.parent().siblings(".col-treenav"); var isOpen = $treenav.find("button").hasClass("is-open"); @@ -296,7 +305,8 @@ } // dont enable dropping on .after if we are open and the next element is us else if ( - $drop.hasClass("after") && isOpen && + $drop.hasClass("after") && + isOpen && $tr.next().data("id") == $this.data("id") ) { return; @@ -336,7 +346,7 @@ $(this).show(); }); }, - stop: function (event, ui) { + stop: function(event, ui) { $(".ui-droppable").hide(); // Show the previous elements. If the user made an invalid movement then // we want this to show anyways. If he did something valid the grid will @@ -346,7 +356,7 @@ }); }); }, - onremove: function () { + onremove: function() { if (this.data("sortable")) { this.sortable("destroy"); } diff --git a/src/GridFieldPageSectionsExtension.php b/src/GridFieldPageSectionsExtension.php index 19fe150..8534e72 100755 --- a/src/GridFieldPageSectionsExtension.php +++ b/src/GridFieldPageSectionsExtension.php @@ -267,6 +267,11 @@ public function getColumnContent($gridField, $record, $columnName) { "data-parent-id", $record->_Parent ? $record->_Parent->ID : $this->parent->ID ); + $deleteButton->setAttribute( + "data-parent-type", + $record->_Parent ? "element" : "section" + ); + $deleteButton->addExtraClass("col-actions__button delete-button font-icon-trash-bin"); $deleteButton->setButtonContent('Delete'); @@ -497,12 +502,13 @@ public function handleRemove(GridField $gridField, HTTPRequest $request) { $obj = DataObject::get_by_id(PageElement::class, $id); $parentId = intval($request->postVar("parentId")); - $parentObj = DataObject::get_by_id(PageElement::class, $parentId); + $parentType = $request->postVar("parentType"); // Detach it from this parent (from the page section if we're top level) - if (!$parentObj) { + if ($parentType == "section") { $gridField->getList()->Remove($obj); } else { + $parentObj = DataObject::get_by_id(PageElement::class, $parentId); $parentObj->Children()->Remove($obj); } From 93a42698bf299ad2f589dfa92258706f6f5b93fe Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Tue, 10 Jul 2018 12:45:16 +0200 Subject: [PATCH 18/82] fix(gridfield): Fix certain drag and drop arrows not showing --- javascript/GridFieldPageSectionsExtension.js | 156 ++++++++++--------- 1 file changed, 86 insertions(+), 70 deletions(-) diff --git a/javascript/GridFieldPageSectionsExtension.js b/javascript/GridFieldPageSectionsExtension.js index f20d509..c4118d5 100755 --- a/javascript/GridFieldPageSectionsExtension.js +++ b/javascript/GridFieldPageSectionsExtension.js @@ -1,10 +1,10 @@ -(function($) { - $.entwine("ss", function($) { +(function ($) { + $.entwine("ss", function ($) { // Recursively hide a data-grid row and it's children - var hideRow = function($row) { + var hideRow = function ($row) { var id = $row.data("id"); $("tr.ss-gridfield-item > .col-treenav[data-parent=" + id + "]").each( - function() { + function () { hideRow($(this).parent()); } ); @@ -12,7 +12,7 @@ }; // Hide our custom context menu when not needed - $(document).on("mousedown", function(event) { + $(document).on("mousedown", function (event) { $parents = $(event.target).parents(".treenav-menu"); if ($parents.length == 0) { $(".treenav-menu").remove(); @@ -21,13 +21,12 @@ // Show context menu $(".ss-gridfield-pagesections tbody").entwine({ - addElement: function(id, elemType) { + addElement: function (id, elemType) { var grid = this.getGridField(); grid.reload({ url: grid.data("url-add"), - data: [ - { + data: [{ name: "id", value: id }, @@ -38,12 +37,11 @@ ] }); }, - removeElement: function(id, parentId, parentType) { + removeElement: function (id, parentId, parentType) { var grid = this.getGridField(); grid.reload({ url: grid.data("url-remove"), - data: [ - { + data: [{ name: "id", value: id }, @@ -58,28 +56,26 @@ ] }); }, - deleteElement: function(id) { + deleteElement: function (id) { var grid = this.getGridField(); grid.reload({ url: grid.data("url-delete"), - data: [ - { - name: "id", - value: id - } - ] + data: [{ + name: "id", + value: id + }] }); }, - onadd: function() { + onadd: function () { var grid = this.getGridField(); var thisGrid = this; - this.find("tr.ss-gridfield-item").each(function() { + this.find("tr.ss-gridfield-item").each(function () { var $this = $(this); // actions - $this.find(".col-actions .add-button").click(function(event) { + $this.find(".col-actions .add-button").click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); $target = $(event.target); @@ -90,10 +86,10 @@ var $menu = $( "
                    " + id + + "' class='treenav-menu' data-id='" + + id + + "'>" ); $menu.css({ top: event.pageY + "px", @@ -103,12 +99,12 @@ $menu.append( "
                  • " + - ss.i18n._t("PageSections.GridField.AddAChild", "Add a child") + - "
                  • " + ss.i18n._t("PageSections.GridField.AddAChild", "Add a child") + + "" ); - $.each(elems, function(key, value) { + $.each(elems, function (key, value) { var $li = $("
                  • " + value + "
                  • "); - $li.click(function() { + $li.click(function () { thisGrid.addElement(rowId, key); $menu.remove(); }); @@ -117,7 +113,7 @@ $menu.show(); }); - $this.find(".col-actions .delete-button").click(function(event) { + $this.find(".col-actions .delete-button").click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -130,10 +126,10 @@ var $menu = $( "
                      " + id + + "' class='treenav-menu' data-id='" + + id + + "'>" ); $menu.css({ top: event.pageY + "px", @@ -143,16 +139,16 @@ $menu.append( "
                    • " + - ss.i18n._t("PageSections.GridField.Delete", "Delete") + - "
                    • " + ss.i18n._t("PageSections.GridField.Delete", "Delete") + + "" ); var $li = $( "
                    • " + - ss.i18n._t("PageSections.GridField.RemoveAChild", "Remove") + - "
                    • " + ss.i18n._t("PageSections.GridField.RemoveAChild", "Remove") + + "" ); - $li.click(function() { + $li.click(function () { thisGrid.removeElement(rowId, parentId, parentType); $menu.remove(); }); @@ -160,13 +156,13 @@ if ($target.data("used-count") < 2) { var $li = $( "
                    • " + - ss.i18n._t( - "PageSections.GridField.DeleteAChild", - "Finally delete" - ) + - "
                    • " + ss.i18n._t( + "PageSections.GridField.DeleteAChild", + "Finally delete" + ) + + "" ); - $li.click(function() { + $li.click(function () { thisGrid.deleteElement(rowId, $menu.data("parent-id")); $menu.remove(); }); @@ -176,6 +172,9 @@ $menu.show(); }); + /*$this.after("" + + "");*/ + // reorder var arrowIcon = ""; @@ -183,18 +182,18 @@ $col = $this.find(".col-reorder"); $col.append( "
                      " + - arrowIcon + - "
                      " + - arrowIcon + - "
                      " + - arrowIcon + - "
                      " + arrowIcon + + "
                      " + + arrowIcon + + "
                      " + + arrowIcon + + "
                      " ); - $col.find("div").each(function() { + $col.find("div").each(function () { $(this).droppable({ hoverClass: "state-active", tolerance: "pointer", - drop: function(event, ui) { + drop: function (event, ui) { $drop = $(this); var type = "before"; @@ -227,8 +226,7 @@ grid.reload({ url: grid.data("url-reorder"), - data: [ - { + data: [{ name: "type", value: type }, @@ -274,27 +272,32 @@ hoverClass: "state-active", tolerance: "pointer", greedy: true, - helper: function() { + helper: function () { var $helper = $( "
                      " + - $this.find(".col-treenav__title").text() + - "
                      " + $this.find(".col-treenav__title").text() + + "" ); $this.css("opacity", 0.6); return $helper; }, - start: function() { + start: function () { var element = $this.data("class"); - $(".ui-droppable").each(function() { + $(".ui-droppable").each(function () { var $drop = $(this); var $treenav = $drop.parent().siblings(".col-treenav"); var isOpen = $treenav.find("button").hasClass("is-open"); var $tr = $drop.parents("tr.ss-gridfield-item"); // Check if we're allowed to drop the element on the specified drop point. + // dont enable dropping on itself - if ($tr.data("id") == $this.data("id")) return; + if ($tr.data("id") == $this.data("id") && + $treenav.siblings(".col-reorder").data("sort") == $this.find(".col-reorder").data("sort") && + $treenav.data("parent") == $this.find(".col-treenav").data("parent")) { + return; + } // dont enable dropping on .before of itself if ( @@ -303,29 +306,42 @@ ) { return; } - // dont enable dropping on .after if we are open and the next element is us - else if ( + + // dont enable dropping on .after of itself + if ( $drop.hasClass("after") && - isOpen && $tr.next().data("id") == $this.data("id") ) { return; } + + // dont enable dropping on .middle of other same id elements + if ( + $drop.hasClass("middle") && + $tr.data("id") == $this.data("id") + ) { + return; + } + // Depending on where we drop it (before, middle or after) we have to either // don't show middle if open - else if ($drop.hasClass("middle") && isOpen) { + if ($drop.hasClass("middle") && isOpen) { return; } + // let's handle level 0 - else if ( + if ( $treenav.data("level") == 0 && ($drop.hasClass("before") || ($drop.hasClass("after") && !isOpen)) ) { - if (!$this.data("allowed-root")) return; + if (!$this.find(".col-treenav").data("allowed-root")) { + return; + } } + // check our allowed children, or the allowed children of our parent row. - else if ( + if ( $drop.hasClass("before") || ($drop.hasClass("after") && !isOpen) ) { @@ -346,7 +362,7 @@ $(this).show(); }); }, - stop: function(event, ui) { + stop: function (event, ui) { $(".ui-droppable").hide(); // Show the previous elements. If the user made an invalid movement then // we want this to show anyways. If he did something valid the grid will @@ -356,7 +372,7 @@ }); }); }, - onremove: function() { + onremove: function () { if (this.data("sortable")) { this.sortable("destroy"); } From ad1c2cd12415f4fd0dcc828cf57742fa202f00b8 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Mon, 16 Jul 2018 15:12:33 +0200 Subject: [PATCH 19/82] feat(treeview): Add first version of TreeView --- css/GridFieldPageSectionsExtension.css | 297 -------- css/TreeView.css | 264 +++++++ javascript/GridFieldPageSectionsExtension.js | 382 ---------- javascript/TreeView.js | 470 ++++++++++++ src/GridFieldPageSectionsExtension.php | 533 ------------- src/PageElement.php | 173 +++-- src/PageElementSelfRel.php | 101 +-- src/PageSection.php | 31 +- src/PageSectionPageElementRel.php | 13 +- src/PageSectionsExtension.php | 68 +- src/TreeView.php | 702 ++++++++++++++++++ src/TreeViewFormAction.php | 20 + .../PageSections/TreeViewAddNewButton.ss | 5 + .../PageSections/TreeViewFindExistingForm.ss | 56 ++ .../PageSections/TreeViewFormAction.ss | 3 + .../PageSections/TreeViewPageElement.ss | 40 + templates/GridFieldDragHandle.ss | 1 - templates/GridFieldPageElement.ss | 11 - .../GridFieldPageSectionsActionColumn.ss | 5 - 19 files changed, 1802 insertions(+), 1373 deletions(-) delete mode 100755 css/GridFieldPageSectionsExtension.css create mode 100644 css/TreeView.css delete mode 100755 javascript/GridFieldPageSectionsExtension.js create mode 100644 javascript/TreeView.js delete mode 100755 src/GridFieldPageSectionsExtension.php create mode 100644 src/TreeView.php create mode 100644 src/TreeViewFormAction.php create mode 100644 templates/FLXLabs/PageSections/TreeViewAddNewButton.ss create mode 100644 templates/FLXLabs/PageSections/TreeViewFindExistingForm.ss create mode 100644 templates/FLXLabs/PageSections/TreeViewFormAction.ss create mode 100644 templates/FLXLabs/PageSections/TreeViewPageElement.ss delete mode 100755 templates/GridFieldDragHandle.ss delete mode 100755 templates/GridFieldPageElement.ss delete mode 100644 templates/GridFieldPageSectionsActionColumn.ss diff --git a/css/GridFieldPageSectionsExtension.css b/css/GridFieldPageSectionsExtension.css deleted file mode 100755 index a9ef2a0..0000000 --- a/css/GridFieldPageSectionsExtension.css +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Tree Nav - */ - -.treenav-menu { - display: none; - z-index: 1000; - position: fixed; - overflow: hidden; - border: 1px solid #CCC; - white-space: nowrap; - font-family: sans-serif; - background: #FFF; - color: #333; - border-radius: 5px; - padding: 0; -} - -.treenav-menu li { - padding: 8px 12px 8px 2em; - cursor: pointer; - list-style-type: none; - transition: all .3s ease; -} - -.treenav-menu li:hover { - background-color: #e9f0f4; -} - -.treenav-menu li.header { - font-weight: bolder; - cursor: default; - border-bottom: 1px solid #CCC; - padding-left: 8px; -} - -.treenav-menu li.header:hover { - background: none; -} - -.treenav-menu li.options { - border-top: 1px solid #CCC; -} - -/* hierarchical gridfield */ - -.ss-gridfield-pagesections .ss-gridfield-item { - height: 100%; -} - -.col-treenav { - height: 100%; - padding-left: 0!important; -} - -.col-treenav__inner { - display: flex; - height: 100%; - flex-flow: row nowrap; - justify-content: flex-start; - align-items: flex-start; -} - -.col-treenav__text { - margin-left: 1em; -} - -.col-treenav__classname { - font-size: 87%; -} - -.col-treenav__title { - font-weight: bold; -} - -.col-treenav button { - appearance: none; - background: transparent; - border: 2px solid #43536d; - color: #43536d; - border-radius: 0.5em; - width: 1em; - height: 1em; - padding: 0; - margin: .25em 0 0 0; - box-sizing: border-box;: - display: block; - position: relative; - cursor: pointer; - font-size: 114%; -} - -.col-treenav button.is-end { - opacity: 0; -} - -.col-treenav button:hover { - color: #005489; - border-color: #005489; -} - -.col-treenav button:before { - position: absolute; - left: -.08em; - top: -.08em; - font-size: 90%; -} - -.col-treenav button .btn__title { - display: none; -} - -.col-treenav button.level1 { - margin-left: 2em; -} - -.col-treenav button.level2 { - margin-left: 4em; -} - -.col-treenav button.level3 { - margin-left: 6em; -} - -.col-treenav button.level4 { - margin-left: 8em; -} - -.col-treenav button.level5 { - margin-left: 10em; -} - -.col-treenav button.level6 { - margin-left: 12em; -} - -/** - * action col - */ -.ss-gridfield-pagesections .col-actions { - padding-right: 0; - padding-left: 0; - width: 2em; -} - -.ss-gridfield-pagesections-actions { - position: absolute; - height: 100%; - display: flex; - flex-flow: column nowrap; -} - -.ss-gridfield-pagesections-actions .edit-link { - display: none; -} - -.ss-gridfield-pagesections-actions .col-actions__button { - opacity: 0; - cursor: pointer; - appearance: none; - width: 2em; - height: 2em; - margin: 0; - padding: 0; - border: 0; - background: transparent; - margin: 1px; - color: #43536d; -} - -.ss-gridfield-pagesections-actions .col-actions__button:before { - font-size: 140%; -} - -.ss-gridfield-pagesections-actions .col-actions__button:hover { - color: #005489; -} - -.ss-gridfield-pagesections-actions .col-actions__button:disabled { - display: none; -} - -.ss-gridfield-pagesections-actions .col-actions__button .btn__title { - display: none; -} - -.ss-gridfield-item:hover .ss-gridfield-pagesections-actions .col-actions__button { - opacity: 1; -} - -/** - * Orderable rows - */ - -.col-treenav__draggable { - z-index: 300; - background: #f6f7f8; - padding: 2px 4px; - border-radius: 2px; - box-shadow: 0 0 1px #ccc; -} - -.col-treenav__draggable.state-active { - border: 1px solid #417505; -} - -.col-treenav__draggable:before { - position: absolute; - content: ""; - left: -19px; - top: -19px; - width: 8px; - height: 8px; - border: 3px solid #4a4a4a; - border-radius: 50%; - box-sizing: border-box; -} - -.ss-gridfield-pagesections .col-reorder { - padding: 0 !important; - position: relative; -} - -.ss-gridfield-pagesections .col-reorder .handle { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - cursor: move; -} - -.ss-gridfield-pagesections .col-reorder .handle .icon { - position: absolute; - top: 50%; - left: 50%; - width: 5px; - height: 11px; - margin: -5px 0 0 -2px; - background-image: url('../../framework/thirdparty/jquery-ui-themes/smoothness/images/ui-icons_222222_256x240.png'); - background-position: -5px -227px; -} - -.ss-gridfield-pagesections .col-reorder .ui-droppable { - position: absolute; - left: 100%; - width: 100vw; - z-index: 100; - display: none; -} - -.ss-gridfield-pagesections .col-reorder .ui-droppable svg { - position: absolute; - left: 0; - top: 50%; - z-index: 100; - transform: translate(0, -50%); -} - -.ss-gridfield-pagesections .col-reorder .ui-droppable svg path { - fill: #A8CB7F; -} - -.ss-gridfield-pagesections .col-reorder .ui-droppable.before { - top: 0; - height: 50%; -} - -.ss-gridfield-pagesections .col-reorder .ui-droppable.before svg { - top: 0; -} - -.ss-gridfield-pagesections .col-reorder .ui-droppable.middle { - top: 25%; - bottom: 25%; -} - -.ss-gridfield-pagesections .col-reorder .ui-droppable.middle svg { -} - -.ss-gridfield-pagesections .col-reorder .ui-droppable.after { - bottom: -1px; - height: 50%; -} - -.ss-gridfield-pagesections .col-reorder .ui-droppable.after svg { - top: 100%; - margin-top: -1px; -} - -.ss-gridfield-pagesections .col-reorder .ui-droppable.state-active { - z-index: 200; -} - -.ss-gridfield-pagesections .col-reorder .ui-droppable.state-active svg path { - fill: #417505; -} diff --git a/css/TreeView.css b/css/TreeView.css new file mode 100644 index 0000000..03c9c1e --- /dev/null +++ b/css/TreeView.css @@ -0,0 +1,264 @@ +/* + * Main + */ + +.treeview-pagesections {} + +.treeview-item { + position: relative; + box-sizing: border-box; + border: 1px solid black; +} + +.treeview-item-flow { + display: flex; + justify-content: flex-start; + align-items: stretch; +} + +.treeview-item-children { + padding-left: 2em; +} + +.treeview-item-content { + display: flex; + flex-flow: row nowrap; + justify-content: flex-start; + align-items: flex-start; + padding-top: 0.5em; +} + +.treeview-item-content__text { + margin-left: 1em; +} + +.treeview-item-content__classname { + font-size: 87%; +} + +.treeview-item-content__title { + font-weight: bold; +} + +.treeview-item-actions { + display: flex; + flex-flow: column nowrap; +} + +.treeview-item-actions .edit-link { + display: none; +} + +.treeview-item-actions .col-actions__button { + opacity: 0; + cursor: pointer; + appearance: none; + width: 2em; + height: 2em; + margin: 0; + padding: 0; + border: 0; + background: transparent; + margin: 1px; + color: #43536d; +} + +.treeview-item-actions .col-actions__button:before { + font-size: 140%; +} + +.treeview-item-actions .col-actions__button:hover { + color: #005489; +} + +.treeview-item-actions .col-actions__button:disabled { + display: none; +} + +.treeview-item-actions .col-actions__button .btn__title { + display: none; +} + +.treeview-item:hover .treeview-item-actions .col-actions__button { + opacity: 1; +} + +.treeview-item-actions .tree-button { + appearance: none; + background: transparent; + border: 2px solid #43536d; + color: #43536d; + border-radius: 0.5em; + width: 1em; + height: 1em; + padding: 0; + margin: 0.25em 0 0 0; + box-sizing: border-box; + display: block; + position: relative; + cursor: pointer; + font-size: 114%; +} + +.treeview-item-actions .tree-button.is-end { + opacity: 0; +} + +.treeview-item-actions .tree-button:hover { + color: #005489; + border-color: #005489; +} + +.treeview-item-actions .tree-button:before { + position: absolute; + left: -0.08em; + top: -0.08em; + font-size: 90%; +} + +.treeview-item-actions .tree-button .btn__title { + display: none; +} + +/* + * Add new button + */ + +.treeview-actions-addnew { + display: inline-block; +} + +.treeview-actions-addnew div { + display: inline-block; +} + +/* + * Tree Nav + */ + +.treeview-menu { + display: none; + z-index: 1000; + position: fixed; + overflow: hidden; + border: 1px solid #ccc; + white-space: nowrap; + font-family: sans-serif; + background: #fff; + color: #333; + border-radius: 5px; + padding: 0; +} + +.treeview-menu li { + padding: 8px 12px 8px 2em; + cursor: pointer; + list-style-type: none; + transition: all 0.3s ease; +} + +.treeview-menu li:hover { + background-color: #e9f0f4; +} + +.treeview-menu li.header { + font-weight: bolder; + cursor: default; + border-bottom: 1px solid #ccc; + padding-left: 8px; +} + +.treeview-menu li.header:hover { + background: none; +} + +.treeview-menu li.options { + border-top: 1px solid #ccc; +} + +/** + * Orderable rows + */ + +.treeview-item__draggable { + z-index: 300; + background: #f6f7f8; + padding: 2px 4px; + border-radius: 2px; + box-shadow: 0 0 1px #ccc; +} + +.treeview-item__draggable.state-active { + border: 1px solid #417505; +} + +.treeview-item__draggable:before { + position: absolute; + content: ''; + left: -19px; + top: -19px; + width: 8px; + height: 8px; + border: 3px solid #4a4a4a; + border-radius: 50%; + box-sizing: border-box; +} + +.treeview-item-reorder { + position: absolute; + height: 100%; +} + +.treeview-item-reorder .ui-droppable { + position: absolute; + left: 100%; + width: 2em; + z-index: 100; + display: none; +} + +.treeview-item-reorder .ui-droppable svg { + position: absolute; + left: 0; + top: 50%; + z-index: 100; + transform: translate(0, -50%); +} + +.treeview-item-reorder .ui-droppable svg path { + fill: #a8cb7f; +} + +.treeview-item-reorder .ui-droppable.before { + top: 0; + height: 33%; +} + +.treeview-item-reorder .ui-droppable.before svg { + top: 0; +} + +.treeview-item-reorder .ui-droppable.middle { + top: 33%; + bottom: 33%; +} + +.treeview-item-reorder .ui-droppable.middle svg {} + +.treeview-item-reorder .ui-droppable.after { + bottom: -1px; + height: 33%; +} + +.treeview-item-reorder .ui-droppable.after svg { + top: 100%; + margin-top: -1px; +} + +.treeview-item-reorder .ui-droppable.state-active { + z-index: 200; +} + +.treeview-item-reorder .ui-droppable.state-active svg path { + fill: #417505; +} diff --git a/javascript/GridFieldPageSectionsExtension.js b/javascript/GridFieldPageSectionsExtension.js deleted file mode 100755 index c4118d5..0000000 --- a/javascript/GridFieldPageSectionsExtension.js +++ /dev/null @@ -1,382 +0,0 @@ -(function ($) { - $.entwine("ss", function ($) { - // Recursively hide a data-grid row and it's children - var hideRow = function ($row) { - var id = $row.data("id"); - $("tr.ss-gridfield-item > .col-treenav[data-parent=" + id + "]").each( - function () { - hideRow($(this).parent()); - } - ); - $row.hide(); - }; - - // Hide our custom context menu when not needed - $(document).on("mousedown", function (event) { - $parents = $(event.target).parents(".treenav-menu"); - if ($parents.length == 0) { - $(".treenav-menu").remove(); - } - }); - - // Show context menu - $(".ss-gridfield-pagesections tbody").entwine({ - addElement: function (id, elemType) { - var grid = this.getGridField(); - - grid.reload({ - url: grid.data("url-add"), - data: [{ - name: "id", - value: id - }, - { - name: "type", - value: elemType - } - ] - }); - }, - removeElement: function (id, parentId, parentType) { - var grid = this.getGridField(); - grid.reload({ - url: grid.data("url-remove"), - data: [{ - name: "id", - value: id - }, - { - name: "parentId", - value: parentId - }, - { - name: "parentType", - value: parentType - } - ] - }); - }, - deleteElement: function (id) { - var grid = this.getGridField(); - - grid.reload({ - url: grid.data("url-delete"), - data: [{ - name: "id", - value: id - }] - }); - }, - onadd: function () { - var grid = this.getGridField(); - var thisGrid = this; - - this.find("tr.ss-gridfield-item").each(function () { - var $this = $(this); - - // actions - $this.find(".col-actions .add-button").click(function (event) { - event.preventDefault(); - event.stopImmediatePropagation(); - $target = $(event.target); - var elems = $target.data("allowed-elements"); - - var id = grid.data("id"); - var rowId = $target.parents(".ss-gridfield-item").data("id"); - - var $menu = $( - "
                        " - ); - $menu.css({ - top: event.pageY + "px", - left: event.pageX + "px" - }); - $(document.body).append($menu); - - $menu.append( - "
                      • " + - ss.i18n._t("PageSections.GridField.AddAChild", "Add a child") + - "
                      • " - ); - $.each(elems, function (key, value) { - var $li = $("
                      • " + value + "
                      • "); - $li.click(function () { - thisGrid.addElement(rowId, key); - $menu.remove(); - }); - $menu.append($li); - }); - $menu.show(); - }); - - $this.find(".col-actions .delete-button").click(function (event) { - event.preventDefault(); - event.stopImmediatePropagation(); - - $target = $(event.target); - - var id = grid.data("id"); - var rowId = $target.parents(".ss-gridfield-item").data("id"); - var parentId = $target.data("parent-id"); - var parentType = $target.data("parent-type"); - - var $menu = $( - "
                          " - ); - $menu.css({ - top: event.pageY + "px", - left: event.pageX + "px" - }); - $(document.body).append($menu); - - $menu.append( - "
                        • " + - ss.i18n._t("PageSections.GridField.Delete", "Delete") + - "
                        • " - ); - - var $li = $( - "
                        • " + - ss.i18n._t("PageSections.GridField.RemoveAChild", "Remove") + - "
                        • " - ); - $li.click(function () { - thisGrid.removeElement(rowId, parentId, parentType); - $menu.remove(); - }); - $menu.append($li); - if ($target.data("used-count") < 2) { - var $li = $( - "
                        • " + - ss.i18n._t( - "PageSections.GridField.DeleteAChild", - "Finally delete" - ) + - "
                        • " - ); - $li.click(function () { - thisGrid.deleteElement(rowId, $menu.data("parent-id")); - $menu.remove(); - }); - $menu.append($li); - } - - $menu.show(); - }); - - /*$this.after("" + - "");*/ - - // reorder - var arrowIcon = - ""; - - $col = $this.find(".col-reorder"); - $col.append( - "
                          " + - arrowIcon + - "
                          " + - arrowIcon + - "
                          " + - arrowIcon + - "
                          " - ); - $col.find("div").each(function () { - $(this).droppable({ - hoverClass: "state-active", - tolerance: "pointer", - drop: function (event, ui) { - $drop = $(this); - - var type = "before"; - var childOrder = 0; - - var $treenav = $this.find(".col-treenav"); - var $reorder = $this.find(".col-reorder"); - - if ($drop.hasClass("middle")) { - type = "child"; - childOrder = 1000000; - } else if ($drop.hasClass("after")) { - // If the current element is open, then dragging the other element to the - // "after" slot means it becomes a child of this element, otherwise it - // has to actually go after this element. - if ($treenav.find("button").hasClass("is-open")) { - type = "child"; - childOrder = -1000000; - } else { - type = "after"; - } - } - - var id = ui.draggable.data("id"); - var parent = ui.draggable.find(".col-treenav").data("parent"); - var newParent = - type === "child" ? $this.data("id") : $treenav.data("parent"); - var sort = - type === "child" ? childOrder : $reorder.data("sort"); - - grid.reload({ - url: grid.data("url-reorder"), - data: [{ - name: "type", - value: type - }, - { - name: "id", - value: id - }, - { - name: "parent", - value: parent - }, - { - name: "newParent", - value: newParent - }, - { - name: "sort", - value: sort - } - ] - }); - // we alter the state of the published / saved buttons - $( - ".cms-edit-form .Actions #Form_EditForm_action_publish" - ).button({ - showingAlternate: true - }); - $(".cms-preview") - .entwine(".ss.preview") - .changeState("StageLink"); - } - }); - }); - - $this.draggable({ - revert: "invalid", - cursor: "crosshair", - cursorAt: { - top: -15, - left: -15 - }, - activeClass: "state-active", - hoverClass: "state-active", - tolerance: "pointer", - greedy: true, - helper: function () { - var $helper = $( - "
                          " + - $this.find(".col-treenav__title").text() + - "
                          " - ); - $this.css("opacity", 0.6); - - return $helper; - }, - start: function () { - var element = $this.data("class"); - $(".ui-droppable").each(function () { - var $drop = $(this); - var $treenav = $drop.parent().siblings(".col-treenav"); - var isOpen = $treenav.find("button").hasClass("is-open"); - var $tr = $drop.parents("tr.ss-gridfield-item"); - - // Check if we're allowed to drop the element on the specified drop point. - - // dont enable dropping on itself - if ($tr.data("id") == $this.data("id") && - $treenav.siblings(".col-reorder").data("sort") == $this.find(".col-reorder").data("sort") && - $treenav.data("parent") == $this.find(".col-treenav").data("parent")) { - return; - } - - // dont enable dropping on .before of itself - if ( - $drop.hasClass("before") && - $tr.prev().data("id") == $this.data("id") - ) { - return; - } - - // dont enable dropping on .after of itself - if ( - $drop.hasClass("after") && - $tr.next().data("id") == $this.data("id") - ) { - return; - } - - // dont enable dropping on .middle of other same id elements - if ( - $drop.hasClass("middle") && - $tr.data("id") == $this.data("id") - ) { - return; - } - - // Depending on where we drop it (before, middle or after) we have to either - // don't show middle if open - if ($drop.hasClass("middle") && isOpen) { - return; - } - - // let's handle level 0 - if ( - $treenav.data("level") == 0 && - ($drop.hasClass("before") || - ($drop.hasClass("after") && !isOpen)) - ) { - if (!$this.find(".col-treenav").data("allowed-root")) { - return; - } - } - - // check our allowed children, or the allowed children of our parent row. - if ( - $drop.hasClass("before") || - ($drop.hasClass("after") && !isOpen) - ) { - var $parent = $treenav - .parent() - .siblings("[data-id=" + $treenav.data("parent") + "]") - .first(); - - var allowed = $parent - .find(".col-treenav") - .data("allowed-elements"); - if (allowed && !allowed[element]) return; - } else { - var allowed = $treenav.data("allowed-elements"); - if (!allowed[element]) return; - } - - $(this).show(); - }); - }, - stop: function (event, ui) { - $(".ui-droppable").hide(); - // Show the previous elements. If the user made an invalid movement then - // we want this to show anyways. If he did something valid the grid will - // refresh so we don't care if it's visible behind the loading icon. - $("tr.ss-gridfield-item").css("opacity", ""); - } - }); - }); - }, - onremove: function () { - if (this.data("sortable")) { - this.sortable("destroy"); - } - } - }); - }); -})(jQuery); diff --git a/javascript/TreeView.js b/javascript/TreeView.js new file mode 100644 index 0000000..f7b6bfc --- /dev/null +++ b/javascript/TreeView.js @@ -0,0 +1,470 @@ +(function ($) { + $.entwine("ss", function ($) { + // Hide our custom context menu when not needed + $(document).on("mousedown", function (event) { + $parents = $(event.target).parents(".treeview-menu"); + if ($parents.length == 0) { + $(".treeview-menu").remove(); + } + }); + + // Load our search form when opening the search dialog + $(".add-existing-search-dialog").entwine({ + loadDialog: function (deferred) { + var dialog = this.addClass("loading").children(".ui-dialog-content").empty(); + + deferred.done(function (data) { + dialog.html(data).parent().removeClass("loading"); + }); + } + }); + + // Submit our search form to our own endpoint and show the results + $(".add-existing-search-dialog .add-existing-search-form").entwine({ + onsubmit: function () { + this.closest(".add-existing-search-dialog").loadDialog($.get( + this.prop("action"), this.serialize() + )); + return false; + } + }); + + // Allow clicking the elements in the search form + $('.add-existing-search-dialog .add-existing-search-items .list-group-item-action').entwine({ + onclick: function () { + if (this.children('a').length > 0) { + this.children('a').first().trigger('click'); + } + } + }); + + // Trigger the "add existing" action on the selected element + $(".add-existing-search-dialog .add-existing-search-items a").entwine({ + onclick: function () { + var link = this.closest(".add-existing-search-items").data("add-link"); + var id = this.data("id"); + + var dialog = this.closest(".add-existing-search-dialog") + .addClass("loading") + .children(".ui-dialog-content") + .empty(); + + dialog.data("treeview").reload({ + url: link, + data: [{ + name: "id", + value: id + }] + }, function () { + dialog.dialog("close"); + }); + + return false; + } + }); + + // Browse search result pages + $(".add-existing-search-dialog .add-existing-search-pagination a").entwine({ + onclick: function () { + this.closest(".add-existing-search-dialog").loadDialog($.get( + this.prop("href") + )); + return false; + } + }); + + // Attach data to our tree view + $(".treeview-pagesections").entwine({ + addItem: function (parents, itemId, elemType) { + var $treeView = $(this); + + $treeView.reload({ + url: $treeView.data("url") + "/add", + data: [{ + name: "parents", + value: parents + }, { + name: "itemId", + value: itemId + }, { + name: "type", + value: elemType + }] + }); + }, + removeItem: function (parents, itemId) { + var $treeView = $(this); + + $treeView.reload({ + url: $treeView.data("url") + "/remove", + data: [{ + name: "parents", + value: parents + }, { + name: "itemId", + value: itemId + }] + }); + }, + deleteItem: function (parents, itemId) { + var $treeView = $(this); + + $treeView.reload({ + url: $treeView.data("url") + "/delete", + data: [{ + name: "parents", + value: parents + }, { + name: "itemId", + value: itemId + }] + }); + }, + onadd: function () { + var $treeView = $(this); + var name = $treeView.data("name"); + var url = $treeView.data('url'); + + // Setup add new button + $treeView.find(".treeview-actions-addnew .tree-button").click(function () { + $treeView.reload({ + url: url + "/add", + data: [{ + name: "type", + value: $("#AddNewClass").val() + }] + }); + }); + + // Setup find existing button + $treeView.find("button[name=action_FindExisting]").click(function () { + var dialog = $("
                          ").appendTo("body").dialog({ + modal: true, + resizable: false, + width: 500, + height: 600, + close: function () { + $(this).dialog("destroy").remove(); + } + }); + + dialog.parent().addClass("add-existing-search-dialog").loadDialog( + $.get($treeView.data("url") + "/find") + ); + dialog.data("treeview", $treeView); + }); + + // Process items + $treeView.find(".treeview-item").each(function () { + var $item = $(this); + var $onlyThis = $item.find("> .treeview-item-flow"); + var itemId = $item.data("id"); + var parents = $item.data("tree"); + + // Open an item button + $onlyThis.find(".treeview-item-actions .tree-button").click(function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + + $treeView.reload({ + url: url + "/tree", + data: [{ + name: "parents", + value: parents + }, { + name: "itemId", + value: itemId + }] + }); + }); + + // Add new item button + $onlyThis.find(".treeview-item-actions .add-button").click(function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + + $target = $(event.target); + var elems = $target.data("allowed-elements"); + + var $menu = $( + "
                            " + ); + $menu.css({ + top: event.pageY + "px", + left: event.pageX + "px" + }); + $(document.body).append($menu); + + $menu.append( + "
                          • " + + ss.i18n._t("PageSections.TreeView.AddAChild", "Add a child") + + "
                          • " + ); + $.each(elems, function (key, value) { + var $li = $("
                          • " + value + "
                          • "); + $li.click(function () { + $treeView.addItem(parents, itemId, key); + $menu.remove(); + }); + $menu.append($li); + }); + $menu.show(); + }); + + // Delete button action + $onlyThis.find(".treeview-item-actions .delete-button").click(function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + + $target = $(event.target); + + var $menu = $( + "
                              " + ); + $menu.css({ + top: event.pageY + "px", + left: event.pageX + "px" + }); + $(document.body).append($menu); + + $menu.append( + "
                            • " + + ss.i18n._t("PageSections.TreeView.Delete", "Delete") + + "
                            • " + ); + + var $li = $( + "
                            • " + + ss.i18n._t("PageSections.TreeView.RemoveAChild", "Remove") + + "
                            • " + ); + $li.click(function () { + $treeView.removeItem(parents, itemId); + $menu.remove(); + }); + $menu.append($li); + if ($target.data("used-count") < 2) { + var $li = $( + "
                            • " + + ss.i18n._t( + "PageSections.TreeView.DeleteAChild", + "Finally delete" + ) + + "
                            • " + ); + $li.click(function () { + $treeView.deleteItem(parents, itemId); + $menu.remove(); + }); + $menu.append($li); + } + + $menu.show(); + }); + + // Attach draggable events & info + $onlyThis.draggable({ + revert: "invalid", + cursor: "crosshair", + cursorAt: { + top: -15, + left: -15 + }, + activeClass: "state-active", + hoverClass: "state-active", + tolerance: "pointer", + greedy: true, + helper: function () { + var $helper = $( + "
                              " + + $item.find(".treeview-item-content__title").text() + + "
                              " + ); + $item.css("opacity", 0.6); + + return $helper; + }, + start: function () { + $(".ui-droppable").each(function () { + var $drop = $(this); + var $dropItem = $drop.closest(".treeview-item"); + var isOpen = $dropItem.data("is-open"); + + // Dont enable dropping on itself + if ($dropItem.data("id") == itemId && + $dropItem.data("tree") == parents) { + return; + } + + // Don't enable dropping on the middle arrow for open items + // (they will have child elements where we can drop before or after) + if ($drop.hasClass("middle") && isOpen) { + return; + } + + // Dont enable dropping on .after of itself + if ( + $drop.hasClass("after") && + $dropItem.next().data("id") == itemId + ) { + return; + } + + // Dont enable dropping on .middle of other same id elements + // (no recursive structures) + if ( + $drop.hasClass("middle") && + $dropItem.data("id") == itemId + ) { + return; + } + + // Don't allow dropping elements on the root level that aren't allowed there + if ( + $dropItem.data("tree").length == 0 && + ($drop.hasClass("before") || $drop.hasClass("after")) + ) { + if (!$item.data("allowed-root")) { + return; + } + } + + // Don't allow dropping elements on this level if they're not an allowed child + // Depending on the arrow we either have to check this element or the parent + // of this element to see which children are allowed + var clazz = $item.data("class"); + if ($drop.hasClass("before") || $drop.hasClass("after")) { + var $parent = $dropItem.parent().closest(".treeview-item"); + var allowed = $parent.data("allowed-elements"); + if (allowed && !allowed[clazz]) { + return; + } + } else { + var allowed = $dropItem.data("allowed-elements"); + if (allowed && !allowed[clazz]) { + return; + } + } + + $drop.show(); + }); + }, + stop: function (event, ui) { + $(".ui-droppable").hide(); + // Show the previous elements. If the user made an invalid movement then + // we want this to show anyways. If he did something valid the treeview will + // refresh so we don't care if it's visible behind the loading icon. + $(".treeview-item").css("opacity", ""); + } + }); + + // Dropping targets + $onlyThis.find(".treeview-item-reorder div").each(function () { + $(this).droppable({ + hoverClass: "state-active", + tolerance: "pointer", + drop: function (event, ui) { + $drop = $(this); + $dropItem = $drop.closest(".treeview-item"); + + $oldItem = ui.draggable.closest(".treeview-item"); + var oldId = $oldItem.data("id"); + var oldParents = $oldItem.data("tree"); + + var type = "child"; + var sort = 100000; + + if ($drop.hasClass("before")) { + type = "before"; + sort = $dropItem.data("sort") - 1; + } else if ($drop.hasClass("after")) { + type = "after"; + sort = $dropItem.data("sort") + 1; + } + + var newParent = type === "child" ? itemId : parents[parents.length - 1]; + + $treeView.reload({ + url: url + "/move", + data: [{ + name: "parents", + value: oldParents + }, { + name: "itemId", + value: oldId + }, { + name: "newParent", + value: newParent + }, { + name: "sort", + value: sort + }] + }); + } + }); + }); + }); + }, + // This is copy paste from SilverStripe GridField.js, modified to work for the TreeView + // It updates the gridfield by sending the specified request + // and using the response as the new content for the gridfield + reload: function (ajaxOpts, successCallback) { + var self = this, + form = this.closest('form'), + focusedElName = this.find(':input:focus').attr('name'), // Save focused element for restoring after refresh + data = form.find(':input').serializeArray(); + + if (!ajaxOpts) ajaxOpts = {}; + if (!ajaxOpts.data) ajaxOpts.data = []; + ajaxOpts.data = ajaxOpts.data.concat(data).concat([{ + name: "state", + value: self.data("state-id"), + }]); + + // Include any GET parameters from the current URL, as the view state might depend on it. + // For example, a list prefiltered through external search criteria might be passed to GridField. + if (window.location.search) { + ajaxOpts.data = window.location.search.replace(/^\?/, '') + '&' + $.param(ajaxOpts.data); + } + + form.addClass('loading'); + + $.ajax($.extend({}, { + headers: { + "X-Pjax": 'CurrentField' + }, + type: "POST", + url: this.data('url'), + dataType: 'html', + success: function (data) { + // Replace the grid field with response, not the form. + // TODO Only replaces all its children, to avoid replacing the current scope + // of the executing method. Means that it doesn't retrigger the onmatch() on the main container. + self.empty().append($(data).children()); + + // Refocus previously focused element. Useful e.g. for finding+adding + // multiple relationships via keyboard. + if (focusedElName) self.find(':input[name="' + focusedElName + '"]').focus(); + + form.removeClass('loading'); + if (successCallback) successCallback.apply(this, arguments); + // TODO: Don't know how original SilverStripe GridField magically calls + self.onadd(); + }, + error: function (e) { + alert(i18n._t('Admin.ERRORINTRANSACTION')); + form.removeClass('loading'); + } + }, ajaxOpts)); + }, + }); + }); +})(jQuery); diff --git a/src/GridFieldPageSectionsExtension.php b/src/GridFieldPageSectionsExtension.php deleted file mode 100755 index 8534e72..0000000 --- a/src/GridFieldPageSectionsExtension.php +++ /dev/null @@ -1,533 +0,0 @@ -parent = $parent; - $this->sortField = $sortField; - } - - public function getParent() { - return $this->parent; - } - public function getSortField() { - return $this->sortField; - } - - public function getURLHandlers($grid) { - return [ - "POST add" => "handleAdd", - "POST remove" => "handleRemove", - "POST delete" => "handleDelete", - "POST reorder" => "handleReorder" - ]; - } - - public static function getModuleDir() { - return basename(dirname(__DIR__)); - } - - public function getHTMLFragments($field) { - $moduleDir = self::getModuleDir(); - Requirements::css($moduleDir . "/css/GridFieldPageSectionsExtension.css"); - Requirements::javascript($moduleDir . "/javascript/GridFieldPageSectionsExtension.js"); - Requirements::add_i18n_javascript($moduleDir . '/javascript/lang', false, true); - - $id = rand(1000000, 9999999); - $field->addExtraClass("ss-gridfield-pagesections pagesection-" . $id); - $field->setAttribute("data-id", $id); - $field->setAttribute("data-url-add", $field->Link("add")); - $field->setAttribute("data-url-remove", $field->Link("remove")); - $field->setAttribute("data-url-delete", $field->Link("delete")); - $field->setAttribute("data-url-reorder", $field->Link("reorder")); - - return []; - } - - public function augmentColumns($gridField, &$columns) { - if (!in_array("Reorder", $columns)) { - array_splice($columns, 0, 0, "Reorder"); - } - - if (!in_array("Actions", $columns)) { - array_splice($columns, 1, 0, "Actions"); - } - - if (!in_array("TreeNav", $columns)) { - array_splice($columns, 2, 0, "TreeNav"); - } - - // Insert grid state initial data - $state = $gridField->getState(); - if (!isset($state->open)) { - $state->open = []; - - // Open all elements by default if they have children - // and the method ->isOpenByDefault() returns true - $list = []; - $newList = $gridField->getManipulatedList(); - while (count($list) < count($newList)) { - foreach ($newList as $item) { - if ($item->isOpenByDefault() && $item->Children()->Count()) { - $this->openElement($state, $item); - } - } - $list = $newList; - $newList = $gridField->getManipulatedList(); - } - } - } - - public function getColumnsHandled($gridField) { - return [ - "Reorder", - "Actions", - "TreeNav", - ]; - } - - public function getColumnMetadata($gridField, $columnName) { - return ["title" => ""]; - } - - public function getColumnAttributes($gridField, $record, $columnName) { - // Handle reorder column - if ($columnName == "Reorder") { - return [ - "class" => "col-reorder", - "data-sort" => $record->getField($this->getSortField()), - ]; - } - - // Handle tree nav column - else if ($columnName == "TreeNav") { - // Construct the array of all allowed child elements - $classes = $record->getAllowedPageElements(); - $elems = []; - foreach ($classes as $class) { - $elems[$class] = $class::getSingularName(); - } - - // Find out if this record is allowed as a root record - // There are two cases, either this GridField is on a page, - // or it is on a PageElement and we're looking that the children - $parentClasses = $this->parent->getAllowedPageElements(); - $isAllowedRoot = in_array($record->ClassName, $parentClasses); - - return [ - "class" => "col-treenav", - "data-class" => $record->ClassName, - "data-level" => strval($record->_Level), - "data-parent" => $record->_Parent ? strval($record->_Parent->ID) : "", - "data-allowed-root" => $isAllowedRoot, - "data-allowed-elements" => json_encode($elems, JSON_UNESCAPED_UNICODE), - ]; - } - - // Handle the actions column - else if ($columnName == "Actions") { - return [ - "class" => "col-actions", - ]; - } - } - - public function getColumnContent($gridField, $record, $columnName) { - // Handle reorder column - if ($columnName == "Reorder") { - return ViewableData::create()->renderWith("GridFieldDragHandle"); - } - - // Handle treenav column - else if ($columnName == "TreeNav") { - if (!$record->canView()) return; - - $id = $record->ID; - $open = $record->_Open; - $level = $record->_Level; - $field = null; - - // Create the tree icon - $icon = ''; - if ($record->Children() && $record->Children()->Count() > 0) { - $icon = ($open === true ? 'font-icon-down-open' : 'font-icon-right-open'); - } - - // Create the tree field - $field = GridField_FormAction::create( - $gridField, - "TreeNavAction".$record->ID, - null, - "dotreenav", - ["element" => $record] - ); - $field->addExtraClass("level".$level . ($open ? " is-open" : " is-closed")); - if (!$record->Children()->Count()) { - $field->addExtraClass(" is-end"); - $field->setDisabled(true); - } - $field->addExtraClass($icon); - $field->setButtonContent(' '); - $field->setForm($gridField->getForm()); - - return ViewableData::create()->customise([ - "ButtonField" => $field, - "ID" => $record->ID, - "UsedCount" => $record->Parents()->Count() + $record->getAllPages()->Count(), - "ClassName" => $record->i18n_singular_name(), - "Title" => $record->Title, - ])->renderWith("GridFieldPageElement"); - } - - else if ($columnName == "Actions") { - // Create a direct link to edit the item - $link = Controller::join_links("item", $record->ID, "edit"); - $temp = $record; - // We need to traverse through all the parents to build the link - while ($temp->_Parent) { - $temp = $temp->_Parent; - $link = Controller::join_links("item", $temp->ID, "ItemEditForm", "field", "Child", $link); - } - $link = Controller::join_links($gridField->link(), $link); - $data = new ArrayData([ - 'Link' => $link - ]); - $editButton = $data->renderWith('SilverStripe\Forms\GridField\GridFieldEditButton'); - - // Create a button to add a new child element - // and save the allowed child classes on the button - $classes = $record->getAllowedPageElements(); - $elems = []; - foreach ($classes as $class) { - $elems[$class] = singleton($class)->singular_name(); - } - $addButton = GridField_FormAction::create( - $gridField, - "AddAction".$record->ID, - null, - null, - null - ); - $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); - $addButton->addExtraClass("col-actions__button add-button font-icon-plus"); - if (!count($elems)) { - $addButton->setDisabled(true); - } - $addButton->setButtonContent('Add'); - - // Create a button to delete and/or remove the element from the parent - $deleteButton = GridField_FormAction::create( - $gridField, - "DeleteAction".$record->ID, - null, - null, - null - ); - $deleteButton->setAttribute( - "data-used-count", - $record->Parents()->Count() + $record->getAllPages()->Count() - ); - $deleteButton->setAttribute( - "data-parent-id", - $record->_Parent ? $record->_Parent->ID : $this->parent->ID - ); - $deleteButton->setAttribute( - "data-parent-type", - $record->_Parent ? "element" : "section" - ); - - $deleteButton->addExtraClass("col-actions__button delete-button font-icon-trash-bin"); - - $deleteButton->setButtonContent('Delete'); - - return ViewableData::create()->customise([ - "EditButton" => $editButton, - "AddButton" => $addButton, - "DeleteButton" => $deleteButton, - ])->renderWith("GridFieldPageSectionsActionColumn"); - } - } - - public function getActions($gridField) { - return ["dotreenav"]; - } - - public function handleAction(GridField $gridField, $actionName, $arguments, $data) { - if ($actionName == "dotreenav") { - $elem = $arguments["element"]; - - // Check if we have children to show - if ($elem->Children()->Count() == 0) { - Controller::curr()->getResponse()->setStatusCode( - 200, - "This element has no children" - ); - return; - } - - // Change the internal GridField state to show the children - $state = $gridField->getState(true); - if ($this->isOpen($state, $elem)) { - $this->closeElement($state, $elem); - } else { - $this->openElement($state, $elem); - } - } - } - - // Check if an element is currently opened - private function isOpen($state, $element) { - $list = []; - $base = $element; - while ($base) { - $list[] = $base; - $base = $base->_Parent; - } - $list = array_reverse($list); - - $opens = $state->open; - foreach ($list as $item) { - if (!isset($opens->{$item->ID})) { - return false; - } - - $opens = $opens->{$item->ID}; - } - - return true; - } - // Open a specific element in the grid - private function openElement($state, $element) { - $list = []; - $base = $element; - while ($base) { - $list[] = $base; - $base = $base->_Parent; - } - $list = array_reverse($list); - - $i = 0; - $opens = $state->open; - foreach ($list as $item) { - if (!isset($opens->{$item->ID})) { - $opens->{$item->ID} = []; - } - - $opens = $opens->{$item->ID}; - } - } - // Close a specific element in the grid - private function closeElement($state, $element) { - $list = []; - $base = $element->_Parent; - while ($base) { - $list[] = $base; - $base = $base->_Parent; - } - $list = array_reverse($list); - - $opens = $state->open; - foreach ($list as $item) { - $opens = $opens->{$item->ID}; - } - - unset($opens->{$element->ID}); - } - - // Return the data list of the GridField with all opened elements filled in - public function getManipulatedData(GridField $gridField, SS_List $dataList) { - $list = $dataList->sort($this->getSortField())->toArray(); - - $state = $gridField->getState(true); - $opens = $state->open; - - $arr = []; - foreach ($list as $item) { - $item->_Level = 0; - $item->_Open = false; - - $this->getManipulatedItem($arr, $opens, $item, 1); - } - - return ArrayList::create($arr); - } - - private function getManipulatedItem(&$arr, $opens, $item, $level) { - $arr[] = $item; - - // We're done here if the item isn't open - if (!isset($opens->{$item->ID})) return; - - $sort = $this->getSortField(); - - // Get all children and insert them into the list - $children = $item->Children()->Sort($sort); - foreach ($children as $child) { - $child->_Level = $level; - $child->_Parent = $item; - $child->_Open = false; - - $this->getManipulatedItem($arr, $opens->{$item->ID}, $child, $level + 1); - } - - $item->_Open = true; - } - - public function handleReorder(GridField $gridField, HTTPRequest $request) { - $vars = $request->postVars(); - - $type = $vars["type"]; - $sort = intval($vars["sort"]); - - $item = PageElement::get()->byID($vars["id"]); - $parent = PageElement::get()->byID($vars["parent"]); - $newParent = PageElement::get()->byID($vars["newParent"]); - - // First check if the parent accepts the PageElement we're trying to move - $parentClass = ""; - if ($newParent) { - $allowed = in_array($item->ClassName, $newParent->getAllowedPageElements()); - $parentClass = $newParent->ClassName; - } else { - $allowed = in_array($item->ClassName, $this->parent->getAllowedPageElements()); - $parentClass = $this->parent->ClassName; - } - if (!$allowed) { - Controller::curr()->getResponse()->setStatusCode( - 400, - "The type " . $item->ClassName . " is not allowed as a child of " . $parentClass - ); - return $gridField->FieldHolder(); - } - - if ($parent) { - $parent->Children()->Remove($item); - } else { - $gridField->getList()->Remove($item); - } - - $num = $type == "before" ? -1 : 1; - $sortBy = $this->getSortField(); - $sortArr = [$sortBy => $sort + $num]; - - if ($type == "child") { - if ($newParent) { - $newParent->Children()->Add($item, $sortArr); - $newParent->write(); - } else { - $gridField->getList()->Add($item, $sortArr); - } - } else { - if ($newParent) { - $newParent->Children()->Add($item, $sortArr); - $newParent->write(); - } else { - $gridField->getList()->Add($item, $sortArr); - } - } - - return $gridField->FieldHolder(); - } - - public function handleAdd(GridField $gridField, HTTPRequest $request) { - $id = intval($request->postVar("id")); - $type = $request->postVar("type"); - - $obj = $gridField->getManipulatedList()->filter("ID", $id)->first(); - if (!$obj) { - Controller::curr()->getResponse()->setStatusCode( - 400, - "Could not find element in the current list" - ); - return $gridField->FieldHolder(); - } - if (!in_array($type, $obj->getAllowedPageElements())) { - Controller::curr()->getResponse()->setStatusCode( - 400, - "The type " . $type . " is not allowed as a child of " . $obj->ClassName - ); - return $gridField->FieldHolder(); - } - - $child = $type::create(); - $child->Name = "New " . $type; - $child->write(); - - $obj->Children()->Add($child); - - $state = $gridField->getState(true); - $this->openElement($state, $obj); - - return $gridField->FieldHolder(); - } - - public function handleRemove(GridField $gridField, HTTPRequest $request) { - $id = intval($request->postVar("id")); - $obj = DataObject::get_by_id(PageElement::class, $id); - - $parentId = intval($request->postVar("parentId")); - $parentType = $request->postVar("parentType"); - - // Detach it from this parent (from the page section if we're top level) - if ($parentType == "section") { - $gridField->getList()->Remove($obj); - } else { - $parentObj = DataObject::get_by_id(PageElement::class, $parentId); - $parentObj->Children()->Remove($obj); - } - - return $gridField->FieldHolder(); - } - - public function handleDelete(GridField $gridField, HTTPRequest $request) { - $id = intval($request->postVar("id")); - $obj = DataObject::get_by_id(PageElement::class, $id); - - // Close the element in case it's open to avoid errors - $state = $gridField->getState(true); - if ($this->isOpen($state, $obj)) { - $this->closeElement($state, $obj); - } - - // Delete the element - $obj->delete(); - - return $gridField->FieldHolder(); - } -} diff --git a/src/PageElement.php b/src/PageElement.php index 1a47d27..0f850b9 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -28,7 +28,8 @@ use UncleCheese\BetterButtons\Buttons\SaveAndClose; use SilverStripe\Forms\Tab; -class PageElement extends DataObject { +class PageElement extends DataObject +{ private static $table_name = "FLXLabs_PageSections_PageElement"; @@ -36,20 +37,35 @@ class PageElement extends DataObject { protected static $pluralName = "Elements"; protected static $defaultIsOpen = true; - public static function getSingularName() { + public static function getSingularName() + { return static::$singularName; } - public static function getPluralName() { + public static function getPluralName() + { return static::$pluralName; } - public static function isOpenByDefault() { + public static function isOpenByDefault() + { return static::$defaultIsOpen; } - function canView($member = null) { return true; } - function canEdit($member = null) { return true; } - function canDelete($member = null) { return true; } - function canCreate($member = null, $context = []) { return true; } + public function canView($member = null) + { + return true; + } + public function canEdit($member = null) + { + return true; + } + public function canDelete($member = null) + { + return true; + } + public function canCreate($member = null, $context = []) + { + return true; + } private static $can_be_root = true; @@ -100,17 +116,26 @@ function canCreate($member = null, $context = []) { return true; } ); // Returns all page element classes, without the base class - public static function getAllPageElementClasses() { + public static function getAllPageElementClasses() + { $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); $classes = array_diff($classes, [PageElement::class]); return $classes; } - public function getAllowedPageElements() { + /** + * The classes of allowed child elements + * + * Gets a list of classnames which are valid child elements of this PageElement. + * @return string[] + */ + public function getAllowedPageElements() + { return self::getAllPageElementClasses(); } - public function onBeforeWrite() { + public function onBeforeWrite() + { parent::onBeforeWrite(); $elems = $this->Children()->Sort("SortOrder")->Column("ID"); @@ -120,7 +145,8 @@ public function onBeforeWrite() { } } - public function onAfterWrite() { + public function onAfterWrite() + { parent::onAfterWrite(); if (Versioned::get_stage() == Versioned::DRAFT) { @@ -131,7 +157,8 @@ public function onAfterWrite() { } } - public function onAfterDelete() { + public function onAfterDelete() + { parent::onAfterDelete(); if (Versioned::get_stage() == Versioned::DRAFT) { @@ -142,37 +169,35 @@ public function onAfterDelete() { } } - public function getChildrenGridField() { - $addNewButton = new GridFieldAddNewMultiClass(); - $addNewButton->setClasses($this->getAllowedPageElements()); - - $list = PageElement::get() - ->exclude("ID", $this->getParentIDs()) - ->filter("ClassName", $this->owner->getAllowedPageElements()); - $autoCompl = new GridFieldAddExistingSearchButton('buttons-before-right'); - $autoCompl->setSearchList($list); - - $config = GridFieldConfig::create() - ->addComponent(new GridFieldButtonRow("before")) - ->addComponent(new GridFieldToolbarHeader()) - ->addComponent($dataColumns = new GridFieldDataColumns()) - ->addComponent($autoCompl) - ->addComponent($addNewButton) - ->addComponent(new GridFieldPageSectionsExtension($this)) - ->addComponent(new GridFieldDetailForm()) - ->addComponent(new GridFieldFooter()); - $dataColumns->setFieldCasting(["GridFieldPreview" => "HTMLText->RAW"]); - - return GridField::create("Child", "Children", $this->Children(), $config); + /** + * Gets the TreeView for the children of this PageElement + * @return \FLXLabs\PageSections\TreeView + */ + public function getChildrenTreeView() + { + return new TreeView("Child", "Children", $this->Children()); } - public function getGridFieldPreview() { + /** + * Gets the preview of this PageElement in the GridField. + * @return string + */ + public function getGridFieldPreview() + { return $this->Name; } - // Gets all the pages that this page element is on, plus adds a __PageSection - // attribute to the page object so we know which section this element is in. - public function getAllPages() { + /** + * Gets all pages that this PageElement is rendered on. + * + * Adds the following properties to each page: + * __PageSection: The PageSection on the page that the PageElement is from. + * __PageElementVersion: The version of the PageElement being used. + * __PageElementPublishedVersion: The published version of the PageElement being used. + * @return \Page[] + */ + public function getAllPages() + { $pages = ArrayList::create(); foreach ($this->PageSections() as $section) { @@ -181,9 +206,12 @@ public function getAllPages() { Versioned::set_stage(Versioned::LIVE); $pubSection = DataObject::get_by_id($section->ClassName, $section->ID); $pubElem = $pubSection ? $pubSection->Elements()->filter("ID", $this->ID)->First() : null; + + // Add extra info to pages $page->__PageSection = $section; $page->__PageElementVersion = $section->Elements()->filter("ID", $this->ID)->First()->Version; $page->__PageElementPublishedVersion = $pubElem ? $pubElem->Version : "Not published"; + Versioned::set_stage($stage); $pages->add($page); } @@ -191,7 +219,21 @@ public function getAllPages() { return $pages; } - public function getCMSFields() { + /** + * Recursively gets all the parents of this element, in no particular order. + * @return \FLXLabs\PageSections\PageElement[] + */ + public function getAllParents() + { + $parents = ArrayList::create($this->Parents()->toList()); + foreach ($this->Parents() as $parent) { + $parents->merge($parent->getAllParents()); + } + return $parents; + } + + public function getCMSFields() + { $fields = parent::getCMSFields(); $fields->removeByName('Pages'); $fields->removeByName('Parents'); @@ -200,7 +242,10 @@ public function getCMSFields() { $fields->removeByName("Children"); if ($this->ID && count($this->getAllowedPageElements()) > 0) { - $fields->insertAfter("Main", Tab::create("Child", "Children", $this->getChildrenGridField())); + $fields->insertAfter( + "Main", + Tab::create("Child", "Children", $this->getChildrenTreeView()) + ); } // Add our newest version as a readonly field @@ -235,22 +280,37 @@ public function getCMSFields() { return $fields; } - public function getParentIDs() { + /** + * Gets the list of all parents of this PageElement. + * @return string[] + */ + public function getParentIDs() + { $IDArr = [$this->ID]; - foreach($this->Parents() as $parent) { + foreach ($this->Parents() as $parent) { $IDArr = array_merge($IDArr, $parent->getParentIDs()); } return $IDArr; } - public function renderChildren($parents = null) { + /** + * Renders the children of this PageElement + * @param string[] $parents The list of parent IDs of this PageElement + * @return string + */ + public function renderChildren($parents = null) + { return $this->renderWith( "RenderChildren", - ["Elements" => $this->Children(), "ParentList" => strval($this->ID) . "," . $parents] + [ + "Elements" => $this->Children(), + "ParentList" => strval($this->ID) . "," . $parents, + ] ); } - public function forTemplate($parentList = "") { + public function forTemplate($parentList = "") + { $parents = ArrayList::create(); $splits = explode(",", $parentList); $num = count($splits); @@ -261,22 +321,29 @@ public function forTemplate($parentList = "") { return $this->renderWith( array_reverse($this->getClassAncestry()), - ["ParentList" => $parentList, "Parents" => $parents, "Page" => $page] + [ + "ParentList" => $parentList, + "Parents" => $parents, + "Page" => $page, + ] ); } - public function replaceDefaultButtons() { + public function replaceDefaultButtons() + { return true; } - public function getBetterButtonsUtils() { + public function getBetterButtonsUtils() + { $fieldList = FieldList::create([ PrevNext::create(), ]); return $fieldList; } - public function getBetterButtonsActions() { + public function getBetterButtonsActions() + { $actions = FieldList::create([ SaveAndClose::create(), CustomAction::create('publishOnAllPages', 'Publish on all pages') @@ -285,7 +352,11 @@ public function getBetterButtonsActions() { return $actions; } - public function publishOnAllPages() { + /** + * Publishes all pages that this PageElement is on. + */ + public function publishOnAllPages() + { foreach ($this->getAllPages() as $page) { $page->publish(Versioned::get_stage(), Versioned::LIVE); } diff --git a/src/PageElementSelfRel.php b/src/PageElementSelfRel.php index dd67661..f8c5e99 100644 --- a/src/PageElementSelfRel.php +++ b/src/PageElementSelfRel.php @@ -1,49 +1,52 @@ - "Int", - ); - - private static $has_one = array( - "Parent" => PageElement::class, - "Child" => PageElement::class, - ); - - public function onBeforeWrite() { - parent::onBeforeWrite(); - - if (!$this->ID) { - if (!$this->SortOrder) { - // Add new elements at the end (highest SortOrder) - $this->SortOrder = ($this->Parent()->Children()->Count() + 1) * 2; - } - } - } - - public function onAfterWrite() { - parent::onAfterWrite(); - - if (!$this->__NewOrder && Versioned::get_stage() == Versioned::DRAFT) { - $this->Parent()->__Counter++; - $this->Parent()->write(); - } - } - - public function onAfterDelete() { - parent::onAfterDelete(); - - if (Versioned::get_stage() == Versioned::DRAFT) { - $this->Parent()->__Counter++; - $this->Parent()->write(); - } - } -} + "Int", + ); + + private static $has_one = array( + "Parent" => PageElement::class, + "Child" => PageElement::class, + ); + + public function onBeforeWrite() + { + parent::onBeforeWrite(); + + if (!$this->ID) { + if (!$this->SortOrder) { + // Add new elements at the end (highest SortOrder) + $this->SortOrder = ($this->Parent()->Children()->Count() + 1) * 2; + } + } + } + + public function onAfterWrite() + { + parent::onAfterWrite(); + + if (!$this->__NewOrder && Versioned::get_stage() == Versioned::DRAFT) { + $this->Parent()->__Counter++; + $this->Parent()->write(); + } + } + + public function onAfterDelete() + { + parent::onAfterDelete(); + + if (Versioned::get_stage() == Versioned::DRAFT) { + $this->Parent()->__Counter++; + $this->Parent()->write(); + } + } +} diff --git a/src/PageSection.php b/src/PageSection.php index a5deefd..8c11809 100644 --- a/src/PageSection.php +++ b/src/PageSection.php @@ -18,8 +18,8 @@ use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass; -class PageSection extends DataObject { - +class PageSection extends DataObject +{ private static $table_name = "FLXLabs_PageSections_PageSection"; private static $db = [ @@ -46,7 +46,8 @@ class PageSection extends DataObject { ] ]; - public function onBeforeWrite() { + public function onBeforeWrite() + { parent::onBeforeWrite(); $elems = $this->Elements()->Sort("SortOrder")->Column("ID"); @@ -56,7 +57,8 @@ public function onBeforeWrite() { } } - public function onAfterWrite() { + public function onAfterWrite() + { parent::onAfterWrite(); if (!$this->__isNew && Versioned::get_stage() == Versioned::DRAFT) { @@ -65,7 +67,8 @@ public function onAfterWrite() { } } - public function forTemplate() { + public function forTemplate() + { return $this->Elements()->Count(); $actions = FieldList::create(); @@ -87,8 +90,12 @@ public function forTemplate() { return $form->forTemplate(); } - // Gets the name of this section from the page it is on - public function getName() { + /** + * Gets the name of this PageSection + * @return string + */ + public function getName() + { $page = $this->Page(); // TODO: Find out why this happens if (!method_exists($page, "getPageSectionNames")) { @@ -102,7 +109,15 @@ public function getName() { return null; } - public function getAllowedPageElements($section = "Main") { + /** + * The classes of allowed child elements + * + * Gets a list of classnames which are valid child elements of this PageSection. + * @param string $section The section for which to get the allowed child classes. + * @return string[] + */ + public function getAllowedPageElements($section = "Main") + { return $this->Page()->getAllowedPageElements($section); } } diff --git a/src/PageSectionPageElementRel.php b/src/PageSectionPageElementRel.php index bd4a867..bca6363 100644 --- a/src/PageSectionPageElementRel.php +++ b/src/PageSectionPageElementRel.php @@ -5,8 +5,8 @@ use SilverStripe\ORM\DataObject; use SilverStripe\Versioned\Versioned; -class PageSectionPageElementRel extends DataObject { - +class PageSectionPageElementRel extends DataObject +{ private static $table_name = "FLXLabs_PageSections_PageSectionPageElementRel"; private static $db = array( @@ -18,7 +18,8 @@ class PageSectionPageElementRel extends DataObject { "Element" => PageElement::class, ); - public function onBeforeWrite() { + public function onBeforeWrite() + { parent::onBeforeWrite(); if (!$this->ID) { @@ -29,7 +30,8 @@ public function onBeforeWrite() { } } - public function onAfterWrite() { + public function onAfterWrite() + { parent::onAfterWrite(); if (!$this->__NewOrder && Versioned::get_stage() == Versioned::DRAFT) { @@ -38,7 +40,8 @@ public function onAfterWrite() { } } - public function onAfterDelete() { + public function onAfterDelete() + { parent::onAfterDelete(); if (Versioned::get_stage() == Versioned::DRAFT) { diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 31550b4..980b7f8 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -19,10 +19,11 @@ use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass; use Symbiote\GridFieldExtensions\GridFieldAddExistingSearchButton; -class PageSectionsExtension extends DataExtension { - +class PageSectionsExtension extends DataExtension +{ // Generate the needed relations on the class - public static function get_extra_config($class = null, $extensionClass = null) { + public static function get_extra_config($class = null, $extensionClass = null) + { $has_one = []; $owns = []; $cascade_deletes = []; @@ -55,13 +56,26 @@ public static function get_extra_config($class = null, $extensionClass = null) { ]; } - public function getAllowedPageElements($section = "Main") { + /** + * The classes of allowed child elements + * + * Gets a list of classnames which are valid child elements of this PageSection. + * @param string $section The section for which to get the allowed child classes. + * @return string[] + */ + public function getAllowedPageElements($section = "Main") + { $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); $classes = array_diff($classes, [PageElement::class]); return $classes; } - public function getPageSectionNames() { + /** + * Gets a list of the PageSection names of this page. + * @return string[] + */ + public function getPageSectionNames() + { $sections = Config::inst()->get($this->owner->ClassName, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); if (!$sections) { $sections = ["Main"]; @@ -69,7 +83,8 @@ public function getPageSectionNames() { return $sections; } - public function onBeforeWrite() { + public function onBeforeWrite() + { parent::onBeforeWrite(); if ($this->owner->ID) { @@ -93,7 +108,8 @@ public function onBeforeWrite() { } } - public function updateCMSFields(FieldList $fields) { + public function updateCMSFields(FieldList $fields) + { $sections = $this->owner->config()->get("page_sections"); if (!$sections) { $sections = ["Main"]; @@ -105,33 +121,18 @@ public function updateCMSFields(FieldList $fields) { $fields->removeByName($name); if ($this->owner->ID) { - $addNewButton = new GridFieldAddNewMultiClass(); - $addNewButton->setClasses($this->owner->getAllowedPageElements()); - - $list = PageElement::get()->filter("ClassName", $this->owner->getAllowedPageElements()); - $addExistingButton = new GridFieldAddExistingSearchButton('buttons-before-right'); - $addExistingButton->setSearchList($list); - - $config = GridFieldConfig::create() - ->addComponent(new GridFieldButtonRow("before")) - ->addComponent(new GridFieldToolbarHeader()) - ->addComponent($dataColumns = new GridFieldDataColumns()) - ->addComponent($addExistingButton) - ->addComponent($addNewButton) - ->addComponent(new GridFieldPageSectionsExtension($this->owner)) - ->addComponent(new GridFieldDetailForm()); - $dataColumns - ->setFieldCasting(["GridFieldPreview" => "HTMLText->RAW"]) - ->setDisplayFields([ - "GridFieldPreview" => "Preview", - ]); - $f = new GridField($name, $section, $this->owner->$name()->Elements(), $config); - $fields->addFieldToTab("Root.PageSections.{$section}", $f); + $tv = new TreeView($name, $section, $this->owner->$name()); + $fields->addFieldToTab("Root.PageSections.{$section}", $tv); } } } - public function RenderPageSection($name = "Main") { + /** + * Renders the PageSection of this page. + * @param string $name The name of the PageSection to render + */ + public function RenderPageSection($name = "Main") + { $elements = $this->owner->{"PageSection" . $name}()->Elements()->Sort("SortOrder"); return $this->owner->renderWith( "RenderChildren", @@ -139,7 +140,12 @@ public function RenderPageSection($name = "Main") { ); } - public function getPublishState() { + /** + * Gets the published state of the page this PageSection belongs to. + * @return \SilverStripe\ORM\FieldType\DBField + */ + public function getPublishState() + { return DBField::create_field("HTMLText", $this->owner->latestPublished() ? "Published" : "Draft"); } } diff --git a/src/TreeView.php b/src/TreeView.php new file mode 100644 index 0000000..fcc4e34 --- /dev/null +++ b/src/TreeView.php @@ -0,0 +1,702 @@ +parent = $parent; + $this->context = singleton(PageElement::class)->getDefaultSearchContext(); + + // Open default elements + $this->opens = new \stdClass(); + foreach ($this->getItems() as $item) { + $this->openRecursive($item); + } + } + + /** + * Recursively opens an item + * + * Recursively opens items if they have children and ->isOpenByDefault() returns true + */ + private function openRecursive($item, $parents = []) + { + if ($item->isOpenByDefault() && $item->Children()->Count()) { + $this->openItem( + array_merge( + array_map(function ($e) { + return $e->ID; + }, $parents), + [$item->ID] + ) + ); + foreach ($item->Children() as $child) { + $this->openRecursive($child, array_merge($parents, [$item])); + } + } + } + + /** + * Extracts info from an incoming request + * @param \SilverStripe\Control\HTTPRequest $request + * @return array + */ + private function pre($request) + { + $data = $request->requestVars(); + + // Protection against CSRF attacks + $token = $this->getForm()->getSecurityToken(); + if (!$token->checkRequest($request)) { + $this->httpError(400, _t( + "SilverStripe\\Forms\\Form.CSRF_FAILED_MESSAGE", + "There seems to have been a technical problem. Please click the back button, " . + "refresh your browser, and try again." + )); + return; + } + + // Restore state from session + $session = $request->getSession(); + if (isset($data["state"])) { + $this->opens = $session->get($data["state"]); + } + + return $data; + } + + public function index($request) + { + $this->pre($request); + return $this->FieldHolder(); + } + + /** + * This action is called when opening or closing an element in the tree + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function tree($request) + { + $data = $this->pre($request); + if (!$data) { + return $this->FieldHolder(); + } + + if (!isset($data["itemId"]) || !isset($data["parents"])) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "Missing required data!" + ); + return $this->FieldHolder(); + } + + $itemId = intval($data["itemId"]); + $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); + $path = array_merge($parents, [$itemId]); + + if ($this->isOpen($path)) { + $this->closeItem($path); + } else { + $this->openItem($path); + } + + return $this->FieldHolder(); + } + + /** + * This action is called when an element in the tree is moved to another spot + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function move($request) + { + $data = $this->pre($request); + if (!$data) { + return $this->FieldHolder(); + } + + if (!isset($data["itemId"]) || !isset($data["parents"]) || + !isset($data["newParent"]) || !isset($data["sort"])) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "Missing required data!" + ); + return $this->FieldHolder(); + } + + $itemId = intval($data["itemId"]); + $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); + $path = array_merge($parents, [$itemId]); + + $item = PageElement::get()->byID($itemId); + + // Get the new parent + $newParentId = $data["newParent"]; + if ($newParentId) { + $newParent = PageElement::get()->byID($newParentId); + } else { + $newParent = $this->parent; + } + + // Check if this element is allowed as a child on the new element + $allowed = in_array($item->ClassName, $newParent->getAllowedPageElements()); + if (!$allowed) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "The type " . $item->ClassName . " is not allowed as a child of " . $newParent->ClassName + ); + return $gridField->FieldHolder(); + } + + // Remove the element from the current parent + if (count($parents) == 0) { + $this->getItems()->removeByID($itemId); + } else { + $parent = PageElement::get()->byID($parents[count($parents) - 1]); + $parent->Children()->removeByID($itemId); + } + + $sort = intval($data["sort"]); + $sortBy = $this->getSortField(); + $sortArr = [$sortBy => $sort]; + + // Add the element to the new parent + if (is_subclass_of($newParent, PageSection::class)) { + $newParent->Elements()->Add($item, $sortArr); + } else { + $newParent->Children()->Add($item, $sortArr); + } + + return $this->FieldHolder(); + } + + /** + * This action is called when adding a new or an existing item + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function add($request) + { + $data = $this->pre($request); + if (!$data) { + return $this->FieldHolder(); + } + + if (!isset($data["type"]) && !isset($data["id"])) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "Missing required data!" + ); + return $this->FieldHolder(); + } + + // If we have an id then add an existing item... + if (isset($data["id"])) { + $element = PageElement::get()->byID($data["id"]); + if (!$element) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "Could not find PageElement with id " . $data['id'] + ); + return $this->FieldHolder(); + } + + $this->getItems()->Add($element); + } else { + // ...otherwise add a completely new item + $itemId = isset($data["itemId"]) ? intval($data["itemId"]) : null; + $type = $data["type"]; + + $child = $type::create(); + $child->Name = "New " . $type; + + // If we have an itemId then we're adding to another element + // otherwise we're adding to the root + if ($itemId) { + $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); + $path = array_merge($parents, [$itemId]); + + $item = PageElement::get()->byID($itemId); + if (!in_array($type, $item->getAllowedPageElements())) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "The type " . $type . " is not allowed as a child of " . $item->ClassName + ); + return $this->FieldHolder(); + } + + $child->write(); + $item->Children()->Add($child); + + // Make sure we can see the child + $this->openItem(array_merge($path, [$item->ID])); + } else { + $child->write(); + $this->getItems()->Add($child); + } + } + + return $this->FieldHolder(); + } + + /** + * Action called when an item is removed from the TreeView + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function remove($request) + { + $data = $this->pre($request); + if (!$data) { + return $this->FieldHolder(); + } + + if (!isset($data["itemId"]) || !isset($data["parents"])) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "Missing required data!" + ); + return $this->FieldHolder(); + } + + $itemId = intval($data["itemId"]); + $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); + + // We only need the parent directly above the child, all parents further up don't matter, + // because the relations are duplicated if the item is duplicated. + // If we have no parents then we're removing it from the root + if (count($parents) == 0) { + $this->getItems()->removeByID($itemId); + } else { + $parent = PageElement::get()->byID($parents[count($parents) - 1]); + $parent->Children()->removeByID($itemId); + } + + return $this->FieldHolder(); + } + + /** + * This action is called when an element is deleted + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function delete($request) + { + $data = $this->pre($request); + if (!$data) { + return $this->FieldHolder(); + } + + if (!isset($data["itemId"]) || !isset($data["parents"])) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "Missing required data!" + ); + return $this->FieldHolder(); + } + + $itemId = intval($data["itemId"]); + $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); + $path = array_merge($parents, [$itemId]); + + $item = PageElement::get()->byID($itemId); + + // Close the element in case it's open to avoid errors + $this->closeItem($path); + + // Delete the element + $item->delete(); + + return $this->FieldHolder(); + } + + /** + * Creates the search form for adding existing elements + * @return \SilverStripe\Forms\Form + */ + public function SearchForm() + { + $form = Form::create( + $this, + 'doSearch', + $this->context->getFields(), + FieldList::create( + FormAction::create('doSearch', _t('GridFieldExtensions.SEARCH', 'Search')) + ->setUseButtonTag(true) + ->addExtraClass('btn btn-primary font-icon-search') + ) + ); + $form->addExtraClass('stacked add-existing-search-form form--no-dividers'); + $form->setFormMethod('GET'); + return $form; + } + + public function find($request) + { + return $this->SearchForm()->forTemplate(); + } + + public function doSearch($request) + { + $list = $this->context->getQuery($request->requestVars(), false, false); + // If we're viewing the search list on a PageElement, + // then we have to remove all parents as possible elements + if ($this->parent->ClassName === PageElement::class) { + $list = $list->subtract($this->parent->getAllParents()); + } + $list = new PaginatedList($list, $request); + $data = $this->customise([ + 'SearchForm' => $this->SearchForm(), + 'Items' => $list + ]); + return $data->renderWith("FLXLabs\PageSections\TreeViewFindExistingForm"); + } + + /** + * Get base items + * + * Gets all the top level items of this TreeView. + * @return \SilverStripe\ORM\ArrayList + */ + public function getItems() + { + return $this->parent->ClassName == PageSection::class ? + $this->parent->Elements() : $this->parent->Children(); + } + + /** + * Gets the sort field + * + * Gets the name of the field by which items are sorted. + * @return string + */ + public function getSortField() + { + return $this->sortField; + } + + /** + * Gets the directory name of this module + * + * @return string + */ + public static function getModuleDir() + { + return basename(dirname(__DIR__)); + } + + /** + * Renders this TreeView as an HTML tag + * @param array $properties The additional properties for the TreeView + * @return string + */ + public function FieldHolder($properties = array()) + { + $moduleDir = self::getModuleDir(); + Requirements::css($moduleDir . "/css/TreeView.css"); + Requirements::javascript($moduleDir . "/javascript/TreeView.js"); + Requirements::add_i18n_javascript($moduleDir . '/javascript/lang', false, true); + + // Ensure $id doesn't contain only numeric characters + $sessionId = 'ps_tv_' . substr(md5(serialize($this->opens)), 0, 8); + $session = Controller::curr()->getRequest()->getSession(); + $session->set($sessionId, $this->opens); + + $content = ''; + + $classes = $this->parent->getAllowedPageElements(); + $elems = []; + foreach ($classes as $class) { + $elems[$class] = $class::getSingularName(); + } + + // Create the add new multi class button + $addNew = DropdownField::create('AddNewClass', '', $elems); + $content .= ArrayData::create([ + "Title" => "Add new", + "ClassField" => $addNew + ])->renderWith("\FLXLabs\PageSections\TreeViewAddNewButton"); + + // Create the find existing button + $findExisting = TreeViewFormAction::create($this, 'FindExisting', 'Find existing'); + $findExisting->addExtraClass("tree-actions-findexisting"); + $content .= $findExisting->forTemplate(); + + $list = $this->getItems()->sort($this->sortField)->toArray(); + + $first = true; + foreach ($list as $item) { + $content .= $this->renderTree($item, [], $this->opens, $first); + $first = false; + } + + return HTML::createTag( + 'fieldset', + [ + 'class' => 'treeview-pagesections pagesection-' . $this->getName(), + 'data-name' => $this->getName(), + 'data-url' => $this->Link(), + 'data-state-id' => $sessionId, + ], + $content + ); + } + + public function Field($properties = array()) + { + return $this->FieldHolder($properties); + } + + /** + * Renders an item tree + * + * Renders the specified item and it's children + * @param PageElement $item The item to render + * @param string[] $parents The hierarchy of parents this item is a child of + * @param \Stdclass $opens The local open state for the item + * @param boolean $isFirst True if this is the first item of the direct parent, false otherwise + */ + private function renderTree($item, $parents, $opens, $isFirst) + { + $childContent = null; + $level = count($parents) + 1; + $isOpen = isset($opens->{$item->ID}) && $item->Children()->Count() > 0; + + // Render children if we are open + if ($isOpen) { + $children = $item->Children()->Sort($this->sortField); + $first = true; + foreach ($children as $child) { + $childContent .= $this->renderTree( + $child, + array_merge($parents, [$item]), + $opens->{$item->ID}, + $first + ); + $first = false; + } + } + + // Construct the array of all allowed child elements + $classes = $item->getAllowedPageElements(); + $elems = []; + foreach ($classes as $class) { + $elems[$class] = $class::getSingularName(); + } + + // Find out if this item is allowed as a root item + // There are two cases, either this GridField is on a page, + // or it is on a PageElement and we're looking that the children + $parentClasses = $this->parent->getAllowedPageElements(); + $isAllowedRoot = in_array($item->ClassName, $parentClasses); + + // Get the list of parents of this element as an array of ids + // (already converted to json/a string) + $tree = "[" . + implode( + ',', + array_map(function ($item) { + return $item->ID; + }, + $parents) + ) . + "]"; + + // Create the tree icon + $icon = ''; + if ($item->Children() && $item->Children()->Count() > 0) { + $icon = ($isOpen === true ? 'font-icon-down-open' : 'font-icon-right-open'); + } + + // Create a button to edit the item + $editButton = TreeViewFormAction::create( + $this, + "DeleteAction".$item->ID, + null, + null, + null + ); + $editButton->addExtraClass("col-actions__button edit-button"); + $editButton->setButtonContent('Edit'); + + // Create a button to add a new child element + // and save the allowed child classes on the button + $classes = $item->getAllowedPageElements(); + $elems = []; + foreach ($classes as $class) { + $elems[$class] = singleton($class)->singular_name(); + } + $addButton = TreeViewFormAction::create( + $this, + "AddAction".$item->ID, + null, + null, + null + ); + $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); + $addButton->addExtraClass("col-actions__button add-button font-icon-plus"); + if (!count($elems)) { + $addButton->setDisabled(true); + } + $addButton->setButtonContent('Add'); + + // Create a button to delete and/or remove the element from the parent + $deleteButton = TreeViewFormAction::create( + $this, + "DeleteAction".$item->ID, + null, + null, + null + ); + $deleteButton->setAttribute( + "data-used-count", + $item->Parents()->Count() + $item->getAllPages()->Count() + ); + $deleteButton->setAttribute( + "data-parent-id", + $item->_Parent ? $item->_Parent->ID : $this->parent->ID + ); + $deleteButton->addExtraClass("col-actions__button delete-button font-icon-trash-bin"); + $deleteButton->setButtonContent('Delete'); + + // Create the tree icon + $icon = ''; + if ($item->Children() && $item->Children()->Count() > 0) { + $icon = ($isOpen === true ? 'font-icon-down-open' : 'font-icon-right-open'); + } + + // Create the tree field + $treeButton = TreeViewFormAction::create( + $this, + "TreeNavAction".$item->ID, + null, + "dotreenav", + ["element" => $item] + ); + $treeButton->addExtraClass("tree-button " . ($isOpen ? "is-open" : "is-closed")); + if (!$item->Children()->Count()) { + $treeButton->addExtraClass(" is-end"); + $treeButton->setDisabled(true); + } + $treeButton->addExtraClass($icon); + $treeButton->setButtonContent(' '); + + return ArrayData::create([ + "ID" => $item->ID, + "ClassName" => $item->ClassName, + "Name" => $item->Name, + "Tree" => $tree, + "SortOrder" => $item->SortOrder, + "IsOpen" => $isOpen, + "IsFirst" => $isFirst, + "Children" => $childContent, + "AllowedRoot" => $isAllowedRoot, + "AllowedElements" => json_encode($elems, JSON_UNESCAPED_UNICODE), + "TreeButton" => $treeButton, + "EditButton" => $editButton, + "AddButton" => $addButton, + "DeleteButton" => $deleteButton, + "UsedCount" => $item->Parents()->Count() + $item->getAllPages()->Count(), + ])->renderWith("\FLXLabs\PageSections\TreeViewPageElement"); + } + + /** + * Checks if the specified item is open + * + * @param string[] $path The hierarchy of item ids, the last being the item to check. + * @return boolean + */ + private function isOpen($path) + { + $opens = $this->opens; + foreach ($path as $itemId) { + if (!isset($opens->{$itemId})) { + return false; + } + + $opens = $opens->{$itemId}; + } + + return true; + } + + /** + * Opens an item + * + * Opens the item at the specified path + * @param string[] $path The hierarchy of item ids, the last being the item to open. + */ + private function openItem($path) + { + $opens = $this->opens; + foreach ($path as $itemId) { + if (!isset($opens->{$itemId})) { + $opens->{$itemId} = new \stdClass(); + } + + $opens = $opens->{$itemId}; + } + } + + /** + * Closes an item + * + * Closes the item at the specified path + * @param string[] $path The hierarchy of item ids, the last being the item to close. + */ + private function closeItem($path) + { + $opens = $this->opens; + for ($i = 0; $i < count($path) - 1; $i++) { + if (!isset($opens->{$path[$i]})) { + return; + } + + $opens = $opens->{$path[$i]}; + } + + unset($opens->{$path[count($path) - 1]}); + } +} diff --git a/src/TreeViewFormAction.php b/src/TreeViewFormAction.php new file mode 100644 index 0000000..45b1475 --- /dev/null +++ b/src/TreeViewFormAction.php @@ -0,0 +1,20 @@ +treeView = $treeView; + + parent::__construct($name, $title); + } +} diff --git a/templates/FLXLabs/PageSections/TreeViewAddNewButton.ss b/templates/FLXLabs/PageSections/TreeViewAddNewButton.ss new file mode 100644 index 0000000..4d9788c --- /dev/null +++ b/templates/FLXLabs/PageSections/TreeViewAddNewButton.ss @@ -0,0 +1,5 @@ +
                              + $ClassField.FieldHolder + + +
                              diff --git a/templates/FLXLabs/PageSections/TreeViewFindExistingForm.ss b/templates/FLXLabs/PageSections/TreeViewFindExistingForm.ss new file mode 100644 index 0000000..969fc94 --- /dev/null +++ b/templates/FLXLabs/PageSections/TreeViewFindExistingForm.ss @@ -0,0 +1,56 @@ +$SearchForm + +

                              <%t GridFieldExtensions.RESULTS "Results" %>

                              +
                              + <% if $Items %> +
                                + <% loop $Items %> +
                              • + $Title +
                              • + <% end_loop %> +
                              + <% else %> +

                              <%t GridFieldExtensions.NOITEMS "There are no items." %>

                              + <% end_if %> + + <% if $Items.MoreThanOnePage %> + + <% end_if %> +
                              diff --git a/templates/FLXLabs/PageSections/TreeViewFormAction.ss b/templates/FLXLabs/PageSections/TreeViewFormAction.ss new file mode 100644 index 0000000..fd52ccc --- /dev/null +++ b/templates/FLXLabs/PageSections/TreeViewFormAction.ss @@ -0,0 +1,3 @@ + diff --git a/templates/FLXLabs/PageSections/TreeViewPageElement.ss b/templates/FLXLabs/PageSections/TreeViewPageElement.ss new file mode 100644 index 0000000..1e2f55c --- /dev/null +++ b/templates/FLXLabs/PageSections/TreeViewPageElement.ss @@ -0,0 +1,40 @@ +
                              +
                              +
                              + <% if IsFirst %> +
                              + <% end_if %> +
                              +
                              +
                              +
                              + $TreeButton + $AddButton + $DeleteButton + $EditButton +
                              +
                              + $ButtonField +
                              +
                              + $ClassName (ID: $ID, {$UsedCount}x) +
                              +
                              + $Name +
                              +
                              +
                              +
                              +
                              + $Children.RAW +
                              +
                              diff --git a/templates/GridFieldDragHandle.ss b/templates/GridFieldDragHandle.ss deleted file mode 100755 index b59a1bf..0000000 --- a/templates/GridFieldDragHandle.ss +++ /dev/null @@ -1 +0,0 @@ -
                              diff --git a/templates/GridFieldPageElement.ss b/templates/GridFieldPageElement.ss deleted file mode 100755 index 1b87b4c..0000000 --- a/templates/GridFieldPageElement.ss +++ /dev/null @@ -1,11 +0,0 @@ -
                              - $ButtonField -
                              -
                              - $ClassName (ID: {$ID}, {$UsedCount}x) -
                              -
                              - $Title -
                              -
                              -
                              diff --git a/templates/GridFieldPageSectionsActionColumn.ss b/templates/GridFieldPageSectionsActionColumn.ss deleted file mode 100644 index 6c45bc9..0000000 --- a/templates/GridFieldPageSectionsActionColumn.ss +++ /dev/null @@ -1,5 +0,0 @@ -
                              - $AddButton - $DeleteButton - $EditButton -
                              From 8aa1ebb78971716fd0be7fd9d40aa3fc9d6a070d Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Thu, 19 Jul 2018 17:37:41 +0200 Subject: [PATCH 20/82] feat(treeview): Implement detail form --- css/TreeView.css | 4 + javascript/TreeView.js | 616 ++++++++++-------- src/PageElement.php | 23 +- src/TreeView.php | 148 ++++- .../PageSections/TreeViewPageElement.ss | 16 +- 5 files changed, 485 insertions(+), 322 deletions(-) diff --git a/css/TreeView.css b/css/TreeView.css index 03c9c1e..cfc9a67 100644 --- a/css/TreeView.css +++ b/css/TreeView.css @@ -120,6 +120,10 @@ display: none; } +.treeview-item-fill { + flex: 1; +} + /* * Add new button */ diff --git a/javascript/TreeView.js b/javascript/TreeView.js index f7b6bfc..bbfa99f 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -1,319 +1,392 @@ (function ($) { - $.entwine("ss", function ($) { + $.entwine('ss', function ($) { // Hide our custom context menu when not needed - $(document).on("mousedown", function (event) { - $parents = $(event.target).parents(".treeview-menu"); + $(document).on('mousedown', function (event) { + $parents = $(event.target).parents('.treeview-menu'); if ($parents.length == 0) { - $(".treeview-menu").remove(); + $('.treeview-menu').remove(); } }); - // Load our search form when opening the search dialog - $(".add-existing-search-dialog").entwine({ + // Show our search form after opening the search dialog + // Show our detail form after opening the detail dialog + $('.add-existing-search-dialog, .view-detail-dialog').entwine({ loadDialog: function (deferred) { - var dialog = this.addClass("loading").children(".ui-dialog-content").empty(); + var dialog = this.addClass('loading') + .children('.ui-dialog-content') + .empty(); deferred.done(function (data) { - dialog.html(data).parent().removeClass("loading"); + dialog + .html(data) + .parent() + .removeClass('loading'); }); } }); // Submit our search form to our own endpoint and show the results - $(".add-existing-search-dialog .add-existing-search-form").entwine({ + $('.add-existing-search-dialog .add-existing-search-form').entwine({ onsubmit: function () { - this.closest(".add-existing-search-dialog").loadDialog($.get( - this.prop("action"), this.serialize() - )); + this.closest('.add-existing-search-dialog').loadDialog( + $.get(this.prop('action'), this.serialize()) + ); return false; } }); // Allow clicking the elements in the search form - $('.add-existing-search-dialog .add-existing-search-items .list-group-item-action').entwine({ + $( + '.add-existing-search-dialog .add-existing-search-items .list-group-item-action' + ).entwine({ onclick: function () { if (this.children('a').length > 0) { - this.children('a').first().trigger('click'); + this.children('a') + .first() + .trigger('click'); } } }); // Trigger the "add existing" action on the selected element - $(".add-existing-search-dialog .add-existing-search-items a").entwine({ + $('.add-existing-search-dialog .add-existing-search-items a').entwine({ onclick: function () { - var link = this.closest(".add-existing-search-items").data("add-link"); - var id = this.data("id"); + var link = this.closest('.add-existing-search-items').data('add-link'); + var id = this.data('id'); - var dialog = this.closest(".add-existing-search-dialog") - .addClass("loading") - .children(".ui-dialog-content") + var dialog = this.closest('.add-existing-search-dialog') + .addClass('loading') + .children('.ui-dialog-content') .empty(); - dialog.data("treeview").reload({ - url: link, - data: [{ - name: "id", - value: id - }] - }, function () { - dialog.dialog("close"); - }); + dialog.data('treeview').reload({ + url: link, + data: [{ + name: 'id', + value: id + }] + }, + function () { + dialog.dialog('close'); + } + ); return false; } }); // Browse search result pages - $(".add-existing-search-dialog .add-existing-search-pagination a").entwine({ + $('.add-existing-search-dialog .add-existing-search-pagination a').entwine({ onclick: function () { - this.closest(".add-existing-search-dialog").loadDialog($.get( - this.prop("href") - )); + this.closest('.add-existing-search-dialog').loadDialog( + $.get(this.prop('href')) + ); + return false; + } + }); + + // Save the item in the detail view + $('.view-detail-dialog .view-detail-form').entwine({ + onsubmit: function () { + var dialog = this.closest('.view-detail-dialog').children('.ui-dialog-content'); + + $.post(this.prop('action'), this.serialize(), function () { + dialog.data('treeview').reload(); + dialog.dialog('close'); + }); + return false; } }); // Attach data to our tree view - $(".treeview-pagesections").entwine({ + $('.treeview-pagesections').entwine({ addItem: function (parents, itemId, elemType) { var $treeView = $(this); $treeView.reload({ - url: $treeView.data("url") + "/add", + url: $treeView.data('url') + '/add', data: [{ - name: "parents", - value: parents - }, { - name: "itemId", - value: itemId - }, { - name: "type", - value: elemType - }] + name: 'parents', + value: parents + }, + { + name: 'itemId', + value: itemId + }, + { + name: 'type', + value: elemType + } + ] }); }, removeItem: function (parents, itemId) { var $treeView = $(this); $treeView.reload({ - url: $treeView.data("url") + "/remove", + url: $treeView.data('url') + '/remove', data: [{ - name: "parents", - value: parents - }, { - name: "itemId", - value: itemId - }] + name: 'parents', + value: parents + }, + { + name: 'itemId', + value: itemId + } + ] }); }, deleteItem: function (parents, itemId) { var $treeView = $(this); $treeView.reload({ - url: $treeView.data("url") + "/delete", + url: $treeView.data('url') + '/delete', data: [{ - name: "parents", - value: parents - }, { - name: "itemId", - value: itemId - }] + name: 'parents', + value: parents + }, + { + name: 'itemId', + value: itemId + } + ] }); }, onadd: function () { var $treeView = $(this); - var name = $treeView.data("name"); + var name = $treeView.data('name'); var url = $treeView.data('url'); // Setup add new button - $treeView.find(".treeview-actions-addnew .tree-button").click(function () { - $treeView.reload({ - url: url + "/add", - data: [{ - name: "type", - value: $("#AddNewClass").val() - }] + $treeView + .find('.treeview-actions-addnew .tree-button') + .click(function () { + $treeView.reload({ + url: url + '/add', + data: [{ + name: 'type', + value: $('#AddNewClass').val() + }] + }); }); - }); // Setup find existing button - $treeView.find("button[name=action_FindExisting]").click(function () { - var dialog = $("
                              ").appendTo("body").dialog({ - modal: true, - resizable: false, - width: 500, - height: 600, - close: function () { - $(this).dialog("destroy").remove(); - } - }); + $treeView.find('button[name=action_FindExisting]').click(function () { + var dialog = $('
                              ') + .appendTo('body') + .dialog({ + modal: true, + resizable: false, + width: 500, + height: 600, + close: function () { + $(this) + .dialog('destroy') + .remove(); + } + }); - dialog.parent().addClass("add-existing-search-dialog").loadDialog( - $.get($treeView.data("url") + "/find") - ); - dialog.data("treeview", $treeView); + dialog + .parent() + .addClass('add-existing-search-dialog') + .loadDialog($.get($treeView.data('url') + '/search')); + dialog.data('treeview', $treeView); }); // Process items - $treeView.find(".treeview-item").each(function () { + $treeView.find('.treeview-item').each(function () { var $item = $(this); - var $onlyThis = $item.find("> .treeview-item-flow"); - var itemId = $item.data("id"); - var parents = $item.data("tree"); + var $onlyThis = $item.find('> .treeview-item-flow'); + var itemId = $item.data('id'); + var parents = $item.data('tree'); + + // Show details dialog on item click + $onlyThis.find('.treeview-item-fill').click(function () { + var dialog = $('
                              ') + .appendTo('body') + .dialog({ + modal: false, + resizable: false, + width: $(window).width() * 0.9, + height: $(window).height() * 0.9, + close: function () { + $(this) + .dialog('destroy') + .remove(); + } + }); - // Open an item button - $onlyThis.find(".treeview-item-actions .tree-button").click(function (event) { - event.preventDefault(); - event.stopImmediatePropagation(); + dialog + .parent() + .addClass('view-detail-dialog') + .loadDialog($.get($treeView.data('url') + '/detail?ID=' + itemId)); + dialog.data('treeview', $treeView); + }); - $treeView.reload({ - url: url + "/tree", - data: [{ - name: "parents", - value: parents - }, { - name: "itemId", - value: itemId - }] + // Open an item button + $onlyThis + .find('.treeview-item-actions .tree-button') + .click(function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + + $treeView.reload({ + url: url + '/tree', + data: [{ + name: 'parents', + value: parents + }, + { + name: 'itemId', + value: itemId + } + ] + }); }); - }); // Add new item button - $onlyThis.find(".treeview-item-actions .add-button").click(function (event) { - event.preventDefault(); - event.stopImmediatePropagation(); - - $target = $(event.target); - var elems = $target.data("allowed-elements"); - - var $menu = $( - "
                                " - ); - $menu.css({ - top: event.pageY + "px", - left: event.pageX + "px" - }); - $(document.body).append($menu); - - $menu.append( - "
                              • " + - ss.i18n._t("PageSections.TreeView.AddAChild", "Add a child") + - "
                              • " - ); - $.each(elems, function (key, value) { - var $li = $("
                              • " + value + "
                              • "); - $li.click(function () { - $treeView.addItem(parents, itemId, key); - $menu.remove(); + $onlyThis + .find('.treeview-item-actions .add-button') + .click(function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + + $target = $(event.target); + var elems = $target.data('allowed-elements'); + + var $menu = $( + "
                                  " + ); + $menu.css({ + top: event.pageY + 'px', + left: event.pageX + 'px' }); - $menu.append($li); + $(document.body).append($menu); + + $menu.append( + "
                                • " + + ss.i18n._t('PageSections.TreeView.AddAChild', 'Add a child') + + '
                                • ' + ); + $.each(elems, function (key, value) { + var $li = $("
                                • " + value + '
                                • '); + $li.click(function () { + $treeView.addItem(parents, itemId, key); + $menu.remove(); + }); + $menu.append($li); + }); + $menu.show(); }); - $menu.show(); - }); // Delete button action - $onlyThis.find(".treeview-item-actions .delete-button").click(function (event) { - event.preventDefault(); - event.stopImmediatePropagation(); - - $target = $(event.target); - - var $menu = $( - "
                                    " - ); - $menu.css({ - top: event.pageY + "px", - left: event.pageX + "px" - }); - $(document.body).append($menu); - - $menu.append( - "
                                  • " + - ss.i18n._t("PageSections.TreeView.Delete", "Delete") + - "
                                  • " - ); - - var $li = $( - "
                                  • " + - ss.i18n._t("PageSections.TreeView.RemoveAChild", "Remove") + - "
                                  • " - ); - $li.click(function () { - $treeView.removeItem(parents, itemId); - $menu.remove(); - }); - $menu.append($li); - if ($target.data("used-count") < 2) { + $onlyThis + .find('.treeview-item-actions .delete-button') + .click(function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + + $target = $(event.target); + + var $menu = $( + "
                                      " + ); + $menu.css({ + top: event.pageY + 'px', + left: event.pageX + 'px' + }); + $(document.body).append($menu); + + $menu.append( + "
                                    • " + + ss.i18n._t('PageSections.TreeView.Delete', 'Delete') + + '
                                    • ' + ); + var $li = $( - "
                                    • " + - ss.i18n._t( - "PageSections.TreeView.DeleteAChild", - "Finally delete" - ) + - "
                                    • " + "
                                    • " + + ss.i18n._t('PageSections.TreeView.RemoveAChild', 'Remove') + + '
                                    • ' ); $li.click(function () { - $treeView.deleteItem(parents, itemId); + $treeView.removeItem(parents, itemId); $menu.remove(); }); $menu.append($li); - } + if ($target.data('used-count') < 2) { + var $li = $( + '
                                    • ' + + ss.i18n._t( + 'PageSections.TreeView.DeleteAChild', + 'Finally delete' + ) + + '
                                    • ' + ); + $li.click(function () { + $treeView.deleteItem(parents, itemId); + $menu.remove(); + }); + $menu.append($li); + } - $menu.show(); - }); + $menu.show(); + }); // Attach draggable events & info $onlyThis.draggable({ - revert: "invalid", - cursor: "crosshair", + revert: 'invalid', + cursor: 'crosshair', cursorAt: { top: -15, left: -15 }, - activeClass: "state-active", - hoverClass: "state-active", - tolerance: "pointer", + activeClass: 'state-active', + hoverClass: 'state-active', + tolerance: 'pointer', greedy: true, helper: function () { var $helper = $( "
                                      " + - $item.find(".treeview-item-content__title").text() + - "
                                      " + $item.find('.treeview-item-content__title').text() + + '' ); - $item.css("opacity", 0.6); + $item.css('opacity', 0.6); return $helper; }, start: function () { - $(".ui-droppable").each(function () { + $('.ui-droppable').each(function () { var $drop = $(this); - var $dropItem = $drop.closest(".treeview-item"); - var isOpen = $dropItem.data("is-open"); + var $dropItem = $drop.closest('.treeview-item'); + var isOpen = $dropItem.data('is-open'); // Dont enable dropping on itself - if ($dropItem.data("id") == itemId && - $dropItem.data("tree") == parents) { + if ( + $dropItem.data('id') == itemId && + $dropItem.data('tree') == parents + ) { return; } // Don't enable dropping on the middle arrow for open items // (they will have child elements where we can drop before or after) - if ($drop.hasClass("middle") && isOpen) { + if ($drop.hasClass('middle') && isOpen) { return; } // Dont enable dropping on .after of itself if ( - $drop.hasClass("after") && - $dropItem.next().data("id") == itemId + $drop.hasClass('after') && + $dropItem.next().data('id') == itemId ) { return; } @@ -321,18 +394,18 @@ // Dont enable dropping on .middle of other same id elements // (no recursive structures) if ( - $drop.hasClass("middle") && - $dropItem.data("id") == itemId + $drop.hasClass('middle') && + $dropItem.data('id') == itemId ) { return; } // Don't allow dropping elements on the root level that aren't allowed there if ( - $dropItem.data("tree").length == 0 && - ($drop.hasClass("before") || $drop.hasClass("after")) + $dropItem.data('tree').length == 0 && + ($drop.hasClass('before') || $drop.hasClass('after')) ) { - if (!$item.data("allowed-root")) { + if (!$item.data('allowed-root')) { return; } } @@ -340,15 +413,15 @@ // Don't allow dropping elements on this level if they're not an allowed child // Depending on the arrow we either have to check this element or the parent // of this element to see which children are allowed - var clazz = $item.data("class"); - if ($drop.hasClass("before") || $drop.hasClass("after")) { - var $parent = $dropItem.parent().closest(".treeview-item"); - var allowed = $parent.data("allowed-elements"); + var clazz = $item.data('class'); + if ($drop.hasClass('before') || $drop.hasClass('after')) { + var $parent = $dropItem.parent().closest('.treeview-item'); + var allowed = $parent.data('allowed-elements'); if (allowed && !allowed[clazz]) { return; } } else { - var allowed = $dropItem.data("allowed-elements"); + var allowed = $dropItem.data('allowed-elements'); if (allowed && !allowed[clazz]) { return; } @@ -358,55 +431,60 @@ }); }, stop: function (event, ui) { - $(".ui-droppable").hide(); + $('.ui-droppable').hide(); // Show the previous elements. If the user made an invalid movement then // we want this to show anyways. If he did something valid the treeview will // refresh so we don't care if it's visible behind the loading icon. - $(".treeview-item").css("opacity", ""); + $('.treeview-item').css('opacity', ''); } }); // Dropping targets - $onlyThis.find(".treeview-item-reorder div").each(function () { + $onlyThis.find('.treeview-item-reorder div').each(function () { $(this).droppable({ - hoverClass: "state-active", - tolerance: "pointer", + hoverClass: 'state-active', + tolerance: 'pointer', drop: function (event, ui) { $drop = $(this); - $dropItem = $drop.closest(".treeview-item"); + $dropItem = $drop.closest('.treeview-item'); - $oldItem = ui.draggable.closest(".treeview-item"); - var oldId = $oldItem.data("id"); - var oldParents = $oldItem.data("tree"); + $oldItem = ui.draggable.closest('.treeview-item'); + var oldId = $oldItem.data('id'); + var oldParents = $oldItem.data('tree'); - var type = "child"; + var type = 'child'; var sort = 100000; - if ($drop.hasClass("before")) { - type = "before"; - sort = $dropItem.data("sort") - 1; - } else if ($drop.hasClass("after")) { - type = "after"; - sort = $dropItem.data("sort") + 1; + if ($drop.hasClass('before')) { + type = 'before'; + sort = $dropItem.data('sort') - 1; + } else if ($drop.hasClass('after')) { + type = 'after'; + sort = $dropItem.data('sort') + 1; } - var newParent = type === "child" ? itemId : parents[parents.length - 1]; + var newParent = + type === 'child' ? itemId : parents[parents.length - 1]; $treeView.reload({ - url: url + "/move", + url: url + '/move', data: [{ - name: "parents", - value: oldParents - }, { - name: "itemId", - value: oldId - }, { - name: "newParent", - value: newParent - }, { - name: "sort", - value: sort - }] + name: 'parents', + value: oldParents + }, + { + name: 'itemId', + value: oldId + }, + { + name: 'newParent', + value: newParent + }, + { + name: 'sort', + value: sort + } + ] }); } }); @@ -425,46 +503,54 @@ if (!ajaxOpts) ajaxOpts = {}; if (!ajaxOpts.data) ajaxOpts.data = []; ajaxOpts.data = ajaxOpts.data.concat(data).concat([{ - name: "state", - value: self.data("state-id"), + name: 'state', + value: self.data('state-id') }]); // Include any GET parameters from the current URL, as the view state might depend on it. // For example, a list prefiltered through external search criteria might be passed to GridField. if (window.location.search) { - ajaxOpts.data = window.location.search.replace(/^\?/, '') + '&' + $.param(ajaxOpts.data); + ajaxOpts.data = + window.location.search.replace(/^\?/, '') + + '&' + + $.param(ajaxOpts.data); } form.addClass('loading'); - $.ajax($.extend({}, { - headers: { - "X-Pjax": 'CurrentField' - }, - type: "POST", - url: this.data('url'), - dataType: 'html', - success: function (data) { - // Replace the grid field with response, not the form. - // TODO Only replaces all its children, to avoid replacing the current scope - // of the executing method. Means that it doesn't retrigger the onmatch() on the main container. - self.empty().append($(data).children()); - - // Refocus previously focused element. Useful e.g. for finding+adding - // multiple relationships via keyboard. - if (focusedElName) self.find(':input[name="' + focusedElName + '"]').focus(); - - form.removeClass('loading'); - if (successCallback) successCallback.apply(this, arguments); - // TODO: Don't know how original SilverStripe GridField magically calls - self.onadd(); - }, - error: function (e) { - alert(i18n._t('Admin.ERRORINTRANSACTION')); - form.removeClass('loading'); - } - }, ajaxOpts)); - }, + $.ajax( + $.extend({}, { + headers: { + 'X-Pjax': 'CurrentField' + }, + type: 'POST', + url: this.data('url'), + dataType: 'html', + success: function (data) { + // Replace the grid field with response, not the form. + // TODO Only replaces all its children, to avoid replacing the current scope + // of the executing method. Means that it doesn't retrigger the onmatch() on the main container. + self.empty().append($(data).children()); + + // Refocus previously focused element. Useful e.g. for finding+adding + // multiple relationships via keyboard. + if (focusedElName) + self.find(':input[name="' + focusedElName + '"]').focus(); + + form.removeClass('loading'); + if (successCallback) successCallback.apply(this, arguments); + // TODO: Don't know how original SilverStripe GridField magically calls + self.onadd(); + }, + error: function (e) { + alert(i18n._t('Admin.ERRORINTRANSACTION')); + form.removeClass('loading'); + } + }, + ajaxOpts + ) + ); + } }); }); })(jQuery); diff --git a/src/PageElement.php b/src/PageElement.php index 0f850b9..b87beb9 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -170,19 +170,10 @@ public function onAfterDelete() } /** - * Gets the TreeView for the children of this PageElement - * @return \FLXLabs\PageSections\TreeView - */ - public function getChildrenTreeView() - { - return new TreeView("Child", "Children", $this->Children()); - } - - /** - * Gets the preview of this PageElement in the GridField. + * Gets the preview of this PageElement in the TreeView. * @return string */ - public function getGridFieldPreview() + public function getTreeViewPreview() { return $this->Name; } @@ -241,12 +232,6 @@ public function getCMSFields() $fields->removeByName('__Counter'); $fields->removeByName("Children"); - if ($this->ID && count($this->getAllowedPageElements()) > 0) { - $fields->insertAfter( - "Main", - Tab::create("Child", "Children", $this->getChildrenTreeView()) - ); - } // Add our newest version as a readonly field $fields->addFieldsToTab( @@ -261,9 +246,7 @@ public function getCMSFields() if ($pages->Count() > 0) { $config = GridFieldConfig_Base::create() ->removeComponentsByType(GridFieldDataColumns::class) - ->addComponent($dataColumns = new GridFieldDataColumns()) - ->addComponent(new GridFieldDetailForm()) - ->addComponent(new GridFieldEditButton()); + ->addComponent($dataColumns = new GridFieldDataColumns()); $dataColumns->setDisplayFields([ "ID" => "ID", "ClassName" => "Type", diff --git a/src/TreeView.php b/src/TreeView.php index fcc4e34..a9ca367 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -2,11 +2,14 @@ namespace FLXLabs\PageSections; +use SilverStripe\AssetAdmin\Forms\UploadField; use SilverStripe\Control\Controller; use SilverStripe\Control\HTTPRequest; +use SilverStripe\Control\Session; use SilverStripe\Forms\Form; use SilverStripe\Forms\FormAction; use SilverStripe\Forms\DropdownField; +use SilverStripe\Forms\HiddenField; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FormField; use SilverStripe\ORM\PaginatedList; @@ -36,8 +39,8 @@ class TreeView extends FormField 'add', 'remove', 'delete', - 'find', - 'doSearch', + 'search', + 'detail', ); @@ -353,14 +356,15 @@ public function delete($request) } /** - * Creates the search form for adding existing elements - * @return \SilverStripe\Forms\Form + * This action is called when the find existing dialog is shown. + * @param \SilverStripe\Control\HTTPRequest $request + * @return string */ - public function SearchForm() + public function search($request) { $form = Form::create( $this, - 'doSearch', + 'search', $this->context->getFields(), FieldList::create( FormAction::create('doSearch', _t('GridFieldExtensions.SEARCH', 'Search')) @@ -370,30 +374,127 @@ public function SearchForm() ); $form->addExtraClass('stacked add-existing-search-form form--no-dividers'); $form->setFormMethod('GET'); - return $form; - } - public function find($request) - { - return $this->SearchForm()->forTemplate(); + // Check if we're requesting the form for the first time (we return the template) + // or if this is a submission (we return the form, so it calls the submitted action) + if (count($request->requestVars()) === 0) { + return $form->forAjaxTemplate(); + } + return $form; } - public function doSearch($request) + /** + * This action is called when a search is performed in the find existing dialog + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function doSearch($data, $form) { - $list = $this->context->getQuery($request->requestVars(), false, false); + $list = $this->context->getQuery($data, false, false); // If we're viewing the search list on a PageElement, // then we have to remove all parents as possible elements if ($this->parent->ClassName === PageElement::class) { $list = $list->subtract($this->parent->getAllParents()); } - $list = new PaginatedList($list, $request); + $list = new PaginatedList($list, $data); $data = $this->customise([ - 'SearchForm' => $this->SearchForm(), + 'SearchForm' => $form, 'Items' => $list ]); return $data->renderWith("FLXLabs\PageSections\TreeViewFindExistingForm"); } + /** + * Creates a detail edit form for the specified item + * @param \FLXLabs\PageSections\PageElement $item + * @param bool $loadData True if the data from $item should be loaded into the form, false otherwise. + * @return \SilverStripe\Forms\Form + */ + public function DetailForm(PageElement $item, bool $loadData = true) + { + $canEdit = $item->canEdit(); + $canDelete = $item->canDelete(); + + $actions = new FieldList(); + if ($canEdit) { + $actions->push(FormAction::create('doSave', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Save', 'Save')) + ->setUseButtonTag(true) + ->addExtraClass('btn-primary font-icon-save')); + } + if ($canDelete) { + $actions->push(FormAction::create('doDelete', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Delete', 'Delete')) + ->setUseButtonTag(true) + ->addExtraClass('btn-outline-danger btn-hide-outline font-icon-trash-bin action-delete')); + } + + $fields = $item->getCMSFields(); + $fields->addFieldToTab("Root.Main", HiddenField::create("ID", "ID", $item->ID)); + + $form = Form::create( + $this, + 'detail', + $fields, + $actions + ); + if ($loadData) { + $form->loadDataFrom($item, Form::MERGE_DEFAULT); + } + + $form->setTemplate([ + 'type' => 'Includes', + 'SilverStripe\\Admin\\LeftAndMain_EditForm', + ]); + $form->addExtraClass('view-detail-form cms-content cms-edit-form center fill-height flexbox-area-grow'); + if ($form->Fields()->hasTabSet()) { + $form->Fields()->findOrMakeTab('Root')->setTemplate('SilverStripe\\Forms\\CMSTabSet'); + $form->addExtraClass('cms-tabset'); + } + + return $form; + } + + /** + * This action is called when the detail form for an item is opened. + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function detail($request) { + $id = intval($request->requestVar("ID")); + + // This is a request to show the form so we return it as a template so + // that SilverStripe doesn't think this is already a submission + // (it would call the first action on the form) + if ($request->isGET()) { + $item = PageElement::get()->byID($id); + if (!$item) { + return $this->httpError(404); + } + $form = $this->DetailForm($item); + // Save the id of the page element on this form's security token + $request->getSession()->set("_tv_df_" . $form->getSecurityToken()->getValue(), $id); + return $form->forAjaxTemplate(); + } + + // If it's a POST request then it's a submission and we have to get the ID + // from the session using the form's security token. + $id = $request->getSession()->get("_tv_df_" . $request->requestVar("SecurityID")); + $item = PageElement::get()->byID($id); + return $this->DetailForm($item, false); + } + + /** + * This action is called when the detail form is submitted (saved/deleted) + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function doSave($data, $form) { + $id = intval($data["ID"]); + $item = PageElement::get()->byID($id); + + $form->saveInto($item); + $item->write(); + } + /** * Get base items * @@ -550,17 +651,6 @@ private function renderTree($item, $parents, $opens, $isFirst) $icon = ($isOpen === true ? 'font-icon-down-open' : 'font-icon-right-open'); } - // Create a button to edit the item - $editButton = TreeViewFormAction::create( - $this, - "DeleteAction".$item->ID, - null, - null, - null - ); - $editButton->addExtraClass("col-actions__button edit-button"); - $editButton->setButtonContent('Edit'); - // Create a button to add a new child element // and save the allowed child classes on the button $classes = $item->getAllowedPageElements(); @@ -624,18 +714,14 @@ private function renderTree($item, $parents, $opens, $isFirst) $treeButton->setButtonContent(' '); return ArrayData::create([ - "ID" => $item->ID, - "ClassName" => $item->ClassName, - "Name" => $item->Name, + "Item" => $item, "Tree" => $tree, - "SortOrder" => $item->SortOrder, "IsOpen" => $isOpen, "IsFirst" => $isFirst, "Children" => $childContent, "AllowedRoot" => $isAllowedRoot, "AllowedElements" => json_encode($elems, JSON_UNESCAPED_UNICODE), "TreeButton" => $treeButton, - "EditButton" => $editButton, "AddButton" => $addButton, "DeleteButton" => $deleteButton, "UsedCount" => $item->Parents()->Count() + $item->getAllPages()->Count(), diff --git a/templates/FLXLabs/PageSections/TreeViewPageElement.ss b/templates/FLXLabs/PageSections/TreeViewPageElement.ss index 1e2f55c..fb9fd1e 100644 --- a/templates/FLXLabs/PageSections/TreeViewPageElement.ss +++ b/templates/FLXLabs/PageSections/TreeViewPageElement.ss @@ -1,9 +1,10 @@
                                      $ButtonField
                                      - $ClassName (ID: $ID, {$UsedCount}x) + $Item.ClassName (ID: $Item.ID, {$UsedCount}x)
                                      - $Name + $Item.Name
                                      +
                                      + $Item.TreeViewPreview +
                                      +
                                      $Children.RAW From 4c3797f64eaa332f5afc3677d98c85f89af45685 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Thu, 19 Jul 2018 18:08:50 +0200 Subject: [PATCH 21/82] fix(move): Fix moving elements --- javascript/TreeView.js | 3 ++- src/TreeView.php | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/javascript/TreeView.js b/javascript/TreeView.js index bbfa99f..749fcca 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -464,7 +464,8 @@ } var newParent = - type === 'child' ? itemId : parents[parents.length - 1]; + type === 'child' ? + itemId : (parents.length > 0 ? parents[parents.length - 1] : ''); $treeView.reload({ url: url + '/move', diff --git a/src/TreeView.php b/src/TreeView.php index a9ca367..2e20211 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -206,7 +206,7 @@ public function move($request) $sortArr = [$sortBy => $sort]; // Add the element to the new parent - if (is_subclass_of($newParent, PageSection::class)) { + if ($newParent->ClassName == PageSection::class) { $newParent->Elements()->Add($item, $sortArr); } else { $newParent->Children()->Add($item, $sortArr); @@ -391,6 +391,9 @@ public function search($request) public function doSearch($data, $form) { $list = $this->context->getQuery($data, false, false); + $allowed = $this->parent->getAllowedPageElements(); + // Remove all disallowed classes + $list = $list->filter("ClassName", $allowed); // If we're viewing the search list on a PageElement, // then we have to remove all parents as possible elements if ($this->parent->ClassName === PageElement::class) { @@ -684,10 +687,6 @@ private function renderTree($item, $parents, $opens, $isFirst) "data-used-count", $item->Parents()->Count() + $item->getAllPages()->Count() ); - $deleteButton->setAttribute( - "data-parent-id", - $item->_Parent ? $item->_Parent->ID : $this->parent->ID - ); $deleteButton->addExtraClass("col-actions__button delete-button font-icon-trash-bin"); $deleteButton->setButtonContent('Delete'); From 56140d0cab7c3cb2af0a973f25c5149648e074f0 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Mon, 30 Jul 2018 16:57:25 +0200 Subject: [PATCH 22/82] fix(page-sections): Support not only SiteTree classes as parents for PageSection --- src/GridFieldPageSectionsExtension.php | 4 +-- src/PageElement.php | 46 +++++++++++++------------- src/PageSection.php | 46 +++++++++----------------- src/PageSectionsExtension.php | 7 +++- 4 files changed, 46 insertions(+), 57 deletions(-) diff --git a/src/GridFieldPageSectionsExtension.php b/src/GridFieldPageSectionsExtension.php index 8534e72..23e4e2d 100755 --- a/src/GridFieldPageSectionsExtension.php +++ b/src/GridFieldPageSectionsExtension.php @@ -209,7 +209,7 @@ public function getColumnContent($gridField, $record, $columnName) { return ViewableData::create()->customise([ "ButtonField" => $field, "ID" => $record->ID, - "UsedCount" => $record->Parents()->Count() + $record->getAllPages()->Count(), + "UsedCount" => $record->Parents()->Count() + $record->getAllSectionParents()->Count(), "ClassName" => $record->i18n_singular_name(), "Title" => $record->Title, ])->renderWith("GridFieldPageElement"); @@ -261,7 +261,7 @@ public function getColumnContent($gridField, $record, $columnName) { ); $deleteButton->setAttribute( "data-used-count", - $record->Parents()->Count() + $record->getAllPages()->Count() + $record->Parents()->Count() + $record->getAllSectionParents()->Count() ); $deleteButton->setAttribute( "data-parent-id", diff --git a/src/PageElement.php b/src/PageElement.php index 1a47d27..4a79d3c 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -96,7 +96,7 @@ function canCreate($member = null, $context = []) { return true; } ]; private static $better_buttons_actions = array ( - 'publishOnAllPages', + 'publishOnAllSectionParents', ); // Returns all page element classes, without the base class @@ -170,25 +170,25 @@ public function getGridFieldPreview() { return $this->Name; } - // Gets all the pages that this page element is on, plus adds a __PageSection - // attribute to the page object so we know which section this element is in. - public function getAllPages() { - $pages = ArrayList::create(); + // Gets all the parents of the sections that this page element is on, plus adds a + // __PageSection attribute to the parent object so we know which section this element is in. + public function getAllSectionParents() { + $parents = ArrayList::create(); foreach ($this->PageSections() as $section) { - $page = $section->Page(); + $p = $section->Parent(); $stage = Versioned::get_stage(); Versioned::set_stage(Versioned::LIVE); $pubSection = DataObject::get_by_id($section->ClassName, $section->ID); $pubElem = $pubSection ? $pubSection->Elements()->filter("ID", $this->ID)->First() : null; - $page->__PageSection = $section; - $page->__PageElementVersion = $section->Elements()->filter("ID", $this->ID)->First()->Version; - $page->__PageElementPublishedVersion = $pubElem ? $pubElem->Version : "Not published"; + $p->__PageSection = $section; + $p->__PageElementVersion = $section->Elements()->filter("ID", $this->ID)->First()->Version; + $p->__PageElementPublishedVersion = $pubElem ? $pubElem->Version : "Not published"; Versioned::set_stage($stage); - $pages->add($page); + $parents->add($p); } - return $pages; + return $parents; } public function getCMSFields() { @@ -210,10 +210,10 @@ public function getCMSFields() { "Title" ); - // Create an array of all the pages this element is on - $pages = $this->getAllPages(); + // Create an array of all the sections this element is on + $parents = $this->getAllSectionParents(); - if ($pages->Count() > 0) { + if ($parents->Count() > 0) { $config = GridFieldConfig_Base::create() ->removeComponentsByType(GridFieldDataColumns::class) ->addComponent($dataColumns = new GridFieldDataColumns()) @@ -223,13 +223,13 @@ public function getCMSFields() { "ID" => "ID", "ClassName" => "Type", "Title" => "Title", - "__PageSection.Name" => "PageSection", + "__PageSection.__Name" => "PageSection", "__PageElementVersion" => "Element version", "__PageElementPublishedVersion" => "Published element version", - "getPublishState" => "Page state", + "getPublishState" => "Parent State", ]); - $gridField = GridField::create("Pages", "Pages", $pages, $config); - $fields->addFieldToTab("Root.Pages", $gridField); + $gridField = GridField::create("Pages", "Section Parents", $parents, $config); + $fields->addFieldToTab("Root.SectionParents", $gridField); } return $fields; @@ -279,16 +279,16 @@ public function getBetterButtonsUtils() { public function getBetterButtonsActions() { $actions = FieldList::create([ SaveAndClose::create(), - CustomAction::create('publishOnAllPages', 'Publish on all pages') + CustomAction::create('publishOnAllSectionParents', 'Publish everywhere') ->setRedirectType(CustomAction::REFRESH) ]); return $actions; } - public function publishOnAllPages() { - foreach ($this->getAllPages() as $page) { - $page->publish(Versioned::get_stage(), Versioned::LIVE); + public function publishOnAllSectionParents() { + foreach ($this->getAllSectionParents() as $parent) { + $parent->publish(Versioned::get_stage(), Versioned::LIVE); } - return 'Published on all pages'; + return 'Published on all section parents'; } } diff --git a/src/PageSection.php b/src/PageSection.php index a5deefd..0059a28 100644 --- a/src/PageSection.php +++ b/src/PageSection.php @@ -2,7 +2,6 @@ namespace FLXLabs\PageSections; -use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Core\ClassInfo; use SilverStripe\ORM\DataObject; use SilverStripe\Forms\Form; @@ -23,13 +22,12 @@ class PageSection extends DataObject { private static $table_name = "FLXLabs_PageSections_PageSection"; private static $db = [ + "__Name" => "Varchar", + "__ParentID" => "Int", + "__ParentClass" => "Varchar", "__Counter" => "Int", ]; - private static $has_one = [ - "Page" => SiteTree::class, - ]; - private static $owns = [ "Elements", ]; @@ -46,6 +44,10 @@ class PageSection extends DataObject { ] ]; + public function Parent() { + return DataObject::get_by_id($this->__ParentClass, $this->__ParentID); + } + public function onBeforeWrite() { parent::onBeforeWrite(); @@ -60,42 +62,24 @@ public function onAfterWrite() { parent::onAfterWrite(); if (!$this->__isNew && Versioned::get_stage() == Versioned::DRAFT) { - $this->Page()->__PageSectionCounter++; - $this->Page()->write(); + $this->Parent()->__PageSectionCounter++; + $this->Parent()->write(); } } public function forTemplate() { return $this->Elements()->Count(); - - $actions = FieldList::create(); - $fields = FieldList::create(); - $form = Form::create(null, "Form", $fields, $actions); - - $config = GridFieldConfig::create() - ->addComponent(new GridFieldToolbarHeader()) - ->addComponent($dataColumns = new GridFieldDataColumns()) - ->addComponent(new GridFieldPageSectionsExtension($this->owner)); - $dataColumns->setFieldCasting(["GridFieldPreview" => "HTMLText->RAW"]); - - $grid = GridField::create("Elements", "Elements", $this->Elements(), $config); - $grid->setForm($form); - $fields->add($grid); - - $form->setFields($fields); - - return $form->forTemplate(); } - // Gets the name of this section from the page it is on + // Gets the name of this section from the parent it is on public function getName() { - $page = $this->Page(); + $parent = $this->Parent(); // TODO: Find out why this happens - if (!method_exists($page, "getPageSectionNames")) { + if (!method_exists($parent, "getPageSectionNames")) { return null; } - foreach ($page->getPageSectionNames() as $sectionName) { - if ($page->{"PageSection" . $sectionName . "ID"} === $this->ID) { + foreach ($parent->getPageSectionNames() as $sectionName) { + if ($parent->{"PageSection" . $sectionName . "ID"} === $this->ID) { return $sectionName; } } @@ -103,6 +87,6 @@ public function getName() { } public function getAllowedPageElements($section = "Main") { - return $this->Page()->getAllowedPageElements($section); + return $this->Parent()->getAllowedPageElements($section); } } diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 31550b4..c37692d 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -84,7 +84,9 @@ public function onBeforeWrite() { // Create a page section if we don't have one yet if (!$this->owner->$name()->ID) { $section = PageSection::create(); - $section->PageID = $this->owner->ID; + $section->__Name = $name; + $section->__ParentID = $this->owner->ID; + $section->__ParentClass = $this->owner->ClassName; $section->__isNew = true; $section->write(); $this->owner->$name = $section; @@ -99,10 +101,13 @@ public function updateCMSFields(FieldList $fields) { $sections = ["Main"]; } + $fields->removeByName("__PageSectionCounter"); + foreach ($sections as $section) { $name = "PageSection".$section; $fields->removeByName($name); + $fields->removeByName($name . "ID"); if ($this->owner->ID) { $addNewButton = new GridFieldAddNewMultiClass(); From 88bd6760800447acd8ee72ee3cd508510f9cb1c3 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Mon, 30 Jul 2018 17:07:16 +0200 Subject: [PATCH 23/82] fix(page-section): Fix name including constant "PageSection" prefix --- src/PageSectionsExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index c37692d..5ce0f23 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -84,7 +84,7 @@ public function onBeforeWrite() { // Create a page section if we don't have one yet if (!$this->owner->$name()->ID) { $section = PageSection::create(); - $section->__Name = $name; + $section->__Name = $section; $section->__ParentID = $this->owner->ID; $section->__ParentClass = $this->owner->ClassName; $section->__isNew = true; From ccb6c677f20ce3e4dbb6b7165d2fa50550bb2aa3 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Mon, 30 Jul 2018 17:21:34 +0200 Subject: [PATCH 24/82] fix(page-section): Fix PageSection name error --- src/PageSectionsExtension.php | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 5ce0f23..fc66f22 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -73,18 +73,15 @@ public function onBeforeWrite() { parent::onBeforeWrite(); if ($this->owner->ID) { - $sections = $this->owner->config()->get("page_sections"); - if (!$sections) { - $sections = ["Main"]; - } + $sections = $this->getPageSectionNames(); - foreach ($sections as $section) { - $name = "PageSection".$section; + foreach ($sections as $sectionName) { + $name = "PageSection".$sectionName; // Create a page section if we don't have one yet if (!$this->owner->$name()->ID) { $section = PageSection::create(); - $section->__Name = $section; + $section->__Name = $sectionName; $section->__ParentID = $this->owner->ID; $section->__ParentClass = $this->owner->ClassName; $section->__isNew = true; @@ -96,10 +93,7 @@ public function onBeforeWrite() { } public function updateCMSFields(FieldList $fields) { - $sections = $this->owner->config()->get("page_sections"); - if (!$sections) { - $sections = ["Main"]; - } + $sections = $this->getPageSectionNames(); $fields->removeByName("__PageSectionCounter"); From bfceeb87c068700f16ff52e67476055e8d9c7474 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Mon, 30 Jul 2018 17:46:50 +0200 Subject: [PATCH 25/82] fix(page-element): Fix getAllSectionParents --- src/PageElement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PageElement.php b/src/PageElement.php index 4a79d3c..1f9038e 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -176,7 +176,7 @@ public function getAllSectionParents() { $parents = ArrayList::create(); foreach ($this->PageSections() as $section) { - $p = $section->Parent(); + $p = $section->Parent()->duplicate(false); $stage = Versioned::get_stage(); Versioned::set_stage(Versioned::LIVE); $pubSection = DataObject::get_by_id($section->ClassName, $section->ID); From 97dfde52bba3ec7c1950046d28812e7feeea3bb2 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Fri, 3 Aug 2018 18:20:15 +0200 Subject: [PATCH 26/82] feat(treeview): Move action buttons. Add button to add item after another --- css/TreeView.css | 53 ++------ javascript/TreeView.js | 115 +++++++++++++----- src/TreeView.php | 52 +++++++- .../PageSections/TreeViewPageElement.ss | 10 +- 4 files changed, 143 insertions(+), 87 deletions(-) diff --git a/css/TreeView.css b/css/TreeView.css index cfc9a67..0f1596d 100644 --- a/css/TreeView.css +++ b/css/TreeView.css @@ -40,50 +40,11 @@ font-weight: bold; } -.treeview-item-actions { - display: flex; - flex-flow: column nowrap; -} - -.treeview-item-actions .edit-link { - display: none; -} - -.treeview-item-actions .col-actions__button { - opacity: 0; - cursor: pointer; - appearance: none; - width: 2em; - height: 2em; - margin: 0; - padding: 0; - border: 0; - background: transparent; - margin: 1px; - color: #43536d; -} - -.treeview-item-actions .col-actions__button:before { - font-size: 140%; -} - -.treeview-item-actions .col-actions__button:hover { - color: #005489; -} - -.treeview-item-actions .col-actions__button:disabled { - display: none; -} - -.treeview-item-actions .col-actions__button .btn__title { - display: none; -} - -.treeview-item:hover .treeview-item-actions .col-actions__button { - opacity: 1; +.treeview-item .treeview-item-actions { + background: #ccc; } -.treeview-item-actions .tree-button { +.treeview-item .tree-button { appearance: none; background: transparent; border: 2px solid #43536d; @@ -100,23 +61,23 @@ font-size: 114%; } -.treeview-item-actions .tree-button.is-end { +.treeview-item .tree-button.is-end { opacity: 0; } -.treeview-item-actions .tree-button:hover { +.treeview-item .tree-button:hover { color: #005489; border-color: #005489; } -.treeview-item-actions .tree-button:before { +.treeview-item .tree-button:before { position: absolute; left: -0.08em; top: -0.08em; font-size: 90%; } -.treeview-item-actions .tree-button .btn__title { +.treeview-item .tree-button .btn__title { display: none; } diff --git a/javascript/TreeView.js b/javascript/TreeView.js index 749fcca..7c0af99 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -101,7 +101,7 @@ // Attach data to our tree view $('.treeview-pagesections').entwine({ - addItem: function (parents, itemId, elemType) { + addItem: function (parents, itemId, elemType, sort = 99999) { var $treeView = $(this); $treeView.reload({ @@ -117,6 +117,10 @@ { name: 'type', value: elemType + }, + { + name: 'sort', + value: sort } ] }); @@ -197,36 +201,12 @@ // Process items $treeView.find('.treeview-item').each(function () { var $item = $(this); - var $onlyThis = $item.find('> .treeview-item-flow'); var itemId = $item.data('id'); var parents = $item.data('tree'); - // Show details dialog on item click - $onlyThis.find('.treeview-item-fill').click(function () { - var dialog = $('
                                      ') - .appendTo('body') - .dialog({ - modal: false, - resizable: false, - width: $(window).width() * 0.9, - height: $(window).height() * 0.9, - close: function () { - $(this) - .dialog('destroy') - .remove(); - } - }); - - dialog - .parent() - .addClass('view-detail-dialog') - .loadDialog($.get($treeView.data('url') + '/detail?ID=' + itemId)); - dialog.data('treeview', $treeView); - }); - // Open an item button - $onlyThis - .find('.treeview-item-actions .tree-button') + $item + .find('> .treeview-item-flow .tree-button') .click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -245,9 +225,34 @@ }); }); + // Edit button + $item + .find('> .treeview-item-actions .edit-button') + .click(function () { + var dialog = $('
                                      ') + .appendTo('body') + .dialog({ + modal: false, + resizable: false, + width: $(window).width() * 0.9, + height: $(window).height() * 0.9, + close: function () { + $(this) + .dialog('destroy') + .remove(); + } + }); + + dialog + .parent() + .addClass('view-detail-dialog') + .loadDialog($.get($treeView.data('url') + '/detail?ID=' + itemId)); + dialog.data('treeview', $treeView); + }); + // Add new item button - $onlyThis - .find('.treeview-item-actions .add-button') + $item + .find('> .treeview-item-actions .add-button') .click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -284,9 +289,53 @@ $menu.show(); }); + // Add new after item button + $item + .find('> .treeview-item-actions .add-after-button') + .click(function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + + $target = $(event.target); + var elems = $target.data('allowed-elements'); + + var $menu = $( + "
                                        " + ); + $menu.css({ + top: event.pageY + 'px', + left: event.pageX + 'px' + }); + $(document.body).append($menu); + + $menu.append( + "
                                      • " + + ss.i18n._t('PageSections.TreeView.AddAfterThis', 'Add after this element') + + '
                                      • ' + ); + $.each(elems, function (key, value) { + var $li = $("
                                      • " + value + '
                                      • '); + $li.click(function () { + $treeView.addItem( + parents.slice(0, parents.length - 1), + parents[parents.length - 1], + key, + $item.data("sort") + 1 + ); + $menu.remove(); + }); + $menu.append($li); + }); + $menu.show(); + }); + // Delete button action - $onlyThis - .find('.treeview-item-actions .delete-button') + $item + .find('> .treeview-item-actions .delete-button') .click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -342,7 +391,7 @@ }); // Attach draggable events & info - $onlyThis.draggable({ + $item.find("> .treeview-item-flow").draggable({ revert: 'invalid', cursor: 'crosshair', cursorAt: { @@ -440,7 +489,7 @@ }); // Dropping targets - $onlyThis.find('.treeview-item-reorder div').each(function () { + $item.find('> .treeview-item-flow .treeview-item-reorder div').each(function () { $(this).droppable({ hoverClass: 'state-active', tolerance: 'pointer', diff --git a/src/TreeView.php b/src/TreeView.php index 0e1e731..71a0964 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -266,7 +266,11 @@ public function add($request) $child = $type::create(); $child->Name = "New " . $type; - + + $sort = intval($data["sort"]); + $sortBy = $this->getSortField(); + $sortArr = [$sortBy => $sort]; + // If we have an itemId then we're adding to another element // otherwise we're adding to the root if ($itemId) { @@ -283,13 +287,13 @@ public function add($request) } $child->write(); - $item->Children()->Add($child); + $item->Children()->Add($child, $sortArr); // Make sure we can see the child $this->openItem(array_merge($path, [$item->ID])); } else { $child->write(); - $this->getItems()->Add($child); + $this->getItems()->Add($child, $sortArr); } } @@ -682,12 +686,37 @@ private function renderTree($item, $parents, $opens, $isFirst) null ); $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); - $addButton->addExtraClass("col-actions__button add-button font-icon-plus"); + $addButton->addExtraClass("add-button font-icon-plus"); if (!count($elems)) { $addButton->setDisabled(true); } $addButton->setButtonContent('Add'); + // Create a button to add an element after + // and save the allowed child classes on the button + $classes = count($parents) > 0 + ? $parents[count($parents) - 1]->getAllowedPageElements() + : $this->section->getAllowedPageElements(); + $elems = []; + foreach ($classes as $class) { + $elems[$class] = singleton($class)->singular_name(); + } + $addAfterButton = TreeViewFormAction::create( + $this, + "AddAfterAction".$item->ID, + null, + null, + null + ); + $addAfterButton->setAttribute("data-allowed-elements", + json_encode($elems, JSON_UNESCAPED_UNICODE) + ); + $addAfterButton->addExtraClass("add-after-button font-icon-plus"); + if (!count($elems)) { + $addAfterButton->setDisabled(true); + } + $addAfterButton->setButtonContent('Add after'); + // Create a button to delete and/or remove the element from the parent $deleteButton = TreeViewFormAction::create( $this, @@ -700,9 +729,20 @@ private function renderTree($item, $parents, $opens, $isFirst) "data-used-count", $item->Parents()->Count() + $item->getAllSectionParents()->Count() ); - $deleteButton->addExtraClass("col-actions__button delete-button font-icon-trash-bin"); + $deleteButton->addExtraClass("delete-button font-icon-trash-bin"); $deleteButton->setButtonContent('Delete'); + // Create a button to edit the record + $editButton = TreeViewFormAction::create( + $this, + "EditAction".$item->ID, + null, + null, + null + ); + $editButton->addExtraClass("edit-button font-icon-edit"); + $editButton->setButtonContent('Edit'); + // Create the tree icon $icon = ''; if ($item->Children() && $item->Children()->Count() > 0) { @@ -735,6 +775,8 @@ private function renderTree($item, $parents, $opens, $isFirst) "AllowedElements" => json_encode($elems, JSON_UNESCAPED_UNICODE), "TreeButton" => $treeButton, "AddButton" => $addButton, + "AddAfterButton" => $addAfterButton, + "EditButton" => $editButton, "DeleteButton" => $deleteButton, "UsedCount" => $item->Parents()->Count() + $item->getAllSectionParents()->Count(), ])->renderWith("\FLXLabs\PageSections\TreeViewPageElement"); diff --git a/templates/FLXLabs/PageSections/TreeViewPageElement.ss b/templates/FLXLabs/PageSections/TreeViewPageElement.ss index fb9fd1e..0d2880f 100644 --- a/templates/FLXLabs/PageSections/TreeViewPageElement.ss +++ b/templates/FLXLabs/PageSections/TreeViewPageElement.ss @@ -17,10 +17,8 @@
                                        -
                                        +
                                        $TreeButton - $AddButton - $DeleteButton
                                        $ButtonField @@ -41,4 +39,10 @@
                                        $Children.RAW
                                        +
                                        + $AddButton + $AddAfterButton + $EditButton + $DeleteButton +
                                        From 6ce37fd2e7d315a1ea55a5c0a4aa6162b188d7c2 Mon Sep 17 00:00:00 2001 From: Felix Eggmann Date: Sat, 11 Aug 2018 11:43:18 +0200 Subject: [PATCH 27/82] style(js): use double quotes --- javascript/TreeView.js | 546 +++++++++++++++++++++-------------------- 1 file changed, 285 insertions(+), 261 deletions(-) diff --git a/javascript/TreeView.js b/javascript/TreeView.js index 7c0af99..8dc4077 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -1,35 +1,35 @@ -(function ($) { - $.entwine('ss', function ($) { +(function($) { + $.entwine("ss", function($) { // Hide our custom context menu when not needed - $(document).on('mousedown', function (event) { - $parents = $(event.target).parents('.treeview-menu'); + $(document).on("mousedown", function(event) { + $parents = $(event.target).parents(".treeview-menu"); if ($parents.length == 0) { - $('.treeview-menu').remove(); + $(".treeview-menu").remove(); } }); // Show our search form after opening the search dialog // Show our detail form after opening the detail dialog - $('.add-existing-search-dialog, .view-detail-dialog').entwine({ - loadDialog: function (deferred) { - var dialog = this.addClass('loading') - .children('.ui-dialog-content') + $(".add-existing-search-dialog, .view-detail-dialog").entwine({ + loadDialog: function(deferred) { + var dialog = this.addClass("loading") + .children(".ui-dialog-content") .empty(); - deferred.done(function (data) { + deferred.done(function(data) { dialog .html(data) .parent() - .removeClass('loading'); + .removeClass("loading"); }); } }); // Submit our search form to our own endpoint and show the results - $('.add-existing-search-dialog .add-existing-search-form').entwine({ - onsubmit: function () { - this.closest('.add-existing-search-dialog').loadDialog( - $.get(this.prop('action'), this.serialize()) + $(".add-existing-search-dialog .add-existing-search-form").entwine({ + onsubmit: function() { + this.closest(".add-existing-search-dialog").loadDialog( + $.get(this.prop("action"), this.serialize()) ); return false; } @@ -37,37 +37,40 @@ // Allow clicking the elements in the search form $( - '.add-existing-search-dialog .add-existing-search-items .list-group-item-action' + ".add-existing-search-dialog .add-existing-search-items .list-group-item-action" ).entwine({ - onclick: function () { - if (this.children('a').length > 0) { - this.children('a') + onclick: function() { + if (this.children("a").length > 0) { + this.children("a") .first() - .trigger('click'); + .trigger("click"); } } }); // Trigger the "add existing" action on the selected element - $('.add-existing-search-dialog .add-existing-search-items a').entwine({ - onclick: function () { - var link = this.closest('.add-existing-search-items').data('add-link'); - var id = this.data('id'); - - var dialog = this.closest('.add-existing-search-dialog') - .addClass('loading') - .children('.ui-dialog-content') + $(".add-existing-search-dialog .add-existing-search-items a").entwine({ + onclick: function() { + var link = this.closest(".add-existing-search-items").data("add-link"); + var id = this.data("id"); + + var dialog = this.closest(".add-existing-search-dialog") + .addClass("loading") + .children(".ui-dialog-content") .empty(); - dialog.data('treeview').reload({ + dialog.data("treeview").reload( + { url: link, - data: [{ - name: 'id', - value: id - }] + data: [ + { + name: "id", + value: id + } + ] }, - function () { - dialog.dialog('close'); + function() { + dialog.dialog("close"); } ); @@ -76,23 +79,25 @@ }); // Browse search result pages - $('.add-existing-search-dialog .add-existing-search-pagination a').entwine({ - onclick: function () { - this.closest('.add-existing-search-dialog').loadDialog( - $.get(this.prop('href')) + $(".add-existing-search-dialog .add-existing-search-pagination a").entwine({ + onclick: function() { + this.closest(".add-existing-search-dialog").loadDialog( + $.get(this.prop("href")) ); return false; } }); // Save the item in the detail view - $('.view-detail-dialog .view-detail-form').entwine({ - onsubmit: function () { - var dialog = this.closest('.view-detail-dialog').children('.ui-dialog-content'); + $(".view-detail-dialog .view-detail-form").entwine({ + onsubmit: function() { + var dialog = this.closest(".view-detail-dialog").children( + ".ui-dialog-content" + ); - $.post(this.prop('action'), this.serialize(), function () { - dialog.data('treeview').reload(); - dialog.dialog('close'); + $.post(this.prop("action"), this.serialize(), function() { + dialog.data("treeview").reload(); + dialog.dialog("close"); }); return false; @@ -100,125 +105,131 @@ }); // Attach data to our tree view - $('.treeview-pagesections').entwine({ - addItem: function (parents, itemId, elemType, sort = 99999) { + $(".treeview-pagesections").entwine({ + addItem: function(parents, itemId, elemType, sort = 99999) { var $treeView = $(this); $treeView.reload({ - url: $treeView.data('url') + '/add', - data: [{ - name: 'parents', + url: $treeView.data("url") + "/add", + data: [ + { + name: "parents", value: parents }, { - name: 'itemId', + name: "itemId", value: itemId }, { - name: 'type', + name: "type", value: elemType }, { - name: 'sort', + name: "sort", value: sort } ] }); }, - removeItem: function (parents, itemId) { + removeItem: function(parents, itemId) { var $treeView = $(this); $treeView.reload({ - url: $treeView.data('url') + '/remove', - data: [{ - name: 'parents', + url: $treeView.data("url") + "/remove", + data: [ + { + name: "parents", value: parents }, { - name: 'itemId', + name: "itemId", value: itemId } ] }); }, - deleteItem: function (parents, itemId) { + deleteItem: function(parents, itemId) { var $treeView = $(this); $treeView.reload({ - url: $treeView.data('url') + '/delete', - data: [{ - name: 'parents', + url: $treeView.data("url") + "/delete", + data: [ + { + name: "parents", value: parents }, { - name: 'itemId', + name: "itemId", value: itemId } ] }); }, - onadd: function () { + onadd: function() { var $treeView = $(this); - var name = $treeView.data('name'); - var url = $treeView.data('url'); + var name = $treeView.data("name"); + var url = $treeView.data("url"); // Setup add new button $treeView - .find('.treeview-actions-addnew .tree-button') - .click(function () { + .find(".treeview-actions-addnew .tree-button") + .click(function() { $treeView.reload({ - url: url + '/add', - data: [{ - name: 'type', - value: $('#AddNewClass').val() - }] + url: url + "/add", + data: [ + { + name: "type", + value: $("#AddNewClass").val() + } + ] }); }); // Setup find existing button - $treeView.find('button[name=action_FindExisting]').click(function () { - var dialog = $('
                                        ') - .appendTo('body') + $treeView.find("button[name=action_FindExisting]").click(function() { + var dialog = $("
                                        ") + .appendTo("body") .dialog({ modal: true, resizable: false, width: 500, height: 600, - close: function () { + close: function() { $(this) - .dialog('destroy') + .dialog("destroy") .remove(); } }); dialog .parent() - .addClass('add-existing-search-dialog') - .loadDialog($.get($treeView.data('url') + '/search')); - dialog.data('treeview', $treeView); + .addClass("add-existing-search-dialog") + .loadDialog($.get($treeView.data("url") + "/search")); + dialog.data("treeview", $treeView); }); // Process items - $treeView.find('.treeview-item').each(function () { + $treeView.find(".treeview-item").each(function() { var $item = $(this); - var itemId = $item.data('id'); - var parents = $item.data('tree'); + var itemId = $item.data("id"); + var parents = $item.data("tree"); // Open an item button $item - .find('> .treeview-item-flow .tree-button') - .click(function (event) { + .find("> .treeview-item-flow .tree-button") + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); $treeView.reload({ - url: url + '/tree', - data: [{ - name: 'parents', + url: url + "/tree", + data: [ + { + name: "parents", value: parents }, { - name: 'itemId', + name: "itemId", value: itemId } ] @@ -226,61 +237,61 @@ }); // Edit button - $item - .find('> .treeview-item-actions .edit-button') - .click(function () { - var dialog = $('
                                        ') - .appendTo('body') - .dialog({ - modal: false, - resizable: false, - width: $(window).width() * 0.9, - height: $(window).height() * 0.9, - close: function () { - $(this) - .dialog('destroy') - .remove(); - } - }); + $item.find("> .treeview-item-actions .edit-button").click(function() { + var dialog = $("
                                        ") + .appendTo("body") + .dialog({ + modal: false, + resizable: false, + width: $(window).width() * 0.9, + height: $(window).height() * 0.9, + close: function() { + $(this) + .dialog("destroy") + .remove(); + } + }); - dialog - .parent() - .addClass('view-detail-dialog') - .loadDialog($.get($treeView.data('url') + '/detail?ID=' + itemId)); - dialog.data('treeview', $treeView); - }); + dialog + .parent() + .addClass("view-detail-dialog") + .loadDialog( + $.get($treeView.data("url") + "/detail?ID=" + itemId) + ); + dialog.data("treeview", $treeView); + }); // Add new item button $item - .find('> .treeview-item-actions .add-button') - .click(function (event) { + .find("> .treeview-item-actions .add-button") + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); $target = $(event.target); - var elems = $target.data('allowed-elements'); + var elems = $target.data("allowed-elements"); var $menu = $( "
                                          " + name + + "' class='treeview-menu' data-id='" + + itemId + + "'>" ); $menu.css({ - top: event.pageY + 'px', - left: event.pageX + 'px' + top: event.pageY + "px", + left: event.pageX + "px" }); $(document.body).append($menu); $menu.append( "
                                        • " + - ss.i18n._t('PageSections.TreeView.AddAChild', 'Add a child') + - '
                                        • ' + ss.i18n._t("PageSections.TreeView.AddAChild", "Add a child") + + "" ); - $.each(elems, function (key, value) { - var $li = $("
                                        • " + value + '
                                        • '); - $li.click(function () { + $.each(elems, function(key, value) { + var $li = $("
                                        • " + value + "
                                        • "); + $li.click(function() { $treeView.addItem(parents, itemId, key); $menu.remove(); }); @@ -291,35 +302,38 @@ // Add new after item button $item - .find('> .treeview-item-actions .add-after-button') - .click(function (event) { + .find("> .treeview-item-actions .add-after-button") + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); $target = $(event.target); - var elems = $target.data('allowed-elements'); + var elems = $target.data("allowed-elements"); var $menu = $( "
                                            " + name + + "' class='treeview-menu' data-id='" + + itemId + + "'>" ); $menu.css({ - top: event.pageY + 'px', - left: event.pageX + 'px' + top: event.pageY + "px", + left: event.pageX + "px" }); $(document.body).append($menu); $menu.append( "
                                          • " + - ss.i18n._t('PageSections.TreeView.AddAfterThis', 'Add after this element') + - '
                                          • ' + ss.i18n._t( + "PageSections.TreeView.AddAfterThis", + "Add after this element" + ) + + "" ); - $.each(elems, function (key, value) { - var $li = $("
                                          • " + value + '
                                          • '); - $li.click(function () { + $.each(elems, function(key, value) { + var $li = $("
                                          • " + value + "
                                          • "); + $li.click(function() { $treeView.addItem( parents.slice(0, parents.length - 1), parents[parents.length - 1], @@ -335,8 +349,8 @@ // Delete button action $item - .find('> .treeview-item-actions .delete-button') - .click(function (event) { + .find("> .treeview-item-actions .delete-button") + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -344,43 +358,43 @@ var $menu = $( "
                                              " + name + + "' class='treeview-menu' data-id='" + + itemId + + "'>" ); $menu.css({ - top: event.pageY + 'px', - left: event.pageX + 'px' + top: event.pageY + "px", + left: event.pageX + "px" }); $(document.body).append($menu); $menu.append( "
                                            • " + - ss.i18n._t('PageSections.TreeView.Delete', 'Delete') + - '
                                            • ' + ss.i18n._t("PageSections.TreeView.Delete", "Delete") + + "" ); var $li = $( "
                                            • " + - ss.i18n._t('PageSections.TreeView.RemoveAChild', 'Remove') + - '
                                            • ' + ss.i18n._t("PageSections.TreeView.RemoveAChild", "Remove") + + "" ); - $li.click(function () { + $li.click(function() { $treeView.removeItem(parents, itemId); $menu.remove(); }); $menu.append($li); - if ($target.data('used-count') < 2) { + if ($target.data("used-count") < 2) { var $li = $( - '
                                            • ' + - ss.i18n._t( - 'PageSections.TreeView.DeleteAChild', - 'Finally delete' - ) + - '
                                            • ' + "
                                            • " + + ss.i18n._t( + "PageSections.TreeView.DeleteAChild", + "Finally delete" + ) + + "
                                            • " ); - $li.click(function () { + $li.click(function() { $treeView.deleteItem(parents, itemId); $menu.remove(); }); @@ -392,50 +406,50 @@ // Attach draggable events & info $item.find("> .treeview-item-flow").draggable({ - revert: 'invalid', - cursor: 'crosshair', + revert: "invalid", + cursor: "crosshair", cursorAt: { top: -15, left: -15 }, - activeClass: 'state-active', - hoverClass: 'state-active', - tolerance: 'pointer', + activeClass: "state-active", + hoverClass: "state-active", + tolerance: "pointer", greedy: true, - helper: function () { + helper: function() { var $helper = $( "
                                              " + - $item.find('.treeview-item-content__title').text() + - '
                                              ' + $item.find(".treeview-item-content__title").text() + + "
                                              " ); - $item.css('opacity', 0.6); + $item.css("opacity", 0.6); return $helper; }, - start: function () { - $('.ui-droppable').each(function () { + start: function() { + $(".ui-droppable").each(function() { var $drop = $(this); - var $dropItem = $drop.closest('.treeview-item'); - var isOpen = $dropItem.data('is-open'); + var $dropItem = $drop.closest(".treeview-item"); + var isOpen = $dropItem.data("is-open"); // Dont enable dropping on itself if ( - $dropItem.data('id') == itemId && - $dropItem.data('tree') == parents + $dropItem.data("id") == itemId && + $dropItem.data("tree") == parents ) { return; } // Don't enable dropping on the middle arrow for open items // (they will have child elements where we can drop before or after) - if ($drop.hasClass('middle') && isOpen) { + if ($drop.hasClass("middle") && isOpen) { return; } // Dont enable dropping on .after of itself if ( - $drop.hasClass('after') && - $dropItem.next().data('id') == itemId + $drop.hasClass("after") && + $dropItem.next().data("id") == itemId ) { return; } @@ -443,18 +457,18 @@ // Dont enable dropping on .middle of other same id elements // (no recursive structures) if ( - $drop.hasClass('middle') && - $dropItem.data('id') == itemId + $drop.hasClass("middle") && + $dropItem.data("id") == itemId ) { return; } // Don't allow dropping elements on the root level that aren't allowed there if ( - $dropItem.data('tree').length == 0 && - ($drop.hasClass('before') || $drop.hasClass('after')) + $dropItem.data("tree").length == 0 && + ($drop.hasClass("before") || $drop.hasClass("after")) ) { - if (!$item.data('allowed-root')) { + if (!$item.data("allowed-root")) { return; } } @@ -462,15 +476,15 @@ // Don't allow dropping elements on this level if they're not an allowed child // Depending on the arrow we either have to check this element or the parent // of this element to see which children are allowed - var clazz = $item.data('class'); - if ($drop.hasClass('before') || $drop.hasClass('after')) { - var $parent = $dropItem.parent().closest('.treeview-item'); - var allowed = $parent.data('allowed-elements'); + var clazz = $item.data("class"); + if ($drop.hasClass("before") || $drop.hasClass("after")) { + var $parent = $dropItem.parent().closest(".treeview-item"); + var allowed = $parent.data("allowed-elements"); if (allowed && !allowed[clazz]) { return; } } else { - var allowed = $dropItem.data('allowed-elements'); + var allowed = $dropItem.data("allowed-elements"); if (allowed && !allowed[clazz]) { return; } @@ -479,104 +493,114 @@ $drop.show(); }); }, - stop: function (event, ui) { - $('.ui-droppable').hide(); + stop: function(event, ui) { + $(".ui-droppable").hide(); // Show the previous elements. If the user made an invalid movement then // we want this to show anyways. If he did something valid the treeview will // refresh so we don't care if it's visible behind the loading icon. - $('.treeview-item').css('opacity', ''); + $(".treeview-item").css("opacity", ""); } }); // Dropping targets - $item.find('> .treeview-item-flow .treeview-item-reorder div').each(function () { - $(this).droppable({ - hoverClass: 'state-active', - tolerance: 'pointer', - drop: function (event, ui) { - $drop = $(this); - $dropItem = $drop.closest('.treeview-item'); - - $oldItem = ui.draggable.closest('.treeview-item'); - var oldId = $oldItem.data('id'); - var oldParents = $oldItem.data('tree'); - - var type = 'child'; - var sort = 100000; - - if ($drop.hasClass('before')) { - type = 'before'; - sort = $dropItem.data('sort') - 1; - } else if ($drop.hasClass('after')) { - type = 'after'; - sort = $dropItem.data('sort') + 1; - } + $item + .find("> .treeview-item-flow .treeview-item-reorder div") + .each(function() { + $(this).droppable({ + hoverClass: "state-active", + tolerance: "pointer", + drop: function(event, ui) { + $drop = $(this); + $dropItem = $drop.closest(".treeview-item"); + + $oldItem = ui.draggable.closest(".treeview-item"); + var oldId = $oldItem.data("id"); + var oldParents = $oldItem.data("tree"); + + var type = "child"; + var sort = 100000; + + if ($drop.hasClass("before")) { + type = "before"; + sort = $dropItem.data("sort") - 1; + } else if ($drop.hasClass("after")) { + type = "after"; + sort = $dropItem.data("sort") + 1; + } - var newParent = - type === 'child' ? - itemId : (parents.length > 0 ? parents[parents.length - 1] : ''); - - $treeView.reload({ - url: url + '/move', - data: [{ - name: 'parents', - value: oldParents - }, - { - name: 'itemId', - value: oldId - }, - { - name: 'newParent', - value: newParent - }, - { - name: 'sort', - value: sort - } - ] - }); - } + var newParent = + type === "child" + ? itemId + : parents.length > 0 + ? parents[parents.length - 1] + : ""; + + $treeView.reload({ + url: url + "/move", + data: [ + { + name: "parents", + value: oldParents + }, + { + name: "itemId", + value: oldId + }, + { + name: "newParent", + value: newParent + }, + { + name: "sort", + value: sort + } + ] + }); + } + }); }); - }); }); }, // This is copy paste from SilverStripe GridField.js, modified to work for the TreeView // It updates the gridfield by sending the specified request // and using the response as the new content for the gridfield - reload: function (ajaxOpts, successCallback) { + reload: function(ajaxOpts, successCallback) { var self = this, - form = this.closest('form'), - focusedElName = this.find(':input:focus').attr('name'), // Save focused element for restoring after refresh - data = form.find(':input').serializeArray(); + form = this.closest("form"), + focusedElName = this.find(":input:focus").attr("name"), // Save focused element for restoring after refresh + data = form.find(":input").serializeArray(); if (!ajaxOpts) ajaxOpts = {}; if (!ajaxOpts.data) ajaxOpts.data = []; - ajaxOpts.data = ajaxOpts.data.concat(data).concat([{ - name: 'state', - value: self.data('state-id') - }]); + ajaxOpts.data = ajaxOpts.data.concat(data).concat([ + { + name: "state", + value: self.data("state-id") + } + ]); // Include any GET parameters from the current URL, as the view state might depend on it. // For example, a list prefiltered through external search criteria might be passed to GridField. if (window.location.search) { ajaxOpts.data = - window.location.search.replace(/^\?/, '') + - '&' + + window.location.search.replace(/^\?/, "") + + "&" + $.param(ajaxOpts.data); } - form.addClass('loading'); + form.addClass("loading"); $.ajax( - $.extend({}, { + $.extend( + {}, + { headers: { - 'X-Pjax': 'CurrentField' + "X-Pjax": "CurrentField" }, - type: 'POST', - url: this.data('url'), - dataType: 'html', - success: function (data) { + type: "POST", + url: this.data("url"), + dataType: "html", + success: function(data) { // Replace the grid field with response, not the form. // TODO Only replaces all its children, to avoid replacing the current scope // of the executing method. Means that it doesn't retrigger the onmatch() on the main container. @@ -587,14 +611,14 @@ if (focusedElName) self.find(':input[name="' + focusedElName + '"]').focus(); - form.removeClass('loading'); + form.removeClass("loading"); if (successCallback) successCallback.apply(this, arguments); // TODO: Don't know how original SilverStripe GridField magically calls self.onadd(); }, - error: function (e) { - alert(i18n._t('Admin.ERRORINTRANSACTION')); - form.removeClass('loading'); + error: function(e) { + alert(i18n._t("Admin.ERRORINTRANSACTION")); + form.removeClass("loading"); } }, ajaxOpts From 4d24aa9b9e6b9004253bbfa873ac0eb7d20e87b2 Mon Sep 17 00:00:00 2001 From: Felix Eggmann Date: Sat, 11 Aug 2018 13:09:39 +0200 Subject: [PATCH 28/82] fix(context-menu): avoid menu to overlap the window --- javascript/TreeView.js | 171 ++++++++++++++++++++++------------------- 1 file changed, 93 insertions(+), 78 deletions(-) diff --git a/javascript/TreeView.js b/javascript/TreeView.js index 8dc4077..7589aaf 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -1,4 +1,51 @@ (function($) { + function TreeViewContextMenu() { + this.createDom = function(id, name) { + this.$menu = $( + "
                                                " + ); + }; + this.addLabel = function(label) { + this.$menu.append("
                                              • " + label + "
                                              • "); + }; + this.addItem = function(type, label, onClick = function() {}) { + var $li = $("
                                              • " + label + "
                                              • "); + $li.click(onClick); + this.$menu.append($li); + }; + this.show = function(x, y) { + var pos = { + top: y, + left: x + }; + + $(document.body).append(this.$menu); + this.$menu.css(pos); + this.$menu.show(); + var that = this; + window.requestAnimationFrame(function() { + var wW = $(window).width(); + var wH = $(window).height(); + var eW = that.$menu.outerWidth(true); + var eH = that.$menu.outerHeight(true); + if (pos.left + eW > wW) { + pos.let = wW - eW; + } + if (pos.top + eH > wH) { + pos.top = wH - eH; + } + that.$menu.css(pos); + }); + }; + this.remove = function() { + this.$menu.remove(); + }; + } + $.entwine("ss", function($) { // Hide our custom context menu when not needed $(document).on("mousedown", function(event) { @@ -271,33 +318,18 @@ $target = $(event.target); var elems = $target.data("allowed-elements"); - var $menu = $( - "
                                                  " - ); - $menu.css({ - top: event.pageY + "px", - left: event.pageX + "px" - }); - $(document.body).append($menu); - - $menu.append( - "
                                                • " + - ss.i18n._t("PageSections.TreeView.AddAChild", "Add a child") + - "
                                                • " + var menu = new TreeViewContextMenu(); + menu.createDom(itemId, name); + menu.addLabel( + ss.i18n._t("PageSections.TreeView.AddAChild", "Add a child") ); $.each(elems, function(key, value) { - var $li = $("
                                                • " + value + "
                                                • "); - $li.click(function() { + menu.addItem(key, value, function() { $treeView.addItem(parents, itemId, key); - $menu.remove(); + menu.remove(); }); - $menu.append($li); }); - $menu.show(); + menu.show(event.pageX, event.pageY); }); // Add new after item button @@ -310,41 +342,27 @@ $target = $(event.target); var elems = $target.data("allowed-elements"); - var $menu = $( - "
                                                    " + var menu = new TreeViewContextMenu(); + menu.createDom(itemId, name); + menu.addLabel( + ss.i18n._t( + "PageSections.TreeView.AddAfterThis", + "Add after this element" + ) ); - $menu.css({ - top: event.pageY + "px", - left: event.pageX + "px" - }); - $(document.body).append($menu); - $menu.append( - "
                                                  • " + - ss.i18n._t( - "PageSections.TreeView.AddAfterThis", - "Add after this element" - ) + - "
                                                  • " - ); $.each(elems, function(key, value) { - var $li = $("
                                                  • " + value + "
                                                  • "); - $li.click(function() { + menu.addItem(key, value, function() { $treeView.addItem( parents.slice(0, parents.length - 1), parents[parents.length - 1], key, $item.data("sort") + 1 ); - $menu.remove(); + menu.remove(); }); - $menu.append($li); }); - $menu.show(); + menu.show(event.pageX, event.pageY); }); // Delete button action @@ -356,52 +374,49 @@ $target = $(event.target); - var $menu = $( - "
                                                      " + var menu = new TreeViewContextMenu(); + menu.createDom(itemId, name); + menu.addLabel( + ss.i18n._t("PageSections.TreeView.Delete", "Delete") ); - $menu.css({ - top: event.pageY + "px", - left: event.pageX + "px" - }); - $(document.body).append($menu); - $menu.append( - "
                                                    • " + - ss.i18n._t("PageSections.TreeView.Delete", "Delete") + - "
                                                    • " + menu.addItem( + "__REMOVE__", + ss.i18n._t("PageSections.TreeView.RemoveAChild", "Remove"), + function() { + $treeView.removeItem(parents, itemId); + menu.remove(); + } ); - var $li = $( - "
                                                    • " + - ss.i18n._t("PageSections.TreeView.RemoveAChild", "Remove") + - "
                                                    • " - ); - $li.click(function() { - $treeView.removeItem(parents, itemId); - $menu.remove(); - }); - $menu.append($li); if ($target.data("used-count") < 2) { + menu.addItem( + "", + ss.i18n._t( + "PageSections.TreeView.DeleteAChild", + "Finally delete" + ), + function() { + $treeView.deleteItem(parents, itemId); + menu.remove(); + } + ); + var $li = $( "
                                                    • " + ss.i18n._t( "PageSections.TreeView.DeleteAChild", "Finally delete" ) + - "
                                                    • " + "", + function() { + $treeView.deleteItem(parents, itemId); + menu.remove(); + } ); - $li.click(function() { - $treeView.deleteItem(parents, itemId); - $menu.remove(); - }); - $menu.append($li); } - $menu.show(); + menu.show(event.pageX, event.pageY); }); // Attach draggable events & info From 7894d008217a0401874efe741e20e0e7f3b9dbc2 Mon Sep 17 00:00:00 2001 From: Felix Eggmann Date: Sat, 11 Aug 2018 13:33:15 +0200 Subject: [PATCH 29/82] fix(treeview): fix header placement and show singular_name in add new dropdown --- css/TreeView.css | 22 ++++++++++++++++--- src/TreeView.php | 8 ++++--- .../PageSections/TreeViewAddNewButton.ss | 1 - 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/css/TreeView.css b/css/TreeView.css index 0f1596d..972ac6e 100644 --- a/css/TreeView.css +++ b/css/TreeView.css @@ -4,6 +4,17 @@ .treeview-pagesections {} +.treeview-pagesections__header { + display: flex; + align-items: center; + margin-bottom: 10px; +} + +.tree-actions-findexisting { + margin-right: 0; + margin-left: auto; +} + .treeview-item { position: relative; box-sizing: border-box; @@ -90,11 +101,16 @@ */ .treeview-actions-addnew { - display: inline-block; + display: flex; + align-items: center; } -.treeview-actions-addnew div { - display: inline-block; +.treeview-actions-addnew__dropdown { + flex: 1 0 200px; + padding-bottom: 0; + margin-bottom: 0; + margin-right: 5px; + border-bottom: 0; } /* diff --git a/src/TreeView.php b/src/TreeView.php index 71a0964..e7f028d 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -565,16 +565,17 @@ public function FieldHolder($properties = array()) $session = Controller::curr()->getRequest()->getSession(); $session->set($sessionId, $this->opens); - $content = ''; + $content = '
                                                      '; $classes = $this->section->getAllowedPageElements(); $elems = []; foreach ($classes as $class) { - $elems[$class] = $class::getSingularName(); + $elems[$class] = singleton($class)->singular_name(); } // Create the add new multi class button $addNew = DropdownField::create('AddNewClass', '', $elems); + $addNew->addExtraClass("treeview-actions-addnew__dropdown"); $content .= ArrayData::create([ "Title" => "Add new", "ClassField" => $addNew @@ -582,8 +583,9 @@ public function FieldHolder($properties = array()) // Create the find existing button $findExisting = TreeViewFormAction::create($this, 'FindExisting', 'Find existing'); - $findExisting->addExtraClass("tree-actions-findexisting"); + $findExisting->addExtraClass("btn font-icon-search tree-actions-findexisting"); $content .= $findExisting->forTemplate(); + $content .= "
                                                      "; $list = $this->getItems()->sort($this->sortField)->toArray(); diff --git a/templates/FLXLabs/PageSections/TreeViewAddNewButton.ss b/templates/FLXLabs/PageSections/TreeViewAddNewButton.ss index 4d9788c..0e32535 100644 --- a/templates/FLXLabs/PageSections/TreeViewAddNewButton.ss +++ b/templates/FLXLabs/PageSections/TreeViewAddNewButton.ss @@ -1,5 +1,4 @@
                                                      $ClassField.FieldHolder -
                                                      From 41f313c29ec9cf601a866676764f6974b5e21e0b Mon Sep 17 00:00:00 2001 From: Felix Eggmann Date: Sat, 11 Aug 2018 18:35:18 +0200 Subject: [PATCH 30/82] feat(treeview): implement css and layout --- css/TreeView.css | 249 ++++++++++++------ javascript/TreeView.js | 206 ++++++++------- src/TreeView.php | 50 ++-- .../PageSections/TreeViewPageElement.ss | 52 ++-- 4 files changed, 335 insertions(+), 222 deletions(-) diff --git a/css/TreeView.css b/css/TreeView.css index 972ac6e..ddd9045 100644 --- a/css/TreeView.css +++ b/css/TreeView.css @@ -15,32 +15,99 @@ margin-left: auto; } -.treeview-item { +.treeview-item__panel { position: relative; box-sizing: border-box; - border: 1px solid black; + background: #fff; + border-radius: 4px; + border: 1px solid #C1CADA; + display: flex; + flex-flow: column nowrap; + justify-content: space-between; + min-height: 95px; +} + +.treeview-item:first-child { + margin-top: 0; +} + +.treeview-item:last-child { + margin-bottome: 0; } .treeview-item-flow { + flex: 1 1 auto; display: flex; + flex-flow: row nowrap; justify-content: flex-start; align-items: stretch; + padding: 8px 0; +} + +.treeview-item__treeswitch { + flex: 0 0 30px; +} + +.treeview-item__treeswitch__button { + width: 100%; + height: 30px; + margin: 0; + margin-top: -7px; + padding: 0; + font-size: 114%; + padding-left: 5px; + box-sizing: border-box; + background: transparent; +} + +.treeview-item__treeswitch__button.is-end { + opacity: 0; +} + +/* +.treeview-item__treeswitch__button:hover { + color: #005489; + border-color: #005489; +} +.treeview-item__treeswitch__button:before { + position: absolute; + left: -0.08em; + top: -0.08em; + font-size: 90%; +} +*/ +.treeview-item__treeswitch__button .btn__title { + display: none; } -.treeview-item-children { - padding-left: 2em; +.treeview-item__preview { + margin-right: auto; + padding-left: 5px; + padding-right: 5px; +} + +.treeview-item__children { + padding-left: 30px; + padding-right: 5px; + border-bottom: 1px solid #fff; } .treeview-item-content { - display: flex; - flex-flow: row nowrap; - justify-content: flex-start; - align-items: flex-start; - padding-top: 0.5em; + flex: 0 0 300px; + border-right: 1px solid #C1CADA; + padding-right: 5px; +} + +.treeview-item .treeview-item .treeview-item-content { + flex: 0 1 270px; +} + +.treeview-item .treeview-item .treeview-item .treeview-item-content { + flex: 0 1 240px; } -.treeview-item-content__text { - margin-left: 1em; +.treeview-item .treeview-item .treeview-item .treeview-item .treeview-item-content { + flex: 0 1 210px; } .treeview-item-content__classname { @@ -48,52 +115,43 @@ } .treeview-item-content__title { + font-size: 18px; font-weight: bold; } -.treeview-item .treeview-item-actions { - background: #ccc; +.treeview-item-actions { + display: flex; } -.treeview-item .tree-button { - appearance: none; - background: transparent; - border: 2px solid #43536d; - color: #43536d; - border-radius: 0.5em; - width: 1em; - height: 1em; - padding: 0; - margin: 0.25em 0 0 0; - box-sizing: border-box; - display: block; - position: relative; - cursor: pointer; - font-size: 114%; +.treeview-item-actions--edit { + position: absolute; + top: 0; + right: 0; + display: none; + background: #fff; } -.treeview-item .tree-button.is-end { - opacity: 0; +.treeview-item-flow:hover .treeview-item-actions--edit { + display: block; } -.treeview-item .tree-button:hover { - color: #005489; - border-color: #005489; +.treeview-item-actions--pre { + background: transparent; + padding-left: 26px; } -.treeview-item .tree-button:before { - position: absolute; - left: -0.08em; - top: -0.08em; - font-size: 90%; +.treeview-item-actions--pre .btn[disabled] { + display: none; } -.treeview-item .tree-button .btn__title { - display: none; +.treeview-item-actions .btn, .treeview-item .treeview-item__post-actions .btn { + padding-left: 0; + background: transparent; + color: #43536d; } -.treeview-item-fill { - flex: 1; +.treeview-item-actions .edit-button { + margin-left: auto; } /* @@ -105,12 +163,24 @@ align-items: center; } +.treeview-actions-addnew .form-group::after { + content: none; +} + .treeview-actions-addnew__dropdown { flex: 1 0 200px; padding-bottom: 0; - margin-bottom: 0; - margin-right: 5px; border-bottom: 0; + margin: 0 5px 0 0; +} + +@media(min-width: 992px) { + .treeview-actions-addnew__dropdown .form__field-holder { + margin-left: 0 !important; + margin-right: 5px !important; + flex: 1 1 auto !important; + max-width: none !important; + } } /* @@ -161,43 +231,60 @@ * Orderable rows */ -.treeview-item__draggable { - z-index: 300; - background: #f6f7f8; - padding: 2px 4px; - border-radius: 2px; - box-shadow: 0 0 1px #ccc; +.treeview-item__dragger { + z-index: 3000; +} + +.treeview-item__dragger .treeview-item-actions, +.treeview-item__dragger .treeview-item-reorder .droppable, +.treeview-item__dragger .treeview-item__post-actions { + display: none !important; } .treeview-item__draggable.state-active { border: 1px solid #417505; } -.treeview-item__draggable:before { +.treeview-item-reorder { position: absolute; - content: ''; - left: -19px; - top: -19px; - width: 8px; - height: 8px; - border: 3px solid #4a4a4a; - border-radius: 50%; - box-sizing: border-box; + z-index: 200; + bottom: -5px; + top: -5px; + left: 0; + right: 0; + pointer-events: none; } -.treeview-item-reorder { +.treeview-item-reorder__handle { + pointer-events: all; + width: 30px; + height: 38px; position: absolute; - height: 100%; + top: 50%; + margin-top: -19px; + font-size: 30px; + cursor: pointer; } .treeview-item-reorder .ui-droppable { position: absolute; - left: 100%; - width: 2em; + left: 0; + right: 0; z-index: 100; display: none; } +.treeview-item-reorder .ui-droppable:before { + content: ""; + position: absolute; + left: -4px; + top: -2px; + right: -4px; + bottom: -2px; + border-radius: 5px; + border: 1px dashed transparent; +} + .treeview-item-reorder .ui-droppable svg { position: absolute; left: 0; @@ -206,40 +293,44 @@ transform: translate(0, -50%); } +.treeview-item-reorder .ui-droppable:not(.middle) svg { + transform: translate(30px, -50%); +} + .treeview-item-reorder .ui-droppable svg path { fill: #a8cb7f; } -.treeview-item-reorder .ui-droppable.before { +.treeview-item-reorder .ui-droppable.middle { top: 0; - height: 33%; + bottom: 0; + left: -15px; } -.treeview-item-reorder .ui-droppable.before svg { - top: 0; +.treeview-item-reorder .ui-droppable.middle svg { + transform: translate(0, -50%) scale(-1, 1) } -.treeview-item-reorder .ui-droppable.middle { - top: 33%; - bottom: 33%; +.treeview-item-reorder .ui-droppable.before { + z-index: 1; + bottom: 100%; + height: 24px; } -.treeview-item-reorder .ui-droppable.middle svg {} - .treeview-item-reorder .ui-droppable.after { - bottom: -1px; - height: 33%; -} - -.treeview-item-reorder .ui-droppable.after svg { + z-index: 1; top: 100%; - margin-top: -1px; + height: 24px; } .treeview-item-reorder .ui-droppable.state-active { z-index: 200; } +.treeview-item-reorder .ui-droppable.state-active:before { + border-color: #417505; +} + .treeview-item-reorder .ui-droppable.state-active svg path { - fill: #417505; + fill: #008A00; } diff --git a/javascript/TreeView.js b/javascript/TreeView.js index 7589aaf..e8caf95 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -32,11 +32,13 @@ var wH = $(window).height(); var eW = that.$menu.outerWidth(true); var eH = that.$menu.outerHeight(true); + pos.left = pos.left - eW * 0.5; + pos.top = pos.top - eH * 0.5; if (pos.left + eW > wW) { - pos.let = wW - eW; + pos.left = wW - eW - 2; } if (pos.top + eH > wH) { - pos.top = wH - eH; + pos.top = wH - eH - 2; } that.$menu.css(pos); }); @@ -263,7 +265,7 @@ // Open an item button $item - .find("> .treeview-item-flow .tree-button") + .find("> .treeview-item__panel > .treeview-item-flow .tree-button") .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -284,33 +286,37 @@ }); // Edit button - $item.find("> .treeview-item-actions .edit-button").click(function() { - var dialog = $("
                                                      ") - .appendTo("body") - .dialog({ - modal: false, - resizable: false, - width: $(window).width() * 0.9, - height: $(window).height() * 0.9, - close: function() { - $(this) - .dialog("destroy") - .remove(); - } - }); + $item + .find("> .treeview-item__panel > .treeview-item-flow .edit-button") + .click(function() { + var dialog = $("
                                                      ") + .appendTo("body") + .dialog({ + modal: false, + resizable: false, + width: $(window).width() * 0.9, + height: $(window).height() * 0.9, + close: function() { + $(this) + .dialog("destroy") + .remove(); + } + }); - dialog - .parent() - .addClass("view-detail-dialog") - .loadDialog( - $.get($treeView.data("url") + "/detail?ID=" + itemId) - ); - dialog.data("treeview", $treeView); - }); + dialog + .parent() + .addClass("view-detail-dialog") + .loadDialog( + $.get($treeView.data("url") + "/detail?ID=" + itemId) + ); + dialog.data("treeview", $treeView); + }); // Add new item button $item - .find("> .treeview-item-actions .add-button") + .find( + "> .treeview-item__panel > .treeview-item-actions .add-button" + ) .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -334,7 +340,7 @@ // Add new after item button $item - .find("> .treeview-item-actions .add-after-button") + .find("> .treeview-item__post-actions .add-after-button") .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -347,7 +353,7 @@ menu.addLabel( ss.i18n._t( "PageSections.TreeView.AddAfterThis", - "Add after this element" + "Add new element" ) ); @@ -367,7 +373,9 @@ // Delete button action $item - .find("> .treeview-item-actions .delete-button") + .find( + "> .treeview-item__panel > .treeview-item-flow .delete-button" + ) .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -420,27 +428,37 @@ }); // Attach draggable events & info - $item.find("> .treeview-item-flow").draggable({ + $item.find(".treeview-item-reorder__handle").draggable({ + //cancel: ".treeview-item", + appendTo: "body", revert: "invalid", cursor: "crosshair", cursorAt: { - top: -15, - left: -15 + top: 30, + left: 15 }, activeClass: "state-active", hoverClass: "state-active", tolerance: "pointer", greedy: true, - helper: function() { - var $helper = $( - "
                                                      " + - $item.find(".treeview-item-content__title").text() + - "
                                                      " - ); - $item.css("opacity", 0.6); + helper: function(event) { + console.log("ev", event); + var $panel = $item.find("> .treeview-item__panel"); + var $helper = $("
                                                      "); + $helper + .append($item.clone(false)) + .find(".treeview-item__children") + .remove(); + $helper.css({ + width: $panel.outerWidth(true), + marginTop: -40, + marginLeft: -4 + }); + $panel.css("opacity", 0.6); return $helper; }, + start: function() { $(".ui-droppable").each(function() { var $drop = $(this); @@ -509,71 +527,69 @@ }); }, stop: function(event, ui) { - $(".ui-droppable").hide(); + $(".ui-droppable").css("display", ""); // Show the previous elements. If the user made an invalid movement then // we want this to show anyways. If he did something valid the treeview will // refresh so we don't care if it's visible behind the loading icon. - $(".treeview-item").css("opacity", ""); + $(".treeview-item__panel").css("opacity", ""); } }); // Dropping targets - $item - .find("> .treeview-item-flow .treeview-item-reorder div") - .each(function() { - $(this).droppable({ - hoverClass: "state-active", - tolerance: "pointer", - drop: function(event, ui) { - $drop = $(this); - $dropItem = $drop.closest(".treeview-item"); - - $oldItem = ui.draggable.closest(".treeview-item"); - var oldId = $oldItem.data("id"); - var oldParents = $oldItem.data("tree"); - - var type = "child"; - var sort = 100000; - - if ($drop.hasClass("before")) { - type = "before"; - sort = $dropItem.data("sort") - 1; - } else if ($drop.hasClass("after")) { - type = "after"; - sort = $dropItem.data("sort") + 1; - } - - var newParent = - type === "child" - ? itemId - : parents.length > 0 - ? parents[parents.length - 1] - : ""; - - $treeView.reload({ - url: url + "/move", - data: [ - { - name: "parents", - value: oldParents - }, - { - name: "itemId", - value: oldId - }, - { - name: "newParent", - value: newParent - }, - { - name: "sort", - value: sort - } - ] - }); + $item.find(".treeview-item-reorder .droppable").each(function() { + $(this).droppable({ + hoverClass: "state-active", + tolerance: "pointer", + drop: function(event, ui) { + $drop = $(this); + $dropItem = $drop.closest(".treeview-item"); + + $oldItem = ui.draggable.closest(".treeview-item"); + var oldId = $oldItem.data("id"); + var oldParents = $oldItem.data("tree"); + + var type = "child"; + var sort = 100000; + + if ($drop.hasClass("before")) { + type = "before"; + sort = $dropItem.data("sort") - 1; + } else if ($drop.hasClass("after")) { + type = "after"; + sort = $dropItem.data("sort") + 1; } - }); + + var newParent = + type === "child" + ? itemId + : parents.length > 0 + ? parents[parents.length - 1] + : ""; + + $treeView.reload({ + url: url + "/move", + data: [ + { + name: "parents", + value: oldParents + }, + { + name: "itemId", + value: oldId + }, + { + name: "newParent", + value: newParent + }, + { + name: "sort", + value: sort + } + ] + }); + } }); + }); }); }, // This is copy paste from SilverStripe GridField.js, modified to work for the TreeView diff --git a/src/TreeView.php b/src/TreeView.php index e7f028d..afd27fe 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -265,7 +265,7 @@ public function add($request) $type = $data["type"]; $child = $type::create(); - $child->Name = "New " . $type; + $child->Name = "New " . $child->singular_name(); $sort = intval($data["sort"]); $sortBy = $this->getSortField(); @@ -675,25 +675,25 @@ private function renderTree($item, $parents, $opens, $isFirst) // Create a button to add a new child element // and save the allowed child classes on the button - $classes = $item->getAllowedPageElements(); - $elems = []; - foreach ($classes as $class) { - $elems[$class] = singleton($class)->singular_name(); - } - $addButton = TreeViewFormAction::create( - $this, - "AddAction".$item->ID, - null, - null, - null - ); - $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); - $addButton->addExtraClass("add-button font-icon-plus"); - if (!count($elems)) { - $addButton->setDisabled(true); + if (count($classes)) { + $elems = []; + foreach ($classes as $class) { + $elems[$class] = singleton($class)->singular_name(); + } + $addButton = TreeViewFormAction::create( + $this, + "AddAction".$item->ID, + null, + null, + null + ); + $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); + $addButton->addExtraClass("btn add-button font-icon-plus"); + if (!count($elems)) { + $addButton->setDisabled(true); + } + $addButton->setButtonContent(' '); } - $addButton->setButtonContent('Add'); - // Create a button to add an element after // and save the allowed child classes on the button $classes = count($parents) > 0 @@ -713,11 +713,11 @@ private function renderTree($item, $parents, $opens, $isFirst) $addAfterButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE) ); - $addAfterButton->addExtraClass("add-after-button font-icon-plus"); + $addAfterButton->addExtraClass("btn add-after-button font-icon-plus"); if (!count($elems)) { $addAfterButton->setDisabled(true); } - $addAfterButton->setButtonContent('Add after'); + $addAfterButton->setButtonContent(' '); // Create a button to delete and/or remove the element from the parent $deleteButton = TreeViewFormAction::create( @@ -731,7 +731,7 @@ private function renderTree($item, $parents, $opens, $isFirst) "data-used-count", $item->Parents()->Count() + $item->getAllSectionParents()->Count() ); - $deleteButton->addExtraClass("delete-button font-icon-trash-bin"); + $deleteButton->addExtraClass("btn delete-button font-icon-trash-bin"); $deleteButton->setButtonContent('Delete'); // Create a button to edit the record @@ -742,7 +742,7 @@ private function renderTree($item, $parents, $opens, $isFirst) null, null ); - $editButton->addExtraClass("edit-button font-icon-edit"); + $editButton->addExtraClass("btn edit-button font-icon-edit"); $editButton->setButtonContent('Edit'); // Create the tree icon @@ -759,7 +759,7 @@ private function renderTree($item, $parents, $opens, $isFirst) "dotreenav", ["element" => $item] ); - $treeButton->addExtraClass("tree-button " . ($isOpen ? "is-open" : "is-closed")); + $treeButton->addExtraClass("tree-button treeview-item__treeswitch__button btn " . ($isOpen ? "is-open" : "is-closed")); if (!$item->Children()->Count()) { $treeButton->addExtraClass(" is-end"); $treeButton->setDisabled(true); @@ -776,7 +776,7 @@ private function renderTree($item, $parents, $opens, $isFirst) "AllowedRoot" => $isAllowedRoot, "AllowedElements" => json_encode($elems, JSON_UNESCAPED_UNICODE), "TreeButton" => $treeButton, - "AddButton" => $addButton, + "AddButton" => isset($addButton) ? $addButton : null, "AddAfterButton" => $addAfterButton, "EditButton" => $editButton, "DeleteButton" => $deleteButton, diff --git a/templates/FLXLabs/PageSections/TreeViewPageElement.ss b/templates/FLXLabs/PageSections/TreeViewPageElement.ss index 0d2880f..1756361 100644 --- a/templates/FLXLabs/PageSections/TreeViewPageElement.ss +++ b/templates/FLXLabs/PageSections/TreeViewPageElement.ss @@ -1,6 +1,6 @@
                                                      -
                                                      +
                                                      +
                                                      <% if IsFirst %> -
                                                      +
                                                      <% end_if %> -
                                                      -
                                                      +
                                                      +
                                                      -
                                                      - $TreeButton -
                                                      -
                                                      - $ButtonField -
                                                      +
                                                      +
                                                      + $TreeButton +
                                                      +
                                                      + $ButtonField
                                                      - $Item.ClassName (ID: $Item.ID, {$UsedCount}x) + $Item.singular_name() ID: $Item.ID, Used: {$UsedCount}
                                                      $Item.Name
                                                      +
                                                      + $Item.TreeViewPreview +
                                                      +
                                                      + $EditButton + $DeleteButton +
                                                      -
                                                      - $Item.TreeViewPreview +
                                                      + $AddButton +
                                                      +
                                                      + $Children.RAW
                                                      -
                                                      -
                                                      -
                                                      - $Children.RAW
                                                      -
                                                      - $AddButton +
                                                      $AddAfterButton - $EditButton - $DeleteButton
                                                      From 9b5f9cbb39f1a34599008afb00a8b9aebe72555e Mon Sep 17 00:00:00 2001 From: Felix Eggmann Date: Sat, 11 Aug 2018 23:46:22 +0200 Subject: [PATCH 31/82] fix(treeview): fix sorting issues, add canBeRoot to PageElement --- css/TreeView.css | 8 +++-- javascript/TreeView.js | 62 +++++++++++++++++++++-------------- src/PageElement.php | 9 +++-- src/PageSectionsExtension.php | 6 +++- src/TreeView.php | 50 ++++++++++++++-------------- 5 files changed, 76 insertions(+), 59 deletions(-) diff --git a/css/TreeView.css b/css/TreeView.css index ddd9045..bcb8761 100644 --- a/css/TreeView.css +++ b/css/TreeView.css @@ -258,12 +258,14 @@ .treeview-item-reorder__handle { pointer-events: all; width: 30px; - height: 38px; position: absolute; - top: 50%; - margin-top: -19px; font-size: 30px; cursor: pointer; + bottom: 35px; + top: 35px; + display: flex; + align-items: center; + cursor: grab } .treeview-item-reorder .ui-droppable { diff --git a/javascript/TreeView.js b/javascript/TreeView.js index e8caf95..9beb5cd 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -32,8 +32,8 @@ var wH = $(window).height(); var eW = that.$menu.outerWidth(true); var eH = that.$menu.outerHeight(true); - pos.left = pos.left - eW * 0.5; - pos.top = pos.top - eH * 0.5; + pos.left = Math.max(2, pos.left - eW * 0.5); + pos.top = Math.max(2, pos.top - eH * 0.5); if (pos.left + eW > wW) { pos.left = wW - eW - 2; } @@ -443,7 +443,6 @@ greedy: true, helper: function(event) { - console.log("ev", event); var $panel = $item.find("> .treeview-item__panel"); var $helper = $("
                                                      "); $helper @@ -463,23 +462,42 @@ $(".ui-droppable").each(function() { var $drop = $(this); var $dropItem = $drop.closest(".treeview-item"); + var $parentDropItem = $dropItem + .parent() + .closest(".treeview-item"); + var clazz = $item.data("class"); + var isOpen = $dropItem.data("is-open"); // Dont enable dropping on itself + // or a same id + if ($dropItem.data("id") == itemId) { + return; + } + + // Dont enable dropping on a child of itself + // or a same id if ( - $dropItem.data("id") == itemId && - $dropItem.data("tree") == parents + $dropItem.parents(".treeview-item[data-id='" + itemId + "']") + .length ) { return; } - // Don't enable dropping on the middle arrow for open items - // (they will have child elements where we can drop before or after) - if ($drop.hasClass("middle") && isOpen) { + // Dont enable dropping on .before/.after + // from neighbours with same id + // (no same ids on same branch) + if ( + ($drop.hasClass("after") || $drop.hasClass("before")) && + $dropItem + .siblings(".treeview-item[data-id='" + itemId + "']") + .not($item).length + ) { return; } - // Dont enable dropping on .after of itself + // Dont enable dropping on next bottom + // of the same id if ( $drop.hasClass("after") && $dropItem.next().data("id") == itemId @@ -487,21 +505,18 @@ return; } - // Dont enable dropping on .middle of other same id elements - // (no recursive structures) - if ( - $drop.hasClass("middle") && - $dropItem.data("id") == itemId - ) { + // Don't enable dropping on the middle arrow for open items + // (they will have child elements where we can drop before or after) + if ($drop.hasClass("middle") && isOpen) { return; } - // Don't allow dropping elements on the root level that aren't allowed there - if ( - $dropItem.data("tree").length == 0 && - ($drop.hasClass("before") || $drop.hasClass("after")) - ) { - if (!$item.data("allowed-root")) { + // avoid adding unallowed elements on root section + if (!$parentDropItem.length) { + var allowed = $drop + .closest(".treeview-pagesections") + .data("allowed-elements"); + if (allowed && !allowed[clazz]) { return; } } @@ -509,14 +524,13 @@ // Don't allow dropping elements on this level if they're not an allowed child // Depending on the arrow we either have to check this element or the parent // of this element to see which children are allowed - var clazz = $item.data("class"); if ($drop.hasClass("before") || $drop.hasClass("after")) { - var $parent = $dropItem.parent().closest(".treeview-item"); - var allowed = $parent.data("allowed-elements"); + var allowed = $parentDropItem.data("allowed-elements"); if (allowed && !allowed[clazz]) { return; } } else { + // is middle var allowed = $dropItem.data("allowed-elements"); if (allowed && !allowed[clazz]) { return; diff --git a/src/PageElement.php b/src/PageElement.php index df5df87..83bd84a 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -33,17 +33,16 @@ class PageElement extends DataObject private static $table_name = "FLXLabs_PageSections_PageElement"; - protected static $singularName = "Element"; - protected static $pluralName = "Elements"; protected static $defaultIsOpen = true; + public static $canBeRoot = true; public static function getSingularName() { - return static::$singularName; + return static::$singular_name; } public static function getPluralName() { - return static::$pluralName; + return static::$plural_name; } public static function isOpenByDefault() { @@ -175,7 +174,7 @@ public function onAfterDelete() */ public function getTreeViewPreview() { - return $this->Name; + return $this->GridFieldPreview; } /** diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 5db9203..aaecd6d 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -67,7 +67,11 @@ public function getAllowedPageElements($section = "Main") { $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); $classes = array_diff($classes, [PageElement::class]); - return $classes; + $ret = []; + foreach($classes as $class) { + if ($class::$canBeRoot) $ret[] = $class; + } + return $ret; } /** diff --git a/src/TreeView.php b/src/TreeView.php index afd27fe..80fbe1e 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -602,6 +602,7 @@ public function FieldHolder($properties = array()) 'data-name' => $this->getName(), 'data-url' => $this->Link(), 'data-state-id' => $sessionId, + 'data-allowed-elements' => json_encode($elems), ], $content ); @@ -642,19 +643,6 @@ private function renderTree($item, $parents, $opens, $isFirst) } } - // Construct the array of all allowed child elements - $classes = $item->getAllowedPageElements(); - $elems = []; - foreach ($classes as $class) { - $elems[$class] = $class::getSingularName(); - } - - // Find out if this item is allowed as a root item - // There are two cases, either this GridField is on a page, - // or it is on a PageElement and we're looking that the children - $parentClasses = $this->section->getAllowedPageElements(); - $isAllowedRoot = in_array($item->ClassName, $parentClasses); - // Get the list of parents of this element as an array of ids // (already converted to json/a string) $tree = "[" . @@ -667,6 +655,27 @@ private function renderTree($item, $parents, $opens, $isFirst) ) . "]"; + // Construct the array of all allowed child elements + $classes = $item->getAllowedPageElements(); + $elems = []; + foreach ($classes as $class) { + $elems[$class] = singleton($class)->singular_name(); + } + + // Construct the array of all allowed child elements in parent slot + $parentClasses = count($parents) > 0 + ? $parents[count($parents) - 1]->getAllowedPageElements() + : $this->section->getAllowedPageElements(); + $parentElems = []; + foreach ($parentClasses as $class) { + $parentElems[$class] = singleton($class)->singular_name(); + } + + // Find out if this item is allowed as a root item + // There are two cases, either this GridField is on a page, + // or it is on a PageElement and we're looking at the children + $isAllowedRoot = in_array($item->ClassName, $parentClasses); + // Create the tree icon $icon = ''; if ($item->Children() && $item->Children()->Count() > 0) { @@ -676,10 +685,6 @@ private function renderTree($item, $parents, $opens, $isFirst) // Create a button to add a new child element // and save the allowed child classes on the button if (count($classes)) { - $elems = []; - foreach ($classes as $class) { - $elems[$class] = singleton($class)->singular_name(); - } $addButton = TreeViewFormAction::create( $this, "AddAction".$item->ID, @@ -696,13 +701,6 @@ private function renderTree($item, $parents, $opens, $isFirst) } // Create a button to add an element after // and save the allowed child classes on the button - $classes = count($parents) > 0 - ? $parents[count($parents) - 1]->getAllowedPageElements() - : $this->section->getAllowedPageElements(); - $elems = []; - foreach ($classes as $class) { - $elems[$class] = singleton($class)->singular_name(); - } $addAfterButton = TreeViewFormAction::create( $this, "AddAfterAction".$item->ID, @@ -711,10 +709,10 @@ private function renderTree($item, $parents, $opens, $isFirst) null ); $addAfterButton->setAttribute("data-allowed-elements", - json_encode($elems, JSON_UNESCAPED_UNICODE) + json_encode($parentElems, JSON_UNESCAPED_UNICODE) ); $addAfterButton->addExtraClass("btn add-after-button font-icon-plus"); - if (!count($elems)) { + if (!count($parentElems)) { $addAfterButton->setDisabled(true); } $addAfterButton->setButtonContent(' '); From 9d68331b73df1cb6b5f22e64fa66d1ce5fe7effc Mon Sep 17 00:00:00 2001 From: Felix Eggmann Date: Sun, 12 Aug 2018 00:01:44 +0200 Subject: [PATCH 32/82] fix(treeview): fix html output for tree item preview --- templates/FLXLabs/PageSections/TreeViewPageElement.ss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/FLXLabs/PageSections/TreeViewPageElement.ss b/templates/FLXLabs/PageSections/TreeViewPageElement.ss index 1756361..b4fa82f 100644 --- a/templates/FLXLabs/PageSections/TreeViewPageElement.ss +++ b/templates/FLXLabs/PageSections/TreeViewPageElement.ss @@ -34,7 +34,7 @@
                                                      - $Item.TreeViewPreview + $Item.TreeViewPreview.RAW
                                                      $EditButton From cf054e3df8fc2e224403f06bc995fc68e5af0973 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Mon, 13 Aug 2018 13:19:58 +0200 Subject: [PATCH 33/82] fix(adding): Fix adding element not working with built-in button --- src/TreeView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TreeView.php b/src/TreeView.php index 80fbe1e..35f38dc 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -267,7 +267,7 @@ public function add($request) $child = $type::create(); $child->Name = "New " . $child->singular_name(); - $sort = intval($data["sort"]); + $sort = isset($data["sort"]) ? intval($data["sort"]) : 0; $sortBy = $this->getSortField(); $sortArr = [$sortBy => $sort]; From 618023d9770fd4a23388deb17174b27a080f34c3 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Fri, 31 Aug 2018 13:08:38 +0200 Subject: [PATCH 34/82] feat(treeview): Support inline edit fields [WIP] --- javascript/TreeView.js | 254 ++++++++++-------- src/GridFieldPopupEditButton.php | 28 ++ src/TreeView.php | 31 ++- .../PageSections/GridFieldPopupEditButton.ss | 6 + 4 files changed, 206 insertions(+), 113 deletions(-) create mode 100644 src/GridFieldPopupEditButton.php create mode 100644 templates/FLXLabs/PageSections/GridFieldPopupEditButton.ss diff --git a/javascript/TreeView.js b/javascript/TreeView.js index 9beb5cd..222aee4 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -1,23 +1,23 @@ -(function($) { +(function ($) { function TreeViewContextMenu() { - this.createDom = function(id, name) { + this.createDom = function (id, name) { this.$menu = $( "
                                                        " + name + + "' class='treeview-menu' data-id='" + + id + + "'>" ); }; - this.addLabel = function(label) { + this.addLabel = function (label) { this.$menu.append("
                                                      • " + label + "
                                                      • "); }; - this.addItem = function(type, label, onClick = function() {}) { + this.addItem = function (type, label, onClick = function () {}) { var $li = $("
                                                      • " + label + "
                                                      • "); $li.click(onClick); this.$menu.append($li); }; - this.show = function(x, y) { + this.show = function (x, y) { var pos = { top: y, left: x @@ -27,7 +27,7 @@ this.$menu.css(pos); this.$menu.show(); var that = this; - window.requestAnimationFrame(function() { + window.requestAnimationFrame(function () { var wW = $(window).width(); var wH = $(window).height(); var eW = that.$menu.outerWidth(true); @@ -43,41 +43,91 @@ that.$menu.css(pos); }); }; - this.remove = function() { + this.remove = function () { this.$menu.remove(); }; } - $.entwine("ss", function($) { + $.entwine("ss", function ($) { // Hide our custom context menu when not needed - $(document).on("mousedown", function(event) { + $(document).on("mousedown", function (event) { $parents = $(event.target).parents(".treeview-menu"); if ($parents.length == 0) { $(".treeview-menu").remove(); } }); + $(".view-detail-dialog .gridfield-better-buttons-prevnext").entwine({ + onclick: function () { + this.closest(".view-detail-dialog").loadDialog( + $.get(this.prop("href")) + ); + return false; + } + }); + + $(".view-detail-dialog .action-detail").entwine({ + onclick: function () { + const dialog = this.closest(".view-detail-dialog").loadDialog( + $.get(this.prop("href")) + ); + dialog.data("href", this.prop("href")); + return false; + } + }); + + $(".view-detail-dialog form").entwine({ + onsubmit: function () { + var dialog = this.closest(".view-detail-dialog"); + + const self = this; + + $.ajax( + $.extend({}, { + headers: { + "X-Pjax": "CurrentField" + }, + type: "POST", + url: this.prop("action"), + dataType: "html", + data: this.serialize(), + success: function () { + if (self.hasClass('view-detail-form')) { + dialog.data("treeview").reload(); + dialog.dialog("close"); + } else { + dialog.loadDialog($.get(dialog.data("href"))); + } + } + }) + ); + + return false; + } + }); + // Show our search form after opening the search dialog // Show our detail form after opening the detail dialog - $(".add-existing-search-dialog, .view-detail-dialog").entwine({ - loadDialog: function(deferred) { + $(".add-existing-search-dialog-treeview, .view-detail-dialog").entwine({ + loadDialog: function (deferred) { var dialog = this.addClass("loading") .children(".ui-dialog-content") .empty(); - deferred.done(function(data) { + deferred.done(function (data) { dialog .html(data) .parent() .removeClass("loading"); }); + return this; } }); // Submit our search form to our own endpoint and show the results - $(".add-existing-search-dialog .add-existing-search-form").entwine({ - onsubmit: function() { - this.closest(".add-existing-search-dialog").loadDialog( + $(".add-existing-search-dialog-treeview .add-existing-search-form").entwine({ + onsubmit: function () { + this.closest(".add-existing-search-dialog-treeview").loadDialog( $.get(this.prop("action"), this.serialize()) ); return false; @@ -86,9 +136,9 @@ // Allow clicking the elements in the search form $( - ".add-existing-search-dialog .add-existing-search-items .list-group-item-action" + ".add-existing-search-dialog-treeview .add-existing-search-items .list-group-item-action" ).entwine({ - onclick: function() { + onclick: function () { if (this.children("a").length > 0) { this.children("a") .first() @@ -98,27 +148,24 @@ }); // Trigger the "add existing" action on the selected element - $(".add-existing-search-dialog .add-existing-search-items a").entwine({ - onclick: function() { + $(".add-existing-search-dialog-treeview .add-existing-search-items a").entwine({ + onclick: function () { var link = this.closest(".add-existing-search-items").data("add-link"); var id = this.data("id"); - var dialog = this.closest(".add-existing-search-dialog") + var dialog = this.closest(".add-existing-search-dialog-treeview") .addClass("loading") .children(".ui-dialog-content") .empty(); - dialog.data("treeview").reload( - { + dialog.data("treeview").reload({ url: link, - data: [ - { - name: "id", - value: id - } - ] + data: [{ + name: "id", + value: id + }] }, - function() { + function () { dialog.dialog("close"); } ); @@ -128,9 +175,9 @@ }); // Browse search result pages - $(".add-existing-search-dialog .add-existing-search-pagination a").entwine({ - onclick: function() { - this.closest(".add-existing-search-dialog").loadDialog( + $(".add-existing-search-dialog-treeview .add-existing-search-pagination a").entwine({ + onclick: function () { + this.closest(".add-existing-search-dialog-treeview").loadDialog( $.get(this.prop("href")) ); return false; @@ -138,30 +185,29 @@ }); // Save the item in the detail view - $(".view-detail-dialog .view-detail-form").entwine({ - onsubmit: function() { + /*$(".view-detail-dialog .view-detail-form").entwine({ + onsubmit: function () { var dialog = this.closest(".view-detail-dialog").children( ".ui-dialog-content" ); - $.post(this.prop("action"), this.serialize(), function() { + $.post(this.prop("action"), this.serialize(), function () { dialog.data("treeview").reload(); dialog.dialog("close"); }); return false; } - }); + });*/ // Attach data to our tree view $(".treeview-pagesections").entwine({ - addItem: function(parents, itemId, elemType, sort = 99999) { + addItem: function (parents, itemId, elemType, sort = 99999) { var $treeView = $(this); $treeView.reload({ url: $treeView.data("url") + "/add", - data: [ - { + data: [{ name: "parents", value: parents }, @@ -180,13 +226,12 @@ ] }); }, - removeItem: function(parents, itemId) { + removeItem: function (parents, itemId) { var $treeView = $(this); $treeView.reload({ url: $treeView.data("url") + "/remove", - data: [ - { + data: [{ name: "parents", value: parents }, @@ -197,13 +242,12 @@ ] }); }, - deleteItem: function(parents, itemId) { + deleteItem: function (parents, itemId) { var $treeView = $(this); $treeView.reload({ url: $treeView.data("url") + "/delete", - data: [ - { + data: [{ name: "parents", value: parents }, @@ -214,7 +258,7 @@ ] }); }, - onadd: function() { + onadd: function () { var $treeView = $(this); var name = $treeView.data("name"); var url = $treeView.data("url"); @@ -222,20 +266,18 @@ // Setup add new button $treeView .find(".treeview-actions-addnew .tree-button") - .click(function() { + .click(function () { $treeView.reload({ url: url + "/add", - data: [ - { - name: "type", - value: $("#AddNewClass").val() - } - ] + data: [{ + name: "type", + value: $("#AddNewClass").val() + }] }); }); // Setup find existing button - $treeView.find("button[name=action_FindExisting]").click(function() { + $treeView.find("button[name=action_FindExisting]").click(function () { var dialog = $("
                                                        ") .appendTo("body") .dialog({ @@ -243,7 +285,7 @@ resizable: false, width: 500, height: 600, - close: function() { + close: function () { $(this) .dialog("destroy") .remove(); @@ -252,13 +294,13 @@ dialog .parent() - .addClass("add-existing-search-dialog") + .addClass("add-existing-search-dialog-treeview") .loadDialog($.get($treeView.data("url") + "/search")); dialog.data("treeview", $treeView); }); // Process items - $treeView.find(".treeview-item").each(function() { + $treeView.find(".treeview-item").each(function () { var $item = $(this); var itemId = $item.data("id"); var parents = $item.data("tree"); @@ -266,14 +308,13 @@ // Open an item button $item .find("> .treeview-item__panel > .treeview-item-flow .tree-button") - .click(function(event) { + .click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); $treeView.reload({ url: url + "/tree", - data: [ - { + data: [{ name: "parents", value: parents }, @@ -288,7 +329,7 @@ // Edit button $item .find("> .treeview-item__panel > .treeview-item-flow .edit-button") - .click(function() { + .click(function () { var dialog = $("
                                                        ") .appendTo("body") .dialog({ @@ -296,7 +337,7 @@ resizable: false, width: $(window).width() * 0.9, height: $(window).height() * 0.9, - close: function() { + close: function () { $(this) .dialog("destroy") .remove(); @@ -317,7 +358,7 @@ .find( "> .treeview-item__panel > .treeview-item-actions .add-button" ) - .click(function(event) { + .click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -329,8 +370,8 @@ menu.addLabel( ss.i18n._t("PageSections.TreeView.AddAChild", "Add a child") ); - $.each(elems, function(key, value) { - menu.addItem(key, value, function() { + $.each(elems, function (key, value) { + menu.addItem(key, value, function () { $treeView.addItem(parents, itemId, key); menu.remove(); }); @@ -341,7 +382,7 @@ // Add new after item button $item .find("> .treeview-item__post-actions .add-after-button") - .click(function(event) { + .click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -357,8 +398,8 @@ ) ); - $.each(elems, function(key, value) { - menu.addItem(key, value, function() { + $.each(elems, function (key, value) { + menu.addItem(key, value, function () { $treeView.addItem( parents.slice(0, parents.length - 1), parents[parents.length - 1], @@ -376,7 +417,7 @@ .find( "> .treeview-item__panel > .treeview-item-flow .delete-button" ) - .click(function(event) { + .click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -391,7 +432,7 @@ menu.addItem( "__REMOVE__", ss.i18n._t("PageSections.TreeView.RemoveAChild", "Remove"), - function() { + function () { $treeView.removeItem(parents, itemId); menu.remove(); } @@ -404,7 +445,7 @@ "PageSections.TreeView.DeleteAChild", "Finally delete" ), - function() { + function () { $treeView.deleteItem(parents, itemId); menu.remove(); } @@ -412,12 +453,12 @@ var $li = $( "
                                                      • " + - ss.i18n._t( - "PageSections.TreeView.DeleteAChild", - "Finally delete" - ) + - "
                                                      • ", - function() { + ss.i18n._t( + "PageSections.TreeView.DeleteAChild", + "Finally delete" + ) + + "", + function () { $treeView.deleteItem(parents, itemId); menu.remove(); } @@ -442,7 +483,7 @@ tolerance: "pointer", greedy: true, - helper: function(event) { + helper: function (event) { var $panel = $item.find("> .treeview-item__panel"); var $helper = $("
                                                        "); $helper @@ -458,8 +499,8 @@ return $helper; }, - start: function() { - $(".ui-droppable").each(function() { + start: function () { + $(".ui-droppable").each(function () { var $drop = $(this); var $dropItem = $drop.closest(".treeview-item"); var $parentDropItem = $dropItem @@ -479,7 +520,7 @@ // or a same id if ( $dropItem.parents(".treeview-item[data-id='" + itemId + "']") - .length + .length ) { return; } @@ -490,8 +531,8 @@ if ( ($drop.hasClass("after") || $drop.hasClass("before")) && $dropItem - .siblings(".treeview-item[data-id='" + itemId + "']") - .not($item).length + .siblings(".treeview-item[data-id='" + itemId + "']") + .not($item).length ) { return; } @@ -540,7 +581,7 @@ $drop.show(); }); }, - stop: function(event, ui) { + stop: function (event, ui) { $(".ui-droppable").css("display", ""); // Show the previous elements. If the user made an invalid movement then // we want this to show anyways. If he did something valid the treeview will @@ -550,11 +591,11 @@ }); // Dropping targets - $item.find(".treeview-item-reorder .droppable").each(function() { + $item.find(".treeview-item-reorder .droppable").each(function () { $(this).droppable({ hoverClass: "state-active", tolerance: "pointer", - drop: function(event, ui) { + drop: function (event, ui) { $drop = $(this); $dropItem = $drop.closest(".treeview-item"); @@ -574,16 +615,15 @@ } var newParent = - type === "child" - ? itemId - : parents.length > 0 - ? parents[parents.length - 1] - : ""; + type === "child" ? + itemId : + parents.length > 0 ? + parents[parents.length - 1] : + ""; $treeView.reload({ url: url + "/move", - data: [ - { + data: [{ name: "parents", value: oldParents }, @@ -609,7 +649,7 @@ // This is copy paste from SilverStripe GridField.js, modified to work for the TreeView // It updates the gridfield by sending the specified request // and using the response as the new content for the gridfield - reload: function(ajaxOpts, successCallback) { + reload: function (ajaxOpts, successCallback) { var self = this, form = this.closest("form"), focusedElName = this.find(":input:focus").attr("name"), // Save focused element for restoring after refresh @@ -617,12 +657,10 @@ if (!ajaxOpts) ajaxOpts = {}; if (!ajaxOpts.data) ajaxOpts.data = []; - ajaxOpts.data = ajaxOpts.data.concat(data).concat([ - { - name: "state", - value: self.data("state-id") - } - ]); + ajaxOpts.data = ajaxOpts.data.concat(data).concat([{ + name: "state", + value: self.data("state-id") + }]); // Include any GET parameters from the current URL, as the view state might depend on it. // For example, a list prefiltered through external search criteria might be passed to GridField. @@ -636,16 +674,14 @@ form.addClass("loading"); $.ajax( - $.extend( - {}, - { + $.extend({}, { headers: { "X-Pjax": "CurrentField" }, type: "POST", url: this.data("url"), dataType: "html", - success: function(data) { + success: function (data) { // Replace the grid field with response, not the form. // TODO Only replaces all its children, to avoid replacing the current scope // of the executing method. Means that it doesn't retrigger the onmatch() on the main container. @@ -661,7 +697,7 @@ // TODO: Don't know how original SilverStripe GridField magically calls self.onadd(); }, - error: function(e) { + error: function (e) { alert(i18n._t("Admin.ERRORINTRANSACTION")); form.removeClass("loading"); } diff --git a/src/GridFieldPopupEditButton.php b/src/GridFieldPopupEditButton.php new file mode 100644 index 0000000..d98d8f6 --- /dev/null +++ b/src/GridFieldPopupEditButton.php @@ -0,0 +1,28 @@ + "font-icon-edit asdf" + ]; + } + + public function getColumnContent($gridField, $record, $columnName) + { + // No permission checks, handled through GridFieldDetailForm, + // which can make the form readonly if no edit permissions are available. + $data = new ArrayData([ + 'Link' => Controller::join_links($gridField->Link('item'), $record->ID, 'edit'), + 'ExtraClass' => $this->getExtraClass() + ]); + return $data->renderWith(['FLXLabs\\PageSections\\GridFieldPopupEditButton']); + } +} diff --git a/src/TreeView.php b/src/TreeView.php index 35f38dc..bfe2b76 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -19,6 +19,7 @@ use SilverStripe\View\HTML; use SilverStripe\View\Requirements; use SilverStripe\View\ViewableData; +use SilverStripe\View\SSViewer; /** * Class TreeView @@ -463,7 +464,9 @@ public function DetailForm(PageElement $item, bool $loadData = true) 'type' => 'Includes', 'SilverStripe\\Admin\\LeftAndMain_EditForm', ]); - $form->addExtraClass('view-detail-form cms-content cms-edit-form center fill-height flexbox-area-grow'); + $form->addExtraClass( + 'view-detail-form cms-content cms-edit-form center fill-height flexbox-area-grow' + ); if ($form->Fields()->hasTabSet()) { $form->Fields()->findOrMakeTab('Root')->setTemplate('SilverStripe\\Forms\\CMSTabSet'); $form->addExtraClass('cms-tabset'); @@ -479,6 +482,11 @@ public function DetailForm(PageElement $item, bool $loadData = true) */ public function detail($request) { $id = intval($request->requestVar("ID")); + if ($id) { + $request->getSession()->set("ElementID", $id); + } else { + $id = $request->getSession()->get("ElementID"); + } // This is a request to show the form so we return it as a template so // that SilverStripe doesn't think this is already a submission @@ -490,17 +498,32 @@ public function detail($request) { } $form = $this->DetailForm($item); // Save the id of the page element on this form's security token - $request->getSession()->set("_tv_df_" . $form->getSecurityToken()->getValue(), $id); - return $form->forAjaxTemplate(); + //$request->getSession()->set("_tv_df_" . $form->getSecurityToken()->getValue(), $id); + return $form->forTemplate(); } // If it's a POST request then it's a submission and we have to get the ID // from the session using the form's security token. - $id = $request->getSession()->get("_tv_df_" . $request->requestVar("SecurityID")); + //$id = $request->getSession()->get("_tv_df_" . $request->requestVar("SecurityID")); $item = PageElement::get()->byID($id); return $this->DetailForm($item, false); } + public function handleRequest(HTTPRequest $request) { + $this->setRequest($request); + + // Forward requests to the elements in the detail form to their respective controller + if ($request->match('detail/$ID!')) { + $id = $request->getSession()->get("ElementID"); + $item = PageElement::get()->byID($id); + $form = $this->DetailForm($item); + $request->shift(1); + return $form->getRequestHandler()->handleRequest($request); + } + + return parent::handleRequest($request); + } + /** * This action is called when the detail form is submitted (saved/deleted) * @param \SilverStripe\Control\HTTPRequest $request diff --git a/templates/FLXLabs/PageSections/GridFieldPopupEditButton.ss b/templates/FLXLabs/PageSections/GridFieldPopupEditButton.ss new file mode 100644 index 0000000..0e9c4e9 --- /dev/null +++ b/templates/FLXLabs/PageSections/GridFieldPopupEditButton.ss @@ -0,0 +1,6 @@ + + <%t SilverStripe\\Forms\\GridField\\GridFieldEditButton.EDIT 'Edit' %> + From 889720f9c8619db71a08f17cdf45e11e2450ad16 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Fri, 31 Aug 2018 15:01:44 +0200 Subject: [PATCH 35/82] feat(treeview): Change "add new" button at the top. Fix sort order for inserting at the start --- javascript/TreeView.js | 40 ++++++++++++------- src/PageElement.php | 6 --- src/PageElementSelfRel.php | 2 +- src/PageSectionPageElementRel.php | 2 +- src/TreeView.php | 21 +++++++--- .../PageSections/TreeViewAddNewButton.ss | 5 +-- 6 files changed, 46 insertions(+), 30 deletions(-) diff --git a/javascript/TreeView.js b/javascript/TreeView.js index 222aee4..1ed4172 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -263,19 +263,6 @@ var name = $treeView.data("name"); var url = $treeView.data("url"); - // Setup add new button - $treeView - .find(".treeview-actions-addnew .tree-button") - .click(function () { - $treeView.reload({ - url: url + "/add", - data: [{ - name: "type", - value: $("#AddNewClass").val() - }] - }); - }); - // Setup find existing button $treeView.find("button[name=action_FindExisting]").click(function () { var dialog = $("
                                                        ") @@ -299,6 +286,31 @@ dialog.data("treeview", $treeView); }); + // Add new button at the very top + $treeView + .find("> .treeview-pagesections__header > .treeview-item-actions .add-button") + .click(function (event) { + console.log("click"); + event.preventDefault(); + event.stopImmediatePropagation(); + + $target = $(event.target); + var elems = $target.data("allowed-elements"); + + var menu = new TreeViewContextMenu(); + menu.createDom(null, name); + menu.addLabel( + ss.i18n._t("PageSections.TreeView.AddAChild", "Add a child") + ); + $.each(elems, function (key, value) { + menu.addItem(key, value, function () { + $treeView.addItem([], null, key, 1); + menu.remove(); + }); + }); + menu.show(event.pageX, event.pageY); + }); + // Process items $treeView.find(".treeview-item").each(function () { var $item = $(this); @@ -372,7 +384,7 @@ ); $.each(elems, function (key, value) { menu.addItem(key, value, function () { - $treeView.addItem(parents, itemId, key); + $treeView.addItem(parents, itemId, key, 1); menu.remove(); }); }); diff --git a/src/PageElement.php b/src/PageElement.php index 83bd84a..4c636e5 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -86,12 +86,6 @@ public function canCreate($member = null, $context = []) "PageSections" => PageSection::class . ".Elements", ]; - private static $many_many_extraFields = [ - "Children" => [ - "SortOrder" => 'Int', - ], - ]; - private static $owns = [ "Children", ]; diff --git a/src/PageElementSelfRel.php b/src/PageElementSelfRel.php index f8c5e99..0006c3c 100644 --- a/src/PageElementSelfRel.php +++ b/src/PageElementSelfRel.php @@ -23,7 +23,7 @@ public function onBeforeWrite() parent::onBeforeWrite(); if (!$this->ID) { - if (!$this->SortOrder) { + if (!$this->SortOrder && $this->SortOrder !== 0) { // Add new elements at the end (highest SortOrder) $this->SortOrder = ($this->Parent()->Children()->Count() + 1) * 2; } diff --git a/src/PageSectionPageElementRel.php b/src/PageSectionPageElementRel.php index bca6363..8649880 100644 --- a/src/PageSectionPageElementRel.php +++ b/src/PageSectionPageElementRel.php @@ -23,7 +23,7 @@ public function onBeforeWrite() parent::onBeforeWrite(); if (!$this->ID) { - if (!$this->SortOrder) { + if (!$this->SortOrder && $this->SortOrder !== 0) { // Add new elements at the end (highest SortOrder) $this->SortOrder = ($this->PageSection()->Elements()->Count() + 1) * 2; } diff --git a/src/TreeView.php b/src/TreeView.php index bfe2b76..cfd12c7 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -289,6 +289,7 @@ public function add($request) $child->write(); $item->Children()->Add($child, $sortArr); + $item->write(); // Make sure we can see the child $this->openItem(array_merge($path, [$item->ID])); @@ -596,12 +597,22 @@ public function FieldHolder($properties = array()) $elems[$class] = singleton($class)->singular_name(); } - // Create the add new multi class button - $addNew = DropdownField::create('AddNewClass', '', $elems); - $addNew->addExtraClass("treeview-actions-addnew__dropdown"); + // Create the add new button at the very top + $addButton = TreeViewFormAction::create( + $this, + "AddActionBase", + null, + null, + null + ); + $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); + $addButton->addExtraClass("btn add-button font-icon-plus"); + if (!count($elems)) { + $addButton->setDisabled(true); + } + $addButton->setButtonContent(' '); $content .= ArrayData::create([ - "Title" => "Add new", - "ClassField" => $addNew + "Button" => $addButton ])->renderWith("\FLXLabs\PageSections\TreeViewAddNewButton"); // Create the find existing button diff --git a/templates/FLXLabs/PageSections/TreeViewAddNewButton.ss b/templates/FLXLabs/PageSections/TreeViewAddNewButton.ss index 0e32535..bef0ae3 100644 --- a/templates/FLXLabs/PageSections/TreeViewAddNewButton.ss +++ b/templates/FLXLabs/PageSections/TreeViewAddNewButton.ss @@ -1,4 +1,3 @@ -
                                                        - $ClassField.FieldHolder - +
                                                        + $Button
                                                        From 6e194071c7709b731dc75086e259a08b0507171f Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Fri, 31 Aug 2018 15:34:00 +0200 Subject: [PATCH 36/82] fix(treeview): Hide element when dragging --- javascript/TreeView.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/javascript/TreeView.js b/javascript/TreeView.js index 1ed4172..14c5ea8 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -290,7 +290,6 @@ $treeView .find("> .treeview-pagesections__header > .treeview-item-actions .add-button") .click(function (event) { - console.log("click"); event.preventDefault(); event.stopImmediatePropagation(); @@ -507,7 +506,14 @@ marginTop: -40, marginLeft: -4 }); - $panel.css("opacity", 0.6); + //$panel.css("opacity", 0.6); + //$panel.hide(); + $panel.css({ + overflow: "hidden", + height: 0, + minHeight: 0, + border: 'none' + }); return $helper; }, @@ -598,7 +604,13 @@ // Show the previous elements. If the user made an invalid movement then // we want this to show anyways. If he did something valid the treeview will // refresh so we don't care if it's visible behind the loading icon. - $(".treeview-item__panel").css("opacity", ""); + //$(".treeview-item__panel").show(); + $(".treeview-item__panel").css({ + overflow: "", + height: "", + minHeight: "", + border: "" + }); } }); From 288957a625011383ce15ac349127b419703441b9 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Fri, 31 Aug 2018 17:43:29 +0200 Subject: [PATCH 37/82] fix(treeview): Fix save buttons in dialog --- javascript/TreeView.js | 530 +++++++++++++++++++++-------------------- 1 file changed, 274 insertions(+), 256 deletions(-) diff --git a/javascript/TreeView.js b/javascript/TreeView.js index 14c5ea8..2e1c089 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -1,23 +1,23 @@ -(function ($) { +(function($) { function TreeViewContextMenu() { - this.createDom = function (id, name) { + this.createDom = function(id, name) { this.$menu = $( "
                                                          " + name + + "' class='treeview-menu' data-id='" + + id + + "'>" ); }; - this.addLabel = function (label) { - this.$menu.append("
                                                        • " + label + "
                                                        • "); + this.addLabel = function(label) { + this.$menu.append("
                                                        • " + label + '
                                                        • '); }; - this.addItem = function (type, label, onClick = function () {}) { - var $li = $("
                                                        • " + label + "
                                                        • "); + this.addItem = function(type, label, onClick = function() {}) { + var $li = $("
                                                        • " + label + '
                                                        • '); $li.click(onClick); this.$menu.append($li); }; - this.show = function (x, y) { + this.show = function(x, y) { var pos = { top: y, left: x @@ -27,7 +27,7 @@ this.$menu.css(pos); this.$menu.show(); var that = this; - window.requestAnimationFrame(function () { + window.requestAnimationFrame(function() { var wW = $(window).width(); var wH = $(window).height(); var eW = that.$menu.outerWidth(true); @@ -43,63 +43,69 @@ that.$menu.css(pos); }); }; - this.remove = function () { + this.remove = function() { this.$menu.remove(); }; } - $.entwine("ss", function ($) { + $.entwine('ss', function($) { // Hide our custom context menu when not needed - $(document).on("mousedown", function (event) { - $parents = $(event.target).parents(".treeview-menu"); + $(document).on('mousedown', function(event) { + $parents = $(event.target).parents('.treeview-menu'); if ($parents.length == 0) { - $(".treeview-menu").remove(); + $('.treeview-menu').remove(); } }); - $(".view-detail-dialog .gridfield-better-buttons-prevnext").entwine({ - onclick: function () { - this.closest(".view-detail-dialog").loadDialog( - $.get(this.prop("href")) + $('.view-detail-dialog .gridfield-better-buttons-prevnext').entwine({ + onclick: function() { + this.closest('.view-detail-dialog').loadDialog( + $.get(this.prop('href')) ); return false; } }); - $(".view-detail-dialog .action-detail").entwine({ - onclick: function () { - const dialog = this.closest(".view-detail-dialog").loadDialog( - $.get(this.prop("href")) + $('.view-detail-dialog .action-detail').entwine({ + onclick: function() { + const dialog = this.closest('.view-detail-dialog').loadDialog( + $.get(this.prop('href')) ); - dialog.data("href", this.prop("href")); + dialog.data('href', this.prop('href')); return false; } }); - $(".view-detail-dialog form").entwine({ - onsubmit: function () { - var dialog = this.closest(".view-detail-dialog"); + $('.view-detail-dialog form').entwine({ + onsubmit: function() { + var dialog = this.closest('.view-detail-dialog'); const self = this; $.ajax( - $.extend({}, { - headers: { - "X-Pjax": "CurrentField" - }, - type: "POST", - url: this.prop("action"), - dataType: "html", - data: this.serialize(), - success: function () { - if (self.hasClass('view-detail-form')) { - dialog.data("treeview").reload(); - dialog.dialog("close"); - } else { - dialog.loadDialog($.get(dialog.data("href"))); + $.extend( + {}, + { + headers: { + 'X-Pjax': 'CurrentField' + }, + type: 'POST', + url: this.prop('action'), + dataType: 'html', + data: this.serialize(), + success: function() { + if (self.hasClass('view-detail-form')) { + dialog.data('treeview').reload(); + dialog.dialog('close'); + } else { + const url = self.prop('href') + ? self.prop('href') + : dialog.data('href'); + dialog.loadDialog($.get(url)); + } } } - }) + ) ); return false; @@ -108,65 +114,66 @@ // Show our search form after opening the search dialog // Show our detail form after opening the detail dialog - $(".add-existing-search-dialog-treeview, .view-detail-dialog").entwine({ - loadDialog: function (deferred) { - var dialog = this.addClass("loading") - .children(".ui-dialog-content") - .empty(); + $('.add-existing-search-dialog-treeview, .view-detail-dialog').entwine({ + loadDialog: function(deferred) { + var dialog = this.addClass('loading').empty(); - deferred.done(function (data) { - dialog - .html(data) - .parent() - .removeClass("loading"); + deferred.done(function(data) { + dialog.html(data).removeClass('loading'); }); return this; } }); // Submit our search form to our own endpoint and show the results - $(".add-existing-search-dialog-treeview .add-existing-search-form").entwine({ - onsubmit: function () { - this.closest(".add-existing-search-dialog-treeview").loadDialog( - $.get(this.prop("action"), this.serialize()) - ); - return false; + $('.add-existing-search-dialog-treeview .add-existing-search-form').entwine( + { + onsubmit: function() { + this.closest('.add-existing-search-dialog-treeview').loadDialog( + $.get(this.prop('action'), this.serialize()) + ); + return false; + } } - }); + ); // Allow clicking the elements in the search form $( - ".add-existing-search-dialog-treeview .add-existing-search-items .list-group-item-action" + '.add-existing-search-dialog-treeview .add-existing-search-items .list-group-item-action' ).entwine({ - onclick: function () { - if (this.children("a").length > 0) { - this.children("a") + onclick: function() { + if (this.children('a').length > 0) { + this.children('a') .first() - .trigger("click"); + .trigger('click'); } } }); // Trigger the "add existing" action on the selected element - $(".add-existing-search-dialog-treeview .add-existing-search-items a").entwine({ - onclick: function () { - var link = this.closest(".add-existing-search-items").data("add-link"); - var id = this.data("id"); - - var dialog = this.closest(".add-existing-search-dialog-treeview") - .addClass("loading") - .children(".ui-dialog-content") + $( + '.add-existing-search-dialog-treeview .add-existing-search-items a' + ).entwine({ + onclick: function() { + var link = this.closest('.add-existing-search-items').data('add-link'); + var id = this.data('id'); + + var dialog = this.closest('.add-existing-search-dialog-treeview') + .addClass('loading') .empty(); - dialog.data("treeview").reload({ + dialog.data('treeview').reload( + { url: link, - data: [{ - name: "id", - value: id - }] + data: [ + { + name: 'id', + value: id + } + ] }, - function () { - dialog.dialog("close"); + function() { + dialog.dialog('close'); } ); @@ -175,10 +182,12 @@ }); // Browse search result pages - $(".add-existing-search-dialog-treeview .add-existing-search-pagination a").entwine({ - onclick: function () { - this.closest(".add-existing-search-dialog-treeview").loadDialog( - $.get(this.prop("href")) + $( + '.add-existing-search-dialog-treeview .add-existing-search-pagination a' + ).entwine({ + onclick: function() { + this.closest('.add-existing-search-dialog-treeview').loadDialog( + $.get(this.prop('href')) ); return false; } @@ -201,108 +210,112 @@ });*/ // Attach data to our tree view - $(".treeview-pagesections").entwine({ - addItem: function (parents, itemId, elemType, sort = 99999) { + $('.treeview-pagesections').entwine({ + addItem: function(parents, itemId, elemType, sort = 99999) { var $treeView = $(this); $treeView.reload({ - url: $treeView.data("url") + "/add", - data: [{ - name: "parents", + url: $treeView.data('url') + '/add', + data: [ + { + name: 'parents', value: parents }, { - name: "itemId", + name: 'itemId', value: itemId }, { - name: "type", + name: 'type', value: elemType }, { - name: "sort", + name: 'sort', value: sort } ] }); }, - removeItem: function (parents, itemId) { + removeItem: function(parents, itemId) { var $treeView = $(this); $treeView.reload({ - url: $treeView.data("url") + "/remove", - data: [{ - name: "parents", + url: $treeView.data('url') + '/remove', + data: [ + { + name: 'parents', value: parents }, { - name: "itemId", + name: 'itemId', value: itemId } ] }); }, - deleteItem: function (parents, itemId) { + deleteItem: function(parents, itemId) { var $treeView = $(this); $treeView.reload({ - url: $treeView.data("url") + "/delete", - data: [{ - name: "parents", + url: $treeView.data('url') + '/delete', + data: [ + { + name: 'parents', value: parents }, { - name: "itemId", + name: 'itemId', value: itemId } ] }); }, - onadd: function () { + onadd: function() { var $treeView = $(this); - var name = $treeView.data("name"); - var url = $treeView.data("url"); + var name = $treeView.data('name'); + var url = $treeView.data('url'); // Setup find existing button - $treeView.find("button[name=action_FindExisting]").click(function () { - var dialog = $("
                                                          ") - .appendTo("body") + $treeView.find('button[name=action_FindExisting]').click(function() { + var dialog = $('
                                                          ') + .appendTo('body') .dialog({ modal: true, resizable: false, width: 500, height: 600, - close: function () { + close: function() { $(this) - .dialog("destroy") + .dialog('destroy') .remove(); } }); dialog - .parent() - .addClass("add-existing-search-dialog-treeview") - .loadDialog($.get($treeView.data("url") + "/search")); - dialog.data("treeview", $treeView); + .addClass('add-existing-search-dialog-treeview') + .data('treeview', $treeView) + .loadDialog($.get($treeView.data('url') + '/search')); }); // Add new button at the very top $treeView - .find("> .treeview-pagesections__header > .treeview-item-actions .add-button") - .click(function (event) { + .find( + '> .treeview-pagesections__header > .treeview-item-actions .add-button' + ) + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); $target = $(event.target); - var elems = $target.data("allowed-elements"); + var elems = $target.data('allowed-elements'); var menu = new TreeViewContextMenu(); menu.createDom(null, name); menu.addLabel( - ss.i18n._t("PageSections.TreeView.AddAChild", "Add a child") + ss.i18n._t('PageSections.TreeView.AddAChild', 'Add a child') ); - $.each(elems, function (key, value) { - menu.addItem(key, value, function () { + $.each(elems, function(key, value) { + menu.addItem(key, value, function() { $treeView.addItem([], null, key, 1); menu.remove(); }); @@ -311,26 +324,27 @@ }); // Process items - $treeView.find(".treeview-item").each(function () { + $treeView.find('.treeview-item').each(function() { var $item = $(this); - var itemId = $item.data("id"); - var parents = $item.data("tree"); + var itemId = $item.data('id'); + var parents = $item.data('tree'); // Open an item button $item - .find("> .treeview-item__panel > .treeview-item-flow .tree-button") - .click(function (event) { + .find('> .treeview-item__panel > .treeview-item-flow .tree-button') + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); $treeView.reload({ - url: url + "/tree", - data: [{ - name: "parents", + url: url + '/tree', + data: [ + { + name: 'parents', value: parents }, { - name: "itemId", + name: 'itemId', value: itemId } ] @@ -339,50 +353,49 @@ // Edit button $item - .find("> .treeview-item__panel > .treeview-item-flow .edit-button") - .click(function () { - var dialog = $("
                                                          ") - .appendTo("body") + .find('> .treeview-item__panel > .treeview-item-flow .edit-button') + .click(function() { + var dialog = $('
                                                          ') + .appendTo('body') .dialog({ modal: false, resizable: false, width: $(window).width() * 0.9, height: $(window).height() * 0.9, - close: function () { + close: function() { $(this) - .dialog("destroy") + .dialog('destroy') .remove(); } }); dialog - .parent() - .addClass("view-detail-dialog") + .addClass('view-detail-dialog') + .data('treeview', $treeView) .loadDialog( - $.get($treeView.data("url") + "/detail?ID=" + itemId) + $.get($treeView.data('url') + '/detail?ID=' + itemId) ); - dialog.data("treeview", $treeView); }); // Add new item button $item .find( - "> .treeview-item__panel > .treeview-item-actions .add-button" + '> .treeview-item__panel > .treeview-item-actions .add-button' ) - .click(function (event) { + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); $target = $(event.target); - var elems = $target.data("allowed-elements"); + var elems = $target.data('allowed-elements'); var menu = new TreeViewContextMenu(); menu.createDom(itemId, name); menu.addLabel( - ss.i18n._t("PageSections.TreeView.AddAChild", "Add a child") + ss.i18n._t('PageSections.TreeView.AddAChild', 'Add a child') ); - $.each(elems, function (key, value) { - menu.addItem(key, value, function () { + $.each(elems, function(key, value) { + menu.addItem(key, value, function() { $treeView.addItem(parents, itemId, key, 1); menu.remove(); }); @@ -392,30 +405,30 @@ // Add new after item button $item - .find("> .treeview-item__post-actions .add-after-button") - .click(function (event) { + .find('> .treeview-item__post-actions .add-after-button') + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); $target = $(event.target); - var elems = $target.data("allowed-elements"); + var elems = $target.data('allowed-elements'); var menu = new TreeViewContextMenu(); menu.createDom(itemId, name); menu.addLabel( ss.i18n._t( - "PageSections.TreeView.AddAfterThis", - "Add new element" + 'PageSections.TreeView.AddAfterThis', + 'Add new element' ) ); - $.each(elems, function (key, value) { - menu.addItem(key, value, function () { + $.each(elems, function(key, value) { + menu.addItem(key, value, function() { $treeView.addItem( parents.slice(0, parents.length - 1), parents[parents.length - 1], key, - $item.data("sort") + 1 + $item.data('sort') + 1 ); menu.remove(); }); @@ -426,9 +439,9 @@ // Delete button action $item .find( - "> .treeview-item__panel > .treeview-item-flow .delete-button" + '> .treeview-item__panel > .treeview-item-flow .delete-button' ) - .click(function (event) { + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -437,39 +450,39 @@ var menu = new TreeViewContextMenu(); menu.createDom(itemId, name); menu.addLabel( - ss.i18n._t("PageSections.TreeView.Delete", "Delete") + ss.i18n._t('PageSections.TreeView.Delete', 'Delete') ); menu.addItem( - "__REMOVE__", - ss.i18n._t("PageSections.TreeView.RemoveAChild", "Remove"), - function () { + '__REMOVE__', + ss.i18n._t('PageSections.TreeView.RemoveAChild', 'Remove'), + function() { $treeView.removeItem(parents, itemId); menu.remove(); } ); - if ($target.data("used-count") < 2) { + if ($target.data('used-count') < 2) { menu.addItem( - "", + '', ss.i18n._t( - "PageSections.TreeView.DeleteAChild", - "Finally delete" + 'PageSections.TreeView.DeleteAChild', + 'Finally delete' ), - function () { + function() { $treeView.deleteItem(parents, itemId); menu.remove(); } ); var $li = $( - "
                                                        • " + - ss.i18n._t( - "PageSections.TreeView.DeleteAChild", - "Finally delete" - ) + - "
                                                        • ", - function () { + '
                                                        • ' + + ss.i18n._t( + 'PageSections.TreeView.DeleteAChild', + 'Finally delete' + ) + + '
                                                        • ', + function() { $treeView.deleteItem(parents, itemId); menu.remove(); } @@ -480,26 +493,26 @@ }); // Attach draggable events & info - $item.find(".treeview-item-reorder__handle").draggable({ + $item.find('.treeview-item-reorder__handle').draggable({ //cancel: ".treeview-item", - appendTo: "body", - revert: "invalid", - cursor: "crosshair", + appendTo: 'body', + revert: 'invalid', + cursor: 'crosshair', cursorAt: { top: 30, left: 15 }, - activeClass: "state-active", - hoverClass: "state-active", - tolerance: "pointer", + activeClass: 'state-active', + hoverClass: 'state-active', + tolerance: 'pointer', greedy: true, - helper: function (event) { - var $panel = $item.find("> .treeview-item__panel"); + helper: function(event) { + var $panel = $item.find('> .treeview-item__panel'); var $helper = $("
                                                          "); $helper .append($item.clone(false)) - .find(".treeview-item__children") + .find('.treeview-item__children') .remove(); $helper.css({ width: $panel.outerWidth(true), @@ -509,7 +522,7 @@ //$panel.css("opacity", 0.6); //$panel.hide(); $panel.css({ - overflow: "hidden", + overflow: 'hidden', height: 0, minHeight: 0, border: 'none' @@ -517,20 +530,20 @@ return $helper; }, - start: function () { - $(".ui-droppable").each(function () { + start: function() { + $('.ui-droppable').each(function() { var $drop = $(this); - var $dropItem = $drop.closest(".treeview-item"); + var $dropItem = $drop.closest('.treeview-item'); var $parentDropItem = $dropItem .parent() - .closest(".treeview-item"); - var clazz = $item.data("class"); + .closest('.treeview-item'); + var clazz = $item.data('class'); - var isOpen = $dropItem.data("is-open"); + var isOpen = $dropItem.data('is-open'); // Dont enable dropping on itself // or a same id - if ($dropItem.data("id") == itemId) { + if ($dropItem.data('id') == itemId) { return; } @@ -538,7 +551,7 @@ // or a same id if ( $dropItem.parents(".treeview-item[data-id='" + itemId + "']") - .length + .length ) { return; } @@ -547,10 +560,10 @@ // from neighbours with same id // (no same ids on same branch) if ( - ($drop.hasClass("after") || $drop.hasClass("before")) && + ($drop.hasClass('after') || $drop.hasClass('before')) && $dropItem - .siblings(".treeview-item[data-id='" + itemId + "']") - .not($item).length + .siblings(".treeview-item[data-id='" + itemId + "']") + .not($item).length ) { return; } @@ -558,23 +571,23 @@ // Dont enable dropping on next bottom // of the same id if ( - $drop.hasClass("after") && - $dropItem.next().data("id") == itemId + $drop.hasClass('after') && + $dropItem.next().data('id') == itemId ) { return; } // Don't enable dropping on the middle arrow for open items // (they will have child elements where we can drop before or after) - if ($drop.hasClass("middle") && isOpen) { + if ($drop.hasClass('middle') && isOpen) { return; } // avoid adding unallowed elements on root section if (!$parentDropItem.length) { var allowed = $drop - .closest(".treeview-pagesections") - .data("allowed-elements"); + .closest('.treeview-pagesections') + .data('allowed-elements'); if (allowed && !allowed[clazz]) { return; } @@ -583,14 +596,14 @@ // Don't allow dropping elements on this level if they're not an allowed child // Depending on the arrow we either have to check this element or the parent // of this element to see which children are allowed - if ($drop.hasClass("before") || $drop.hasClass("after")) { - var allowed = $parentDropItem.data("allowed-elements"); + if ($drop.hasClass('before') || $drop.hasClass('after')) { + var allowed = $parentDropItem.data('allowed-elements'); if (allowed && !allowed[clazz]) { return; } } else { // is middle - var allowed = $dropItem.data("allowed-elements"); + var allowed = $dropItem.data('allowed-elements'); if (allowed && !allowed[clazz]) { return; } @@ -599,68 +612,69 @@ $drop.show(); }); }, - stop: function (event, ui) { - $(".ui-droppable").css("display", ""); + stop: function(event, ui) { + $('.ui-droppable').css('display', ''); // Show the previous elements. If the user made an invalid movement then // we want this to show anyways. If he did something valid the treeview will // refresh so we don't care if it's visible behind the loading icon. //$(".treeview-item__panel").show(); - $(".treeview-item__panel").css({ - overflow: "", - height: "", - minHeight: "", - border: "" + $('.treeview-item__panel').css({ + overflow: '', + height: '', + minHeight: '', + border: '' }); } }); // Dropping targets - $item.find(".treeview-item-reorder .droppable").each(function () { + $item.find('.treeview-item-reorder .droppable').each(function() { $(this).droppable({ - hoverClass: "state-active", - tolerance: "pointer", - drop: function (event, ui) { + hoverClass: 'state-active', + tolerance: 'pointer', + drop: function(event, ui) { $drop = $(this); - $dropItem = $drop.closest(".treeview-item"); + $dropItem = $drop.closest('.treeview-item'); - $oldItem = ui.draggable.closest(".treeview-item"); - var oldId = $oldItem.data("id"); - var oldParents = $oldItem.data("tree"); + $oldItem = ui.draggable.closest('.treeview-item'); + var oldId = $oldItem.data('id'); + var oldParents = $oldItem.data('tree'); - var type = "child"; + var type = 'child'; var sort = 100000; - if ($drop.hasClass("before")) { - type = "before"; - sort = $dropItem.data("sort") - 1; - } else if ($drop.hasClass("after")) { - type = "after"; - sort = $dropItem.data("sort") + 1; + if ($drop.hasClass('before')) { + type = 'before'; + sort = $dropItem.data('sort') - 1; + } else if ($drop.hasClass('after')) { + type = 'after'; + sort = $dropItem.data('sort') + 1; } var newParent = - type === "child" ? - itemId : - parents.length > 0 ? - parents[parents.length - 1] : - ""; + type === 'child' + ? itemId + : parents.length > 0 + ? parents[parents.length - 1] + : ''; $treeView.reload({ - url: url + "/move", - data: [{ - name: "parents", + url: url + '/move', + data: [ + { + name: 'parents', value: oldParents }, { - name: "itemId", + name: 'itemId', value: oldId }, { - name: "newParent", + name: 'newParent', value: newParent }, { - name: "sort", + name: 'sort', value: sort } ] @@ -673,39 +687,43 @@ // This is copy paste from SilverStripe GridField.js, modified to work for the TreeView // It updates the gridfield by sending the specified request // and using the response as the new content for the gridfield - reload: function (ajaxOpts, successCallback) { + reload: function(ajaxOpts, successCallback) { var self = this, - form = this.closest("form"), - focusedElName = this.find(":input:focus").attr("name"), // Save focused element for restoring after refresh - data = form.find(":input").serializeArray(); + form = this.closest('form'), + focusedElName = this.find(':input:focus').attr('name'), // Save focused element for restoring after refresh + data = form.find(':input').serializeArray(); if (!ajaxOpts) ajaxOpts = {}; if (!ajaxOpts.data) ajaxOpts.data = []; - ajaxOpts.data = ajaxOpts.data.concat(data).concat([{ - name: "state", - value: self.data("state-id") - }]); + ajaxOpts.data = ajaxOpts.data.concat(data).concat([ + { + name: 'state', + value: self.data('state-id') + } + ]); // Include any GET parameters from the current URL, as the view state might depend on it. // For example, a list prefiltered through external search criteria might be passed to GridField. if (window.location.search) { ajaxOpts.data = - window.location.search.replace(/^\?/, "") + - "&" + + window.location.search.replace(/^\?/, '') + + '&' + $.param(ajaxOpts.data); } - form.addClass("loading"); + form.addClass('loading'); $.ajax( - $.extend({}, { + $.extend( + {}, + { headers: { - "X-Pjax": "CurrentField" + 'X-Pjax': 'CurrentField' }, - type: "POST", - url: this.data("url"), - dataType: "html", - success: function (data) { + type: 'POST', + url: this.data('url'), + dataType: 'html', + success: function(data) { // Replace the grid field with response, not the form. // TODO Only replaces all its children, to avoid replacing the current scope // of the executing method. Means that it doesn't retrigger the onmatch() on the main container. @@ -716,14 +734,14 @@ if (focusedElName) self.find(':input[name="' + focusedElName + '"]').focus(); - form.removeClass("loading"); + form.removeClass('loading'); if (successCallback) successCallback.apply(this, arguments); // TODO: Don't know how original SilverStripe GridField magically calls self.onadd(); }, - error: function (e) { - alert(i18n._t("Admin.ERRORINTRANSACTION")); - form.removeClass("loading"); + error: function(e) { + alert(i18n._t('Admin.ERRORINTRANSACTION')); + form.removeClass('loading'); } }, ajaxOpts From b49b9afa941bcb6f7a9ebd039441e57de5d0b7c1 Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Fri, 31 Aug 2018 18:30:45 +0200 Subject: [PATCH 38/82] fix(treeview): Fix dialog save and close button --- javascript/TreeView.js | 242 ++++++++++++++++++++--------------------- 1 file changed, 117 insertions(+), 125 deletions(-) diff --git a/javascript/TreeView.js b/javascript/TreeView.js index 2e1c089..8ff88d3 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -1,23 +1,23 @@ -(function($) { +(function ($) { function TreeViewContextMenu() { - this.createDom = function(id, name) { + this.createDom = function (id, name) { this.$menu = $( "
                                                            " + name + + "' class='treeview-menu' data-id='" + + id + + "'>" ); }; - this.addLabel = function(label) { + this.addLabel = function (label) { this.$menu.append("
                                                          • " + label + '
                                                          • '); }; - this.addItem = function(type, label, onClick = function() {}) { + this.addItem = function (type, label, onClick = function () {}) { var $li = $("
                                                          • " + label + '
                                                          • '); $li.click(onClick); this.$menu.append($li); }; - this.show = function(x, y) { + this.show = function (x, y) { var pos = { top: y, left: x @@ -27,7 +27,7 @@ this.$menu.css(pos); this.$menu.show(); var that = this; - window.requestAnimationFrame(function() { + window.requestAnimationFrame(function () { var wW = $(window).width(); var wH = $(window).height(); var eW = that.$menu.outerWidth(true); @@ -43,14 +43,14 @@ that.$menu.css(pos); }); }; - this.remove = function() { + this.remove = function () { this.$menu.remove(); }; } - $.entwine('ss', function($) { + $.entwine('ss', function ($) { // Hide our custom context menu when not needed - $(document).on('mousedown', function(event) { + $(document).on('mousedown', function (event) { $parents = $(event.target).parents('.treeview-menu'); if ($parents.length == 0) { $('.treeview-menu').remove(); @@ -58,7 +58,7 @@ }); $('.view-detail-dialog .gridfield-better-buttons-prevnext').entwine({ - onclick: function() { + onclick: function () { this.closest('.view-detail-dialog').loadDialog( $.get(this.prop('href')) ); @@ -67,7 +67,7 @@ }); $('.view-detail-dialog .action-detail').entwine({ - onclick: function() { + onclick: function () { const dialog = this.closest('.view-detail-dialog').loadDialog( $.get(this.prop('href')) ); @@ -77,35 +77,36 @@ }); $('.view-detail-dialog form').entwine({ - onsubmit: function() { + onsubmit: function () { var dialog = this.closest('.view-detail-dialog'); const self = this; $.ajax( - $.extend( - {}, - { - headers: { - 'X-Pjax': 'CurrentField' - }, - type: 'POST', - url: this.prop('action'), - dataType: 'html', - data: this.serialize(), - success: function() { - if (self.hasClass('view-detail-form')) { - dialog.data('treeview').reload(); - dialog.dialog('close'); + $.extend({}, { + headers: { + 'X-Pjax': 'CurrentField' + }, + type: 'POST', + url: this.prop('action'), + dataType: 'html', + data: this.serialize(), + success: function () { + if (self.hasClass('view-detail-form')) { + dialog.data('treeview').reload(); + dialog.dialog('close'); + } else { + if (dialog.data("clickedButton") === 'action_doSaveAndQuit') { + dialog.loadDialog($.get(dialog.data("origUrl"))); } else { - const url = self.prop('href') - ? self.prop('href') - : dialog.data('href'); + const url = self.prop('href') ? + self.prop('href') : + dialog.data('href'); dialog.loadDialog($.get(url)); } } } - ) + }) ); return false; @@ -115,33 +116,34 @@ // Show our search form after opening the search dialog // Show our detail form after opening the detail dialog $('.add-existing-search-dialog-treeview, .view-detail-dialog').entwine({ - loadDialog: function(deferred) { + loadDialog: function (deferred) { var dialog = this.addClass('loading').empty(); - deferred.done(function(data) { + deferred.done(function (data) { dialog.html(data).removeClass('loading'); + dialog.find("button[type=submit]").click(function () { + dialog.data("clickedButton", this.name); + }); }); return this; } }); // Submit our search form to our own endpoint and show the results - $('.add-existing-search-dialog-treeview .add-existing-search-form').entwine( - { - onsubmit: function() { - this.closest('.add-existing-search-dialog-treeview').loadDialog( - $.get(this.prop('action'), this.serialize()) - ); - return false; - } + $('.add-existing-search-dialog-treeview .add-existing-search-form').entwine({ + onsubmit: function () { + this.closest('.add-existing-search-dialog-treeview').loadDialog( + $.get(this.prop('action'), this.serialize()) + ); + return false; } - ); + }); // Allow clicking the elements in the search form $( '.add-existing-search-dialog-treeview .add-existing-search-items .list-group-item-action' ).entwine({ - onclick: function() { + onclick: function () { if (this.children('a').length > 0) { this.children('a') .first() @@ -154,7 +156,7 @@ $( '.add-existing-search-dialog-treeview .add-existing-search-items a' ).entwine({ - onclick: function() { + onclick: function () { var link = this.closest('.add-existing-search-items').data('add-link'); var id = this.data('id'); @@ -162,17 +164,14 @@ .addClass('loading') .empty(); - dialog.data('treeview').reload( - { + dialog.data('treeview').reload({ url: link, - data: [ - { - name: 'id', - value: id - } - ] + data: [{ + name: 'id', + value: id + }] }, - function() { + function () { dialog.dialog('close'); } ); @@ -185,7 +184,7 @@ $( '.add-existing-search-dialog-treeview .add-existing-search-pagination a' ).entwine({ - onclick: function() { + onclick: function () { this.closest('.add-existing-search-dialog-treeview').loadDialog( $.get(this.prop('href')) ); @@ -211,13 +210,12 @@ // Attach data to our tree view $('.treeview-pagesections').entwine({ - addItem: function(parents, itemId, elemType, sort = 99999) { + addItem: function (parents, itemId, elemType, sort = 99999) { var $treeView = $(this); $treeView.reload({ url: $treeView.data('url') + '/add', - data: [ - { + data: [{ name: 'parents', value: parents }, @@ -236,13 +234,12 @@ ] }); }, - removeItem: function(parents, itemId) { + removeItem: function (parents, itemId) { var $treeView = $(this); $treeView.reload({ url: $treeView.data('url') + '/remove', - data: [ - { + data: [{ name: 'parents', value: parents }, @@ -253,13 +250,12 @@ ] }); }, - deleteItem: function(parents, itemId) { + deleteItem: function (parents, itemId) { var $treeView = $(this); $treeView.reload({ url: $treeView.data('url') + '/delete', - data: [ - { + data: [{ name: 'parents', value: parents }, @@ -270,13 +266,13 @@ ] }); }, - onadd: function() { + onadd: function () { var $treeView = $(this); var name = $treeView.data('name'); var url = $treeView.data('url'); // Setup find existing button - $treeView.find('button[name=action_FindExisting]').click(function() { + $treeView.find('button[name=action_FindExisting]').click(function () { var dialog = $('
                                                            ') .appendTo('body') .dialog({ @@ -284,17 +280,19 @@ resizable: false, width: 500, height: 600, - close: function() { + close: function () { $(this) .dialog('destroy') .remove(); } }); + var url = $.get($treeView.data('url') + '/search'); dialog .addClass('add-existing-search-dialog-treeview') .data('treeview', $treeView) - .loadDialog($.get($treeView.data('url') + '/search')); + .data('origUrl', url) + .loadDialog(url); }); // Add new button at the very top @@ -302,7 +300,7 @@ .find( '> .treeview-pagesections__header > .treeview-item-actions .add-button' ) - .click(function(event) { + .click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -314,8 +312,8 @@ menu.addLabel( ss.i18n._t('PageSections.TreeView.AddAChild', 'Add a child') ); - $.each(elems, function(key, value) { - menu.addItem(key, value, function() { + $.each(elems, function (key, value) { + menu.addItem(key, value, function () { $treeView.addItem([], null, key, 1); menu.remove(); }); @@ -324,7 +322,7 @@ }); // Process items - $treeView.find('.treeview-item').each(function() { + $treeView.find('.treeview-item').each(function () { var $item = $(this); var itemId = $item.data('id'); var parents = $item.data('tree'); @@ -332,14 +330,13 @@ // Open an item button $item .find('> .treeview-item__panel > .treeview-item-flow .tree-button') - .click(function(event) { + .click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); $treeView.reload({ url: url + '/tree', - data: [ - { + data: [{ name: 'parents', value: parents }, @@ -354,7 +351,7 @@ // Edit button $item .find('> .treeview-item__panel > .treeview-item-flow .edit-button') - .click(function() { + .click(function () { var dialog = $('
                                                            ') .appendTo('body') .dialog({ @@ -362,19 +359,19 @@ resizable: false, width: $(window).width() * 0.9, height: $(window).height() * 0.9, - close: function() { + close: function () { $(this) .dialog('destroy') .remove(); } }); + var url = $treeView.data('url') + '/detail?ID=' + itemId; dialog .addClass('view-detail-dialog') .data('treeview', $treeView) - .loadDialog( - $.get($treeView.data('url') + '/detail?ID=' + itemId) - ); + .data('origUrl', url) + .loadDialog($.get(url)); }); // Add new item button @@ -382,7 +379,7 @@ .find( '> .treeview-item__panel > .treeview-item-actions .add-button' ) - .click(function(event) { + .click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -394,8 +391,8 @@ menu.addLabel( ss.i18n._t('PageSections.TreeView.AddAChild', 'Add a child') ); - $.each(elems, function(key, value) { - menu.addItem(key, value, function() { + $.each(elems, function (key, value) { + menu.addItem(key, value, function () { $treeView.addItem(parents, itemId, key, 1); menu.remove(); }); @@ -406,7 +403,7 @@ // Add new after item button $item .find('> .treeview-item__post-actions .add-after-button') - .click(function(event) { + .click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -422,8 +419,8 @@ ) ); - $.each(elems, function(key, value) { - menu.addItem(key, value, function() { + $.each(elems, function (key, value) { + menu.addItem(key, value, function () { $treeView.addItem( parents.slice(0, parents.length - 1), parents[parents.length - 1], @@ -441,7 +438,7 @@ .find( '> .treeview-item__panel > .treeview-item-flow .delete-button' ) - .click(function(event) { + .click(function (event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -456,7 +453,7 @@ menu.addItem( '__REMOVE__', ss.i18n._t('PageSections.TreeView.RemoveAChild', 'Remove'), - function() { + function () { $treeView.removeItem(parents, itemId); menu.remove(); } @@ -469,7 +466,7 @@ 'PageSections.TreeView.DeleteAChild', 'Finally delete' ), - function() { + function () { $treeView.deleteItem(parents, itemId); menu.remove(); } @@ -477,12 +474,12 @@ var $li = $( '
                                                          • ' + - ss.i18n._t( - 'PageSections.TreeView.DeleteAChild', - 'Finally delete' - ) + - '
                                                          • ', - function() { + ss.i18n._t( + 'PageSections.TreeView.DeleteAChild', + 'Finally delete' + ) + + '', + function () { $treeView.deleteItem(parents, itemId); menu.remove(); } @@ -507,7 +504,7 @@ tolerance: 'pointer', greedy: true, - helper: function(event) { + helper: function (event) { var $panel = $item.find('> .treeview-item__panel'); var $helper = $("
                                                            "); $helper @@ -530,8 +527,8 @@ return $helper; }, - start: function() { - $('.ui-droppable').each(function() { + start: function () { + $('.ui-droppable').each(function () { var $drop = $(this); var $dropItem = $drop.closest('.treeview-item'); var $parentDropItem = $dropItem @@ -551,7 +548,7 @@ // or a same id if ( $dropItem.parents(".treeview-item[data-id='" + itemId + "']") - .length + .length ) { return; } @@ -562,8 +559,8 @@ if ( ($drop.hasClass('after') || $drop.hasClass('before')) && $dropItem - .siblings(".treeview-item[data-id='" + itemId + "']") - .not($item).length + .siblings(".treeview-item[data-id='" + itemId + "']") + .not($item).length ) { return; } @@ -612,7 +609,7 @@ $drop.show(); }); }, - stop: function(event, ui) { + stop: function (event, ui) { $('.ui-droppable').css('display', ''); // Show the previous elements. If the user made an invalid movement then // we want this to show anyways. If he did something valid the treeview will @@ -628,11 +625,11 @@ }); // Dropping targets - $item.find('.treeview-item-reorder .droppable').each(function() { + $item.find('.treeview-item-reorder .droppable').each(function () { $(this).droppable({ hoverClass: 'state-active', tolerance: 'pointer', - drop: function(event, ui) { + drop: function (event, ui) { $drop = $(this); $dropItem = $drop.closest('.treeview-item'); @@ -652,16 +649,15 @@ } var newParent = - type === 'child' - ? itemId - : parents.length > 0 - ? parents[parents.length - 1] - : ''; + type === 'child' ? + itemId : + parents.length > 0 ? + parents[parents.length - 1] : + ''; $treeView.reload({ url: url + '/move', - data: [ - { + data: [{ name: 'parents', value: oldParents }, @@ -687,7 +683,7 @@ // This is copy paste from SilverStripe GridField.js, modified to work for the TreeView // It updates the gridfield by sending the specified request // and using the response as the new content for the gridfield - reload: function(ajaxOpts, successCallback) { + reload: function (ajaxOpts, successCallback) { var self = this, form = this.closest('form'), focusedElName = this.find(':input:focus').attr('name'), // Save focused element for restoring after refresh @@ -695,12 +691,10 @@ if (!ajaxOpts) ajaxOpts = {}; if (!ajaxOpts.data) ajaxOpts.data = []; - ajaxOpts.data = ajaxOpts.data.concat(data).concat([ - { - name: 'state', - value: self.data('state-id') - } - ]); + ajaxOpts.data = ajaxOpts.data.concat(data).concat([{ + name: 'state', + value: self.data('state-id') + }]); // Include any GET parameters from the current URL, as the view state might depend on it. // For example, a list prefiltered through external search criteria might be passed to GridField. @@ -714,16 +708,14 @@ form.addClass('loading'); $.ajax( - $.extend( - {}, - { + $.extend({}, { headers: { 'X-Pjax': 'CurrentField' }, type: 'POST', url: this.data('url'), dataType: 'html', - success: function(data) { + success: function (data) { // Replace the grid field with response, not the form. // TODO Only replaces all its children, to avoid replacing the current scope // of the executing method. Means that it doesn't retrigger the onmatch() on the main container. @@ -739,7 +731,7 @@ // TODO: Don't know how original SilverStripe GridField magically calls self.onadd(); }, - error: function(e) { + error: function (e) { alert(i18n._t('Admin.ERRORINTRANSACTION')); form.removeClass('loading'); } From c269a7b07137cd38aee4ca977fc4853bf951774d Mon Sep 17 00:00:00 2001 From: Marco Crespi Date: Mon, 3 Sep 2018 12:33:51 +0200 Subject: [PATCH 39/82] fix(sections): Fix creating page sections parents not working --- src/PageSectionsExtension.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index aaecd6d..9bc5ff6 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -87,27 +87,34 @@ public function getPageSectionNames() return $sections; } - public function onBeforeWrite() + public function onAfterWrite() { - parent::onBeforeWrite(); + parent::onAfterWrite(); - if ($this->owner->ID) { + if ($this->owner->ID && !$this->owner->__rewrite) { $sections = $this->getPageSectionNames(); + $new = false; foreach ($sections as $sectionName) { - $name = "PageSection".$sectionName; + $name = "PageSection".$sectionName."ID"; // Create a page section if we don't have one yet - if (!$this->owner->$name()->ID) { + if (!$this->owner->$name) { $section = PageSection::create(); $section->__Name = $sectionName; $section->__ParentID = $this->owner->ID; $section->__ParentClass = $this->owner->ClassName; $section->__isNew = true; $section->write(); - $this->owner->$name = $section; + $this->owner->$name = $section->ID; + $new = true; } } + + if ($new) { + $this->owner->__rewrite = true; + $this->owner->write(); + } } } From abe62d22d484dd67c67247cc41993c189ebdf786 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Wed, 10 Oct 2018 14:14:03 +0200 Subject: [PATCH 40/82] fix(publish): Fix publish being detected as new change --- src/PageElement.php | 7 ++++++- src/PageSection.php | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/PageElement.php b/src/PageElement.php index 4c636e5..eb9386d 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -142,11 +142,16 @@ public function onAfterWrite() { parent::onAfterWrite(); - if (Versioned::get_stage() == Versioned::DRAFT) { + if (Versioned::get_stage() == Versioned::DRAFT && $this->isChanged("__Counter", DataObject::CHANGE_VALUE)) { foreach ($this->PageSections() as $section) { $section->__Counter++; $section->write(); } + + foreach ($this->Parents() as $parent) { + $parent->__Counter++; + $parent->write(); + } } } diff --git a/src/PageSection.php b/src/PageSection.php index 25dd1de..68da74f 100644 --- a/src/PageSection.php +++ b/src/PageSection.php @@ -62,7 +62,7 @@ public function onAfterWrite() { parent::onAfterWrite(); - if (!$this->__isNew && Versioned::get_stage() == Versioned::DRAFT) { + if (!$this->__isNew && Versioned::get_stage() == Versioned::DRAFT && $this->isChanged("__Counter", DataObject::CHANGE_VALUE)) { $this->Parent()->__PageSectionCounter++; $this->Parent()->write(); } From 3c51ffe7853abbbc6a53523c584754246f15d5b3 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Wed, 10 Oct 2018 14:37:58 +0200 Subject: [PATCH 41/82] fix(publish): Fix changing sub elements not marking the section changed --- src/PageElement.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/PageElement.php b/src/PageElement.php index eb9386d..75a3a78 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -131,6 +131,12 @@ public function onBeforeWrite() { parent::onBeforeWrite(); + // If a field changed then update the counter, unless it's the counter that changed + $changed = $this->getChangedFields(true, DataObject::CHANGE_VALUE); + if (count($changed) > 0 && (!isset($changed["__Counter"]) || $changed["__Counter"]["level"] <= 1)) { + $this->__Counter++; + } + $elems = $this->Children()->Sort("SortOrder")->Column("ID"); $count = count($elems); for ($i = 0; $i < $count; $i++) { From 64ab5fb7df4dc381155a41a3f5fc29ee2ba6c313 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Thu, 11 Oct 2018 14:40:32 +0200 Subject: [PATCH 42/82] fix(restore): Automatically try and restore page section if it's deleted --- src/PageSectionsExtension.php | 49 ++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 9bc5ff6..a64d323 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -11,6 +11,7 @@ use SilverStripe\Forms\GridField\GridFieldDataColumns; use SilverStripe\Forms\GridField\GridFieldDetailForm; use SilverStripe\Forms\GridField\GridField; +use SilverStripe\ORM\DataObject; use SilverStripe\ORM\SiteTree; use SilverStripe\ORM\DataExtension; use SilverStripe\ORM\FieldType\DBField; @@ -130,12 +131,58 @@ public function updateCMSFields(FieldList $fields) { $fields->removeByName($name . "ID"); if ($this->owner->ID) { - $tv = new TreeView($name, $section, $this->owner->$name()); + $tv = new TreeView($name, $section, $this->owner->$name); $fields->addFieldToTab("Root.PageSections.{$section}", $tv); } } } + // Add a get method for each page section to the owner + public function allMethodNames($custom = false) + { + $arr = [ + "getAllowedPageElements", + "getPageSectionNames", + "onAfterWrite", + "updateCMSFields", + "allMethodNames", + "RenderPageSection", + "getPublishState" + ]; + + $sections = $this->getPageSectionNames(); + foreach ($sections as $section) { + $arr[] = "PageSection" . $section; + $arr[] = "getPageSection" . $section; + } + + return $arr; + } + + public function __call($method, $arguments) + { + // Check if we're trying to get a page section + if (mb_strpos($method, "getPageSection") === 0) { + $name = mb_substr($method, 3); + $section = $this->owner->$name(); + // If we have a page section we're good + if ($section && $section->ID) { + return $section; + } + + // Try restoring the section from the history + $archived = Versioned::get_latest_version(PageSection::class, $this->owner->{$name . "ID"}); + if ($archived && $archived->ID) { + $id = $archived->writeToStage(Versioned::DRAFT, true); + return DataObject::get_by_id(PageSection::class, $id, false); + } + + throw new \Error("Could not restore PageSection"); + } else { + throw new \Error("Unknown method $method on PageSectionsExtension"); + } + } + /** * Renders the PageSection of this page. * @param string $name The name of the PageSection to render From a3508b5105c3ab672b068cf7df9c7ceaf9861ba8 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Thu, 11 Oct 2018 16:59:23 +0200 Subject: [PATCH 43/82] fix(page-section): Fix switching parent type causing section disconnect --- src/PageSectionsExtension.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index a64d323..7dff40b 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -167,12 +167,21 @@ public function __call($method, $arguments) $section = $this->owner->$name(); // If we have a page section we're good if ($section && $section->ID) { + if ($section->__ParentClass != $this->owner->ClassName || $section->__ParentID != $this->owner->ID) { + $section->__ParentClass = $this->owner->ClassName; + $section->__ParentID = $this->owner->ID; + $section->write(); + } return $section; } // Try restoring the section from the history $archived = Versioned::get_latest_version(PageSection::class, $this->owner->{$name . "ID"}); if ($archived && $archived->ID) { + // Update the back references + $archived->__ParentClass = $this->owner->ClassName; + $archived->__ParentID = $this->owner->ID; + // Save a copy in draft $id = $archived->writeToStage(Versioned::DRAFT, true); return DataObject::get_by_id(PageSection::class, $id, false); } From 55986b127dcf6475aa6daab479e17c1bf67ecce6 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Fri, 12 Oct 2018 14:40:16 +0200 Subject: [PATCH 44/82] fix(duplicate): Fix duplication not working properly --- src/PageSectionsExtension.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 7dff40b..4e76b55 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -27,7 +27,7 @@ public static function get_extra_config($class = null, $extensionClass = null) { $has_one = []; $owns = []; - $cascade_deletes = []; + $cascades = []; // Get all the sections that should be added $sections = Config::inst()->get($class, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); @@ -40,7 +40,7 @@ public static function get_extra_config($class = null, $extensionClass = null) $has_one[$name] = PageSection::class; $owns[] = $name; - $cascade_deletes[] = $name; + $cascades[] = $name; // Add the inverse relation to the PageElement class /*Config::inst()->update(PageElement::class, "versioned_belongs_many_many", array( @@ -53,7 +53,8 @@ public static function get_extra_config($class = null, $extensionClass = null) "db" => ["__PageSectionCounter" => "Int"], "has_one" => $has_one, "owns" => $owns, - "cascade_deletes" => $cascade_deletes, + "cascade_deletes" => $cascades, + "cascade_duplicates" => $cascades, ]; } From 313b3ea1a6c5f9195c6dd62aca7ed860b5a9d9f9 Mon Sep 17 00:00:00 2001 From: Felix Eggmann Date: Sat, 13 Oct 2018 13:33:48 +0200 Subject: [PATCH 45/82] fix(duplicate): remove cascade duplicates to avoid conflicts in database --- src/PageSectionsExtension.php | 378 +++++++++++++++++----------------- 1 file changed, 189 insertions(+), 189 deletions(-) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 4e76b55..4cdf3ee 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -22,196 +22,196 @@ class PageSectionsExtension extends DataExtension { - // Generate the needed relations on the class - public static function get_extra_config($class = null, $extensionClass = null) - { - $has_one = []; - $owns = []; - $cascades = []; - - // Get all the sections that should be added - $sections = Config::inst()->get($class, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); - if (!$sections) { - $sections = ["Main"]; - } - - foreach ($sections as $section) { - $name = "PageSection".$section; - $has_one[$name] = PageSection::class; - - $owns[] = $name; - $cascades[] = $name; - - // Add the inverse relation to the PageElement class - /*Config::inst()->update(PageElement::class, "versioned_belongs_many_many", array( - $class . "_" . $name => $class . "." . $name - ));*/ - } - - // Create the relations for our sections - return [ - "db" => ["__PageSectionCounter" => "Int"], - "has_one" => $has_one, - "owns" => $owns, - "cascade_deletes" => $cascades, - "cascade_duplicates" => $cascades, - ]; - } - - /** - * The classes of allowed child elements - * - * Gets a list of classnames which are valid child elements of this PageSection. - * @param string $section The section for which to get the allowed child classes. - * @return string[] - */ - public function getAllowedPageElements($section = "Main") - { - $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); - $classes = array_diff($classes, [PageElement::class]); - $ret = []; - foreach($classes as $class) { + // Generate the needed relations on the class + public static function get_extra_config($class = null, $extensionClass = null) + { + $has_one = []; + $owns = []; + $cascades = []; + + // Get all the sections that should be added + $sections = Config::inst()->get($class, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); + if (!$sections) { + $sections = ["Main"]; + } + + foreach ($sections as $section) { + $name = "PageSection".$section; + $has_one[$name] = PageSection::class; + + $owns[] = $name; + $cascades[] = $name; + + // Add the inverse relation to the PageElement class + /*Config::inst()->update(PageElement::class, "versioned_belongs_many_many", array( + $class . "_" . $name => $class . "." . $name + ));*/ + } + + // Create the relations for our sections + return [ + "db" => ["__PageSectionCounter" => "Int"], + "has_one" => $has_one, + "owns" => $owns, + "cascade_deletes" => $cascades, + //"cascade_duplicates" => $cascades, + ]; + } + + /** + * The classes of allowed child elements + * + * Gets a list of classnames which are valid child elements of this PageSection. + * @param string $section The section for which to get the allowed child classes. + * @return string[] + */ + public function getAllowedPageElements($section = "Main") + { + $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); + $classes = array_diff($classes, [PageElement::class]); + $ret = []; + foreach ($classes as $class) { if ($class::$canBeRoot) $ret[] = $class; - } - return $ret; - } - - /** - * Gets a list of the PageSection names of this page. - * @return string[] - */ - public function getPageSectionNames() - { - $sections = Config::inst()->get($this->owner->ClassName, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); - if (!$sections) { - $sections = ["Main"]; - } - return $sections; - } - - public function onAfterWrite() - { - parent::onAfterWrite(); - - if ($this->owner->ID && !$this->owner->__rewrite) { - $sections = $this->getPageSectionNames(); - - $new = false; - foreach ($sections as $sectionName) { - $name = "PageSection".$sectionName."ID"; - - // Create a page section if we don't have one yet - if (!$this->owner->$name) { - $section = PageSection::create(); - $section->__Name = $sectionName; - $section->__ParentID = $this->owner->ID; - $section->__ParentClass = $this->owner->ClassName; - $section->__isNew = true; - $section->write(); - $this->owner->$name = $section->ID; - $new = true; - } - } - - if ($new) { - $this->owner->__rewrite = true; - $this->owner->write(); - } - } - } + } + return $ret; + } + + /** + * Gets a list of the PageSection names of this page. + * @return string[] + */ + public function getPageSectionNames() + { + $sections = Config::inst()->get($this->owner->ClassName, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); + if (!$sections) { + $sections = ["Main"]; + } + return $sections; + } + + public function onAfterWrite() + { + parent::onAfterWrite(); + + if ($this->owner->ID && !$this->owner->__rewrite) { + $sections = $this->getPageSectionNames(); + + $new = false; + foreach ($sections as $sectionName) { + $name = "PageSection".$sectionName."ID"; + + // Create a page section if we don't have one yet + if (!$this->owner->$name) { + $section = PageSection::create(); + $section->__Name = $sectionName; + $section->__ParentID = $this->owner->ID; + $section->__ParentClass = $this->owner->ClassName; + $section->__isNew = true; + $section->write(); + $this->owner->$name = $section->ID; + $new = true; + } + } + + if ($new) { + $this->owner->__rewrite = true; + $this->owner->write(); + } + } + } public function updateCMSFields(FieldList $fields) { - $sections = $this->getPageSectionNames(); - - $fields->removeByName("__PageSectionCounter"); - - foreach ($sections as $section) { - $name = "PageSection".$section; - - $fields->removeByName($name); - $fields->removeByName($name . "ID"); - - if ($this->owner->ID) { - $tv = new TreeView($name, $section, $this->owner->$name); - $fields->addFieldToTab("Root.PageSections.{$section}", $tv); - } - } - } - - // Add a get method for each page section to the owner - public function allMethodNames($custom = false) - { - $arr = [ - "getAllowedPageElements", - "getPageSectionNames", - "onAfterWrite", - "updateCMSFields", - "allMethodNames", - "RenderPageSection", - "getPublishState" - ]; - - $sections = $this->getPageSectionNames(); - foreach ($sections as $section) { - $arr[] = "PageSection" . $section; - $arr[] = "getPageSection" . $section; - } - - return $arr; - } - - public function __call($method, $arguments) - { - // Check if we're trying to get a page section - if (mb_strpos($method, "getPageSection") === 0) { - $name = mb_substr($method, 3); - $section = $this->owner->$name(); - // If we have a page section we're good - if ($section && $section->ID) { - if ($section->__ParentClass != $this->owner->ClassName || $section->__ParentID != $this->owner->ID) { - $section->__ParentClass = $this->owner->ClassName; - $section->__ParentID = $this->owner->ID; - $section->write(); - } - return $section; - } - - // Try restoring the section from the history - $archived = Versioned::get_latest_version(PageSection::class, $this->owner->{$name . "ID"}); - if ($archived && $archived->ID) { - // Update the back references - $archived->__ParentClass = $this->owner->ClassName; - $archived->__ParentID = $this->owner->ID; - // Save a copy in draft - $id = $archived->writeToStage(Versioned::DRAFT, true); - return DataObject::get_by_id(PageSection::class, $id, false); - } - - throw new \Error("Could not restore PageSection"); - } else { - throw new \Error("Unknown method $method on PageSectionsExtension"); - } - } - - /** - * Renders the PageSection of this page. - * @param string $name The name of the PageSection to render - */ - public function RenderPageSection($name = "Main") - { - $elements = $this->owner->{"PageSection" . $name}()->Elements()->Sort("SortOrder"); - return $this->owner->renderWith( - "RenderChildren", - ["Elements" => $elements, "ParentList" => strval($this->owner->ID)] - ); - } - - /** - * Gets the published state of the page this PageSection belongs to. - * @return \SilverStripe\ORM\FieldType\DBField - */ - public function getPublishState() - { - return DBField::create_field("HTMLText", $this->owner->latestPublished() ? "Published" : "Draft"); - } + $sections = $this->getPageSectionNames(); + + $fields->removeByName("__PageSectionCounter"); + + foreach ($sections as $section) { + $name = "PageSection".$section; + + $fields->removeByName($name); + $fields->removeByName($name . "ID"); + + if ($this->owner->ID) { + $tv = new TreeView($name, $section, $this->owner->$name); + $fields->addFieldToTab("Root.PageSections.{$section}", $tv); + } + } + } + + // Add a get method for each page section to the owner + public function allMethodNames($custom = false) + { + $arr = [ + "getAllowedPageElements", + "getPageSectionNames", + "onAfterWrite", + "updateCMSFields", + "allMethodNames", + "RenderPageSection", + "getPublishState" + ]; + + $sections = $this->getPageSectionNames(); + foreach ($sections as $section) { + $arr[] = "PageSection" . $section; + $arr[] = "getPageSection" . $section; + } + + return $arr; + } + + public function __call($method, $arguments) + { + // Check if we're trying to get a page section + if (mb_strpos($method, "getPageSection") === 0) { + $name = mb_substr($method, 3); + $section = $this->owner->$name(); + // If we have a page section we're good + if ($section && $section->ID) { + if ($section->__ParentClass != $this->owner->ClassName || $section->__ParentID != $this->owner->ID) { + $section->__ParentClass = $this->owner->ClassName; + $section->__ParentID = $this->owner->ID; + $section->write(); + } + return $section; + } + + // Try restoring the section from the history + $archived = Versioned::get_latest_version(PageSection::class, $this->owner->{$name . "ID"}); + if ($archived && $archived->ID) { + // Update the back references + $archived->__ParentClass = $this->owner->ClassName; + $archived->__ParentID = $this->owner->ID; + // Save a copy in draft + $id = $archived->writeToStage(Versioned::DRAFT, true); + return DataObject::get_by_id(PageSection::class, $id, false); + } + + throw new \Error("Could not restore PageSection"); + } else { + throw new \Error("Unknown method $method on PageSectionsExtension"); + } + } + + /** + * Renders the PageSection of this page. + * @param string $name The name of the PageSection to render + */ + public function RenderPageSection($name = "Main") + { + $elements = $this->owner->{"PageSection" . $name}()->Elements()->Sort("SortOrder"); + return $this->owner->renderWith( + "RenderChildren", + ["Elements" => $elements, "ParentList" => strval($this->owner->ID)] + ); + } + + /** + * Gets the published state of the page this PageSection belongs to. + * @return \SilverStripe\ORM\FieldType\DBField + */ + public function getPublishState() + { + return DBField::create_field("HTMLText", $this->owner->latestPublished() ? "Published" : "Draft"); + } } From 7a25303c3e0fa9a30b34b9b7832b7b2cbf1666a1 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Mon, 15 Oct 2018 12:35:28 +0200 Subject: [PATCH 46/82] fix(page-section): Properly duplicate page sections --- src/PageElement.php | 2 +- src/PageSectionsExtension.php | 383 +++++++++++++++++----------------- 2 files changed, 195 insertions(+), 190 deletions(-) diff --git a/src/PageElement.php b/src/PageElement.php index 75a3a78..d51ebdc 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -195,7 +195,7 @@ public function getAllSectionParents() { $parents = ArrayList::create(); foreach ($this->PageSections() as $section) { - $p = $section->Parent()->duplicate(false); + $p = $section->Parent(); $stage = Versioned::get_stage(); Versioned::set_stage(Versioned::LIVE); $pubSection = DataObject::get_by_id($section->ClassName, $section->ID); diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 4cdf3ee..a060f95 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -22,196 +22,201 @@ class PageSectionsExtension extends DataExtension { - // Generate the needed relations on the class - public static function get_extra_config($class = null, $extensionClass = null) - { - $has_one = []; - $owns = []; - $cascades = []; - - // Get all the sections that should be added - $sections = Config::inst()->get($class, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); - if (!$sections) { - $sections = ["Main"]; - } - - foreach ($sections as $section) { - $name = "PageSection".$section; - $has_one[$name] = PageSection::class; - - $owns[] = $name; - $cascades[] = $name; - - // Add the inverse relation to the PageElement class - /*Config::inst()->update(PageElement::class, "versioned_belongs_many_many", array( - $class . "_" . $name => $class . "." . $name - ));*/ - } - - // Create the relations for our sections - return [ - "db" => ["__PageSectionCounter" => "Int"], - "has_one" => $has_one, - "owns" => $owns, - "cascade_deletes" => $cascades, - //"cascade_duplicates" => $cascades, - ]; - } - - /** - * The classes of allowed child elements - * - * Gets a list of classnames which are valid child elements of this PageSection. - * @param string $section The section for which to get the allowed child classes. - * @return string[] - */ - public function getAllowedPageElements($section = "Main") - { - $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); - $classes = array_diff($classes, [PageElement::class]); - $ret = []; - foreach ($classes as $class) { + // Generate the needed relations on the class + public static function get_extra_config($class = null, $extensionClass = null) + { + $has_one = []; + $names = []; + + // Get all the sections that should be added + $sections = Config::inst()->get($class, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); + if (!$sections) { + $sections = ["Main"]; + } + + foreach ($sections as $section) { + $name = "PageSection".$section; + $has_one[$name] = PageSection::class; + + $names[] = $name; + + // Add the inverse relation to the PageElement class + /*Config::inst()->update(PageElement::class, "versioned_belongs_many_many", array( + $class . "_" . $name => $class . "." . $name + ));*/ + } + + // Create the relations for our sections + return [ + "db" => ["__PageSectionCounter" => "Int"], + "has_one" => $has_one, + "owns" => $names, + "cascade_deletes" => $names, + "cascade_duplicates" => $names, + ]; + } + + /** + * The classes of allowed child elements + * + * Gets a list of classnames which are valid child elements of this PageSection. + * @param string $section The section for which to get the allowed child classes. + * @return string[] + */ + public function getAllowedPageElements($section = "Main") + { + $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); + $classes = array_diff($classes, [PageElement::class]); + $ret = []; + foreach($classes as $class) { if ($class::$canBeRoot) $ret[] = $class; - } - return $ret; - } - - /** - * Gets a list of the PageSection names of this page. - * @return string[] - */ - public function getPageSectionNames() - { - $sections = Config::inst()->get($this->owner->ClassName, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); - if (!$sections) { - $sections = ["Main"]; - } - return $sections; - } - - public function onAfterWrite() - { - parent::onAfterWrite(); - - if ($this->owner->ID && !$this->owner->__rewrite) { - $sections = $this->getPageSectionNames(); - - $new = false; - foreach ($sections as $sectionName) { - $name = "PageSection".$sectionName."ID"; - - // Create a page section if we don't have one yet - if (!$this->owner->$name) { - $section = PageSection::create(); - $section->__Name = $sectionName; - $section->__ParentID = $this->owner->ID; - $section->__ParentClass = $this->owner->ClassName; - $section->__isNew = true; - $section->write(); - $this->owner->$name = $section->ID; - $new = true; - } - } - - if ($new) { - $this->owner->__rewrite = true; - $this->owner->write(); - } - } - } + } + return $ret; + } + + /** + * Gets a list of the PageSection names of this page. + * @return string[] + */ + public function getPageSectionNames() + { + $sections = Config::inst()->get($this->owner->ClassName, "page_sections", Config::EXCLUDE_EXTRA_SOURCES); + if (!$sections) { + $sections = ["Main"]; + } + return $sections; + } + + public function onAfterWrite() + { + parent::onAfterWrite(); + + if ($this->owner->ID && !$this->owner->__rewrite) { + $sections = $this->getPageSectionNames(); + + foreach ($sections as $sectionName) { + $name = "PageSection".$sectionName; + + if (!$this->owner->{$name . "ID"}) { + // Restore or create a page section if we don't have one yet + $this->restoreOrCreate($sectionName); + } + } + } + } public function updateCMSFields(FieldList $fields) { - $sections = $this->getPageSectionNames(); - - $fields->removeByName("__PageSectionCounter"); - - foreach ($sections as $section) { - $name = "PageSection".$section; - - $fields->removeByName($name); - $fields->removeByName($name . "ID"); - - if ($this->owner->ID) { - $tv = new TreeView($name, $section, $this->owner->$name); - $fields->addFieldToTab("Root.PageSections.{$section}", $tv); - } - } - } - - // Add a get method for each page section to the owner - public function allMethodNames($custom = false) - { - $arr = [ - "getAllowedPageElements", - "getPageSectionNames", - "onAfterWrite", - "updateCMSFields", - "allMethodNames", - "RenderPageSection", - "getPublishState" - ]; - - $sections = $this->getPageSectionNames(); - foreach ($sections as $section) { - $arr[] = "PageSection" . $section; - $arr[] = "getPageSection" . $section; - } - - return $arr; - } - - public function __call($method, $arguments) - { - // Check if we're trying to get a page section - if (mb_strpos($method, "getPageSection") === 0) { - $name = mb_substr($method, 3); - $section = $this->owner->$name(); - // If we have a page section we're good - if ($section && $section->ID) { - if ($section->__ParentClass != $this->owner->ClassName || $section->__ParentID != $this->owner->ID) { - $section->__ParentClass = $this->owner->ClassName; - $section->__ParentID = $this->owner->ID; - $section->write(); - } - return $section; - } - - // Try restoring the section from the history - $archived = Versioned::get_latest_version(PageSection::class, $this->owner->{$name . "ID"}); - if ($archived && $archived->ID) { - // Update the back references - $archived->__ParentClass = $this->owner->ClassName; - $archived->__ParentID = $this->owner->ID; - // Save a copy in draft - $id = $archived->writeToStage(Versioned::DRAFT, true); - return DataObject::get_by_id(PageSection::class, $id, false); - } - - throw new \Error("Could not restore PageSection"); - } else { - throw new \Error("Unknown method $method on PageSectionsExtension"); - } - } - - /** - * Renders the PageSection of this page. - * @param string $name The name of the PageSection to render - */ - public function RenderPageSection($name = "Main") - { - $elements = $this->owner->{"PageSection" . $name}()->Elements()->Sort("SortOrder"); - return $this->owner->renderWith( - "RenderChildren", - ["Elements" => $elements, "ParentList" => strval($this->owner->ID)] - ); - } - - /** - * Gets the published state of the page this PageSection belongs to. - * @return \SilverStripe\ORM\FieldType\DBField - */ - public function getPublishState() - { - return DBField::create_field("HTMLText", $this->owner->latestPublished() ? "Published" : "Draft"); - } + $sections = $this->getPageSectionNames(); + + $fields->removeByName("__PageSectionCounter"); + + foreach ($sections as $section) { + $name = "PageSection".$section; + + $fields->removeByName($name); + $fields->removeByName($name . "ID"); + + if ($this->owner->ID) { + $tv = new TreeView($name, $section, $this->owner->$name); + $fields->addFieldToTab("Root.PageSections.{$section}", $tv); + } + } + } + + // Add a get method for each page section to the owner + public function allMethodNames($custom = false) + { + $arr = [ + "getAllowedPageElements", + "getPageSectionNames", + "onAfterWrite", + "updateCMSFields", + "allMethodNames", + "RenderPageSection", + "getPublishState" + ]; + + $sections = $this->getPageSectionNames(); + foreach ($sections as $section) { + $arr[] = "PageSection" . $section; + $arr[] = "getPageSection" . $section; + } + + return $arr; + } + + public function __call($method, $arguments) + { + //var_dump($method); + + // Check if we're trying to get a page section + if (mb_strpos($method, "getPageSection") === 0) { + $name = mb_substr($method, 3); + $section = $this->owner->$name(); + + // If we have a page section we're good + if ($section && $section->ID) { + if ($section->__ParentClass != $this->owner->ClassName || $section->__ParentID != $this->owner->ID) { + $section->__ParentClass = $this->owner->ClassName; + $section->__ParentID = $this->owner->ID; + $section->write(); + } + return $section; + } + + return $this->restoreOrCreate($name); + } else { + throw new \Error("Unknown method $method on PageSectionsExtension"); + } + } + + private function restoreOrCreate($name) { + // Try restoring the section from the history + $archived = Versioned::get_latest_version(PageSection::class, $this->owner->{$name . "ID"}); + if ($archived && $archived->ID) { + // Update the back references + $archived->ID = $this->owner->{$name . "ID"}; + $archived->__ParentClass = $this->owner->ClassName; + $archived->__ParentID = $this->owner->ID; + // Save a copy in draft + $id = $archived->writeToStage(Versioned::DRAFT, true); + + $this->owner->flushCache(true); + + return DataObject::get_by_id(PageSection::class, $id, false); + } else { + $section = PageSection::create(); + $section->__Name = $name; + $section->__ParentID = $this->owner->ID; + $section->__ParentClass = $this->owner->ClassName; + $section->__isNew = true; + $section->write(); + $this->owner->{$name . "ID"} = $section->ID; + $this->owner->write(); + + return $section; + } + } + + /** + * Renders the PageSection of this page. + * @param string $name The name of the PageSection to render + */ + public function RenderPageSection($name = "Main") + { + $elements = $this->owner->{"PageSection" . $name}()->Elements()->Sort("SortOrder"); + return $this->owner->renderWith( + "RenderChildren", + ["Elements" => $elements, "ParentList" => strval($this->owner->ID)] + ); + } + + /** + * Gets the published state of the page this PageSection belongs to. + * @return \SilverStripe\ORM\FieldType\DBField + */ + public function getPublishState() + { + return DBField::create_field("HTMLText", $this->owner->latestPublished() ? "Published" : "Draft"); + } } From 7661df367e8d453037286057a912659903d2442d Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Mon, 15 Oct 2018 12:46:27 +0200 Subject: [PATCH 47/82] fix(archive): Fix restoring archived versions not working --- src/PageSectionsExtension.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index a060f95..fad5a74 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -105,6 +105,16 @@ public function onAfterWrite() } } + public function onBeforeArchive() + { + $sections = $this->getPageSectionNames(); + + foreach ($sections as $sectionName) { + $name = "PageSection".$sectionName; + $this->owner->{$name . "ID"} = 0; + } + } + public function updateCMSFields(FieldList $fields) { $sections = $this->getPageSectionNames(); From fdf2e25485481bdc5951bba7431631d26ee33a95 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Mon, 15 Oct 2018 13:04:40 +0200 Subject: [PATCH 48/82] fix(page-element): Fix listing deleted parent sections --- src/PageElement.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/PageElement.php b/src/PageElement.php index d51ebdc..ad8075f 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -196,6 +196,12 @@ public function getAllSectionParents() { foreach ($this->PageSections() as $section) { $p = $section->Parent(); + if (!$p->ID) { + // If our parent doesn't have an ID it's probably deleted/archived, so we just don't list it. + // TODO: Improve this to list the parent as "archived" + continue; + } + $stage = Versioned::get_stage(); Versioned::set_stage(Versioned::LIVE); $pubSection = DataObject::get_by_id($section->ClassName, $section->ID); From a6c01af85a2ab1ee0be5bfbcf948e21a1c54c3c5 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Mon, 15 Oct 2018 13:05:32 +0200 Subject: [PATCH 49/82] fix(page-element): Fix empty parent sections --- src/PageElement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PageElement.php b/src/PageElement.php index ad8075f..e834740 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -196,7 +196,7 @@ public function getAllSectionParents() { foreach ($this->PageSections() as $section) { $p = $section->Parent(); - if (!$p->ID) { + if (!$p || !$p->ID) { // If our parent doesn't have an ID it's probably deleted/archived, so we just don't list it. // TODO: Improve this to list the parent as "archived" continue; From 24cc58ea69ac7d01b01d9b49519495c83b1df788 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Mon, 15 Oct 2018 13:54:13 +0200 Subject: [PATCH 50/82] fix(page-element): Show archived parents --- src/PageElement.php | 8 ++++++-- src/PageSectionsExtension.php | 8 +++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/PageElement.php b/src/PageElement.php index e834740..783247c 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -197,8 +197,12 @@ public function getAllSectionParents() { foreach ($this->PageSections() as $section) { $p = $section->Parent(); if (!$p || !$p->ID) { - // If our parent doesn't have an ID it's probably deleted/archived, so we just don't list it. - // TODO: Improve this to list the parent as "archived" + // If our parent doesn't have an ID it's probably deleted/archived. + $p = $archived = Versioned::get_latest_version($section->__ParentClass, $section->__ParentID); + $p->__PageSection = $section; + $p->__PageElementVersion = $section->Elements()->filter("ID", $this->ID)->First()->Version; + $p->__PageElementPublishedVersion = "Not published"; + $parents->add($p); continue; } diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index fad5a74..8967d92 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -227,6 +227,12 @@ public function RenderPageSection($name = "Main") */ public function getPublishState() { - return DBField::create_field("HTMLText", $this->owner->latestPublished() ? "Published" : "Draft"); + $stage = "Draft"; + if ($this->owner->isPublished()) { + $stage = "Published"; + } else if ($this->owner->isArchived()) { + $stage = "Archived"; + } + return DBField::create_field("HTMLText", $stage); } } From c50697a946d3fefa14ee9220357d4a4fabe5a04d Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Wed, 24 Oct 2018 11:52:44 +0200 Subject: [PATCH 51/82] fix(restoreOrCreate): Fix page section not being created properly --- src/PageSectionsExtension.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 8967d92..90c61ae 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -180,7 +180,9 @@ public function __call($method, $arguments) } } - private function restoreOrCreate($name) { + private function restoreOrCreate($sectionName) { + $name = "PageSection" . $sectionName; + // Try restoring the section from the history $archived = Versioned::get_latest_version(PageSection::class, $this->owner->{$name . "ID"}); if ($archived && $archived->ID) { @@ -196,11 +198,12 @@ private function restoreOrCreate($name) { return DataObject::get_by_id(PageSection::class, $id, false); } else { $section = PageSection::create(); - $section->__Name = $name; + $section->__Name = $sectionName; $section->__ParentID = $this->owner->ID; $section->__ParentClass = $this->owner->ClassName; $section->__isNew = true; $section->write(); + $this->owner->{$name . "ID"} = $section->ID; $this->owner->write(); From 0f42fd9fb86dd6afce52bbacc47713f0af73ae5e Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Wed, 24 Oct 2018 12:07:28 +0200 Subject: [PATCH 52/82] fix(restoreOrCreate): More fixes when trying to restore a PageSection --- src/PageSectionsExtension.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 90c61ae..95ab2f1 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -120,15 +120,15 @@ public function updateCMSFields(FieldList $fields) { $fields->removeByName("__PageSectionCounter"); - foreach ($sections as $section) { - $name = "PageSection".$section; + foreach ($sections as $sectionName) { + $name = "PageSection".$sectionName; $fields->removeByName($name); $fields->removeByName($name . "ID"); if ($this->owner->ID) { - $tv = new TreeView($name, $section, $this->owner->$name); - $fields->addFieldToTab("Root.PageSections.{$section}", $tv); + $tv = new TreeView($name, $sectionName, $this->owner->$name); + $fields->addFieldToTab("Root.PageSections.{$sectionName}", $tv); } } } @@ -162,6 +162,7 @@ public function __call($method, $arguments) // Check if we're trying to get a page section if (mb_strpos($method, "getPageSection") === 0) { $name = mb_substr($method, 3); + $sectionName = mb_substr($name, 11); $section = $this->owner->$name(); // If we have a page section we're good @@ -174,13 +175,17 @@ public function __call($method, $arguments) return $section; } - return $this->restoreOrCreate($name); + return $this->restoreOrCreate($sectionName); } else { throw new \Error("Unknown method $method on PageSectionsExtension"); } } private function restoreOrCreate($sectionName) { + if (mb_strpos($sectionName, "PageSection") !== false) { + throw new \Error("PageSection name should not contain 'PageSection' when restoring or creating!"); + } + $name = "PageSection" . $sectionName; // Try restoring the section from the history From 1c82636928cf84ced9e424f201a714045354fbda Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Wed, 24 Oct 2018 12:18:08 +0200 Subject: [PATCH 53/82] fix(archived): Fix for archived PageSections --- src/PageSectionsExtension.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 95ab2f1..acfea82 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -91,7 +91,7 @@ public function onAfterWrite() { parent::onAfterWrite(); - if ($this->owner->ID && !$this->owner->__rewrite) { + if ($this->owner->ID && !$this->owner->__archived) { $sections = $this->getPageSectionNames(); foreach ($sections as $sectionName) { @@ -109,6 +109,7 @@ public function onBeforeArchive() { $sections = $this->getPageSectionNames(); + $this->owner->__archived = true; foreach ($sections as $sectionName) { $name = "PageSection".$sectionName; $this->owner->{$name . "ID"} = 0; From de0158bd229dbbf4d5b163f34aa4b5bba9967283 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Wed, 24 Oct 2018 12:24:03 +0200 Subject: [PATCH 54/82] fix(archive): Better archive fix --- src/PageSectionsExtension.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index acfea82..cf10cd0 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -91,7 +91,7 @@ public function onAfterWrite() { parent::onAfterWrite(); - if ($this->owner->ID && !$this->owner->__archived) { + if ($this->owner->ID) { $sections = $this->getPageSectionNames(); foreach ($sections as $sectionName) { @@ -109,7 +109,6 @@ public function onBeforeArchive() { $sections = $this->getPageSectionNames(); - $this->owner->__archived = true; foreach ($sections as $sectionName) { $name = "PageSection".$sectionName; $this->owner->{$name . "ID"} = 0; @@ -176,6 +175,11 @@ public function __call($method, $arguments) return $section; } + // Fix for archived errors + if ($this->owner->isArchived()) { + return new PageSection(); + } + return $this->restoreOrCreate($sectionName); } else { throw new \Error("Unknown method $method on PageSectionsExtension"); From 802248602c931297a3ad4ca651146c726253542f Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Fri, 26 Oct 2018 16:14:43 +0200 Subject: [PATCH 55/82] fix(dependencies): Remove betterbuttons dependency --- composer.json | 3 +-- src/PageElement.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 4bc3698..16b73a4 100755 --- a/composer.json +++ b/composer.json @@ -19,8 +19,7 @@ }], "require": { "silverstripe/framework": "^4.0.1", - "symbiote/silverstripe-gridfieldextensions": "^3", - "unclecheese/betterbuttons": "dev-feature/ss4-upgrade" + "symbiote/silverstripe-gridfieldextensions": "^3" }, "extra": { "installer-name": "pagesections", diff --git a/src/PageElement.php b/src/PageElement.php index 783247c..f8552ab 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -59,7 +59,7 @@ public function canEdit($member = null) } public function canDelete($member = null) { - return true; + return false; } public function canCreate($member = null, $context = []) { From 2e27ad8202e47c072447039e828f1d4400bd9ab0 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Fri, 26 Oct 2018 16:18:21 +0200 Subject: [PATCH 56/82] fix(betterbuttons): Remove remaining references --- src/PageElement.php | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/src/PageElement.php b/src/PageElement.php index f8552ab..a305443 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -22,10 +22,6 @@ use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass; use Symbiote\GridFieldExtensions\GridFieldAddExistingSearchButton; -use UncleCheese\BetterButtons\Actions\PrevNext; -use UncleCheese\BetterButtons\Actions\CustomAction; -use UncleCheese\BetterButtons\Buttons\Save; -use UncleCheese\BetterButtons\Buttons\SaveAndClose; use SilverStripe\Forms\Tab; class PageElement extends DataObject @@ -104,10 +100,6 @@ public function canCreate($member = null, $context = []) "ID", ]; - private static $better_buttons_actions = array ( - 'publishOnAllSectionParents', - ); - // Returns all page element classes, without the base class public static function getAllPageElementClasses() { @@ -326,29 +318,4 @@ public function replaceDefaultButtons() { return true; } - - public function getBetterButtonsUtils() - { - $fieldList = FieldList::create([ - PrevNext::create(), - ]); - return $fieldList; - } - - public function getBetterButtonsActions() - { - $actions = FieldList::create([ - SaveAndClose::create(), - CustomAction::create('publishOnAllSectionParents', 'Publish everywhere') - ->setRedirectType(CustomAction::REFRESH) - ]); - return $actions; - } - - public function publishOnAllSectionParents() { - foreach ($this->getAllSectionParents() as $parent) { - $parent->publish(Versioned::get_stage(), Versioned::LIVE); - } - return 'Published on all section parents'; - } } From 883e4935bb3e2b974263512813c203fddbd681ac Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Fri, 21 Dec 2018 16:18:20 +0100 Subject: [PATCH 57/82] fix(treeview): Fix setting value after constructor call not working --- src/TreeView.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/TreeView.php b/src/TreeView.php index cfd12c7..71998c0 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -53,13 +53,20 @@ public function __construct($name, $title = null, $section = null) $this->section = $section; $this->context = singleton(PageElement::class)->getDefaultSearchContext(); - // Open default elements - $this->opens = new \stdClass(); - foreach ($this->getItems() as $item) { - $this->openRecursive($item); + if ($section) { + // Open default elements + $this->opens = new \stdClass(); + foreach ($this->getItems() as $item) { + $this->openRecursive($item); + } } } + public function setValue($value, $data = null) { + $this->section = $value; + return $this; + } + /** * Saves this TreeView into the specified record * @@ -874,3 +881,5 @@ private function closeItem($path) unset($opens->{$path[count($path) - 1]}); } } + +class TreeView_Readonly extends TreeView {} From e46975414faffa3653928eabedb70993fb94d170 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Wed, 27 Feb 2019 18:59:15 +0100 Subject: [PATCH 58/82] fix(page-section): Fix parent and name --- src/PageSection.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/PageSection.php b/src/PageSection.php index 68da74f..3b19cab 100644 --- a/src/PageSection.php +++ b/src/PageSection.php @@ -45,7 +45,11 @@ class PageSection extends DataObject ]; public function Parent() { - return DataObject::get_by_id($this->__ParentClass, $this->__ParentID); + $parent = DataObject::get_by_id($this->__ParentClass, $this->__ParentID); + if ($parent == null) { + $parent = Versioned::get_latest_version($this->__ParentClass, $this->__ParentID); + } + return $parent; } public function onBeforeWrite() { @@ -78,6 +82,10 @@ public function forTemplate() * @return string */ public function getName() { + if ($this->__Name) { + return $this->__Name; + } + $parent = $this->Parent(); // TODO: Find out why this happens if (!method_exists($parent, "getPageSectionNames")) { @@ -98,7 +106,7 @@ public function getName() { * @param string $section The section for which to get the allowed child classes. * @return string[] */ - public function getAllowedPageElements($section = "Main") { - return $this->Parent()->getAllowedPageElements($section); + public function getAllowedPageElements() { + return $this->Parent()->getAllowedPageElements($this->__Name); } } From eb7afc08203360e6b11bd116966082e24ac05203 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Tue, 12 Mar 2019 16:58:33 +0100 Subject: [PATCH 59/82] fix(archive): Fixes for deleting and archive --- javascript/TreeView.js | 245 ++++++++++-------- src/PageElement.php | 79 +++--- src/PageSection.php | 16 +- src/PageSectionsExtension.php | 2 +- src/TreeView.php | 159 ++++++------ .../PageSections/TreeViewPageElement.ss | 2 + 6 files changed, 252 insertions(+), 251 deletions(-) diff --git a/javascript/TreeView.js b/javascript/TreeView.js index 8ff88d3..db8acdd 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -1,23 +1,23 @@ -(function ($) { +(function($) { function TreeViewContextMenu() { - this.createDom = function (id, name) { + this.createDom = function(id, name) { this.$menu = $( "
                                                              " + name + + "' class='treeview-menu' data-id='" + + id + + "'>" ); }; - this.addLabel = function (label) { + this.addLabel = function(label) { this.$menu.append("
                                                            • " + label + '
                                                            • '); }; - this.addItem = function (type, label, onClick = function () {}) { + this.addItem = function(type, label, onClick = function() {}) { var $li = $("
                                                            • " + label + '
                                                            • '); $li.click(onClick); this.$menu.append($li); }; - this.show = function (x, y) { + this.show = function(x, y) { var pos = { top: y, left: x @@ -27,7 +27,7 @@ this.$menu.css(pos); this.$menu.show(); var that = this; - window.requestAnimationFrame(function () { + window.requestAnimationFrame(function() { var wW = $(window).width(); var wH = $(window).height(); var eW = that.$menu.outerWidth(true); @@ -43,14 +43,14 @@ that.$menu.css(pos); }); }; - this.remove = function () { + this.remove = function() { this.$menu.remove(); }; } - $.entwine('ss', function ($) { + $.entwine('ss', function($) { // Hide our custom context menu when not needed - $(document).on('mousedown', function (event) { + $(document).on('mousedown', function(event) { $parents = $(event.target).parents('.treeview-menu'); if ($parents.length == 0) { $('.treeview-menu').remove(); @@ -58,7 +58,7 @@ }); $('.view-detail-dialog .gridfield-better-buttons-prevnext').entwine({ - onclick: function () { + onclick: function() { this.closest('.view-detail-dialog').loadDialog( $.get(this.prop('href')) ); @@ -67,7 +67,7 @@ }); $('.view-detail-dialog .action-detail').entwine({ - onclick: function () { + onclick: function() { const dialog = this.closest('.view-detail-dialog').loadDialog( $.get(this.prop('href')) ); @@ -77,36 +77,39 @@ }); $('.view-detail-dialog form').entwine({ - onsubmit: function () { + onsubmit: function() { var dialog = this.closest('.view-detail-dialog'); const self = this; $.ajax( - $.extend({}, { - headers: { - 'X-Pjax': 'CurrentField' - }, - type: 'POST', - url: this.prop('action'), - dataType: 'html', - data: this.serialize(), - success: function () { - if (self.hasClass('view-detail-form')) { - dialog.data('treeview').reload(); - dialog.dialog('close'); - } else { - if (dialog.data("clickedButton") === 'action_doSaveAndQuit') { - dialog.loadDialog($.get(dialog.data("origUrl"))); + $.extend( + {}, + { + headers: { + 'X-Pjax': 'CurrentField' + }, + type: 'POST', + url: this.prop('action'), + dataType: 'html', + data: this.serialize(), + success: function() { + if (self.hasClass('view-detail-form')) { + dialog.data('treeview').reload(); + dialog.dialog('close'); } else { - const url = self.prop('href') ? - self.prop('href') : - dialog.data('href'); - dialog.loadDialog($.get(url)); + if (dialog.data('clickedButton') === 'action_doSaveAndQuit') { + dialog.loadDialog($.get(dialog.data('origUrl'))); + } else { + const url = self.prop('href') + ? self.prop('href') + : dialog.data('href'); + dialog.loadDialog($.get(url)); + } } } } - }) + ) ); return false; @@ -116,13 +119,13 @@ // Show our search form after opening the search dialog // Show our detail form after opening the detail dialog $('.add-existing-search-dialog-treeview, .view-detail-dialog').entwine({ - loadDialog: function (deferred) { + loadDialog: function(deferred) { var dialog = this.addClass('loading').empty(); - deferred.done(function (data) { + deferred.done(function(data) { dialog.html(data).removeClass('loading'); - dialog.find("button[type=submit]").click(function () { - dialog.data("clickedButton", this.name); + dialog.find('button[type=submit]').click(function() { + dialog.data('clickedButton', this.name); }); }); return this; @@ -130,20 +133,22 @@ }); // Submit our search form to our own endpoint and show the results - $('.add-existing-search-dialog-treeview .add-existing-search-form').entwine({ - onsubmit: function () { - this.closest('.add-existing-search-dialog-treeview').loadDialog( - $.get(this.prop('action'), this.serialize()) - ); - return false; + $('.add-existing-search-dialog-treeview .add-existing-search-form').entwine( + { + onsubmit: function() { + this.closest('.add-existing-search-dialog-treeview').loadDialog( + $.get(this.prop('action'), this.serialize()) + ); + return false; + } } - }); + ); // Allow clicking the elements in the search form $( '.add-existing-search-dialog-treeview .add-existing-search-items .list-group-item-action' ).entwine({ - onclick: function () { + onclick: function() { if (this.children('a').length > 0) { this.children('a') .first() @@ -156,7 +161,7 @@ $( '.add-existing-search-dialog-treeview .add-existing-search-items a' ).entwine({ - onclick: function () { + onclick: function() { var link = this.closest('.add-existing-search-items').data('add-link'); var id = this.data('id'); @@ -164,14 +169,17 @@ .addClass('loading') .empty(); - dialog.data('treeview').reload({ + dialog.data('treeview').reload( + { url: link, - data: [{ - name: 'id', - value: id - }] + data: [ + { + name: 'id', + value: id + } + ] }, - function () { + function() { dialog.dialog('close'); } ); @@ -184,7 +192,7 @@ $( '.add-existing-search-dialog-treeview .add-existing-search-pagination a' ).entwine({ - onclick: function () { + onclick: function() { this.closest('.add-existing-search-dialog-treeview').loadDialog( $.get(this.prop('href')) ); @@ -210,12 +218,13 @@ // Attach data to our tree view $('.treeview-pagesections').entwine({ - addItem: function (parents, itemId, elemType, sort = 99999) { + addItem: function(parents, itemId, elemType, sort = 99999) { var $treeView = $(this); $treeView.reload({ url: $treeView.data('url') + '/add', - data: [{ + data: [ + { name: 'parents', value: parents }, @@ -234,12 +243,13 @@ ] }); }, - removeItem: function (parents, itemId) { + removeItem: function(parents, itemId) { var $treeView = $(this); $treeView.reload({ url: $treeView.data('url') + '/remove', - data: [{ + data: [ + { name: 'parents', value: parents }, @@ -250,12 +260,13 @@ ] }); }, - deleteItem: function (parents, itemId) { + deleteItem: function(parents, itemId) { var $treeView = $(this); $treeView.reload({ url: $treeView.data('url') + '/delete', - data: [{ + data: [ + { name: 'parents', value: parents }, @@ -266,13 +277,13 @@ ] }); }, - onadd: function () { + onadd: function() { var $treeView = $(this); var name = $treeView.data('name'); var url = $treeView.data('url'); // Setup find existing button - $treeView.find('button[name=action_FindExisting]').click(function () { + $treeView.find('button[name=action_FindExisting]').click(function() { var dialog = $('
                                                              ') .appendTo('body') .dialog({ @@ -280,7 +291,7 @@ resizable: false, width: 500, height: 600, - close: function () { + close: function() { $(this) .dialog('destroy') .remove(); @@ -300,7 +311,7 @@ .find( '> .treeview-pagesections__header > .treeview-item-actions .add-button' ) - .click(function (event) { + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -312,8 +323,8 @@ menu.addLabel( ss.i18n._t('PageSections.TreeView.AddAChild', 'Add a child') ); - $.each(elems, function (key, value) { - menu.addItem(key, value, function () { + $.each(elems, function(key, value) { + menu.addItem(key, value, function() { $treeView.addItem([], null, key, 1); menu.remove(); }); @@ -322,7 +333,7 @@ }); // Process items - $treeView.find('.treeview-item').each(function () { + $treeView.find('.treeview-item').each(function() { var $item = $(this); var itemId = $item.data('id'); var parents = $item.data('tree'); @@ -330,13 +341,14 @@ // Open an item button $item .find('> .treeview-item__panel > .treeview-item-flow .tree-button') - .click(function (event) { + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); $treeView.reload({ url: url + '/tree', - data: [{ + data: [ + { name: 'parents', value: parents }, @@ -351,7 +363,7 @@ // Edit button $item .find('> .treeview-item__panel > .treeview-item-flow .edit-button') - .click(function () { + .click(function() { var dialog = $('
                                                              ') .appendTo('body') .dialog({ @@ -359,7 +371,7 @@ resizable: false, width: $(window).width() * 0.9, height: $(window).height() * 0.9, - close: function () { + close: function() { $(this) .dialog('destroy') .remove(); @@ -379,7 +391,7 @@ .find( '> .treeview-item__panel > .treeview-item-actions .add-button' ) - .click(function (event) { + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -391,8 +403,8 @@ menu.addLabel( ss.i18n._t('PageSections.TreeView.AddAChild', 'Add a child') ); - $.each(elems, function (key, value) { - menu.addItem(key, value, function () { + $.each(elems, function(key, value) { + menu.addItem(key, value, function() { $treeView.addItem(parents, itemId, key, 1); menu.remove(); }); @@ -403,7 +415,7 @@ // Add new after item button $item .find('> .treeview-item__post-actions .add-after-button') - .click(function (event) { + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); @@ -419,8 +431,8 @@ ) ); - $.each(elems, function (key, value) { - menu.addItem(key, value, function () { + $.each(elems, function(key, value) { + menu.addItem(key, value, function() { $treeView.addItem( parents.slice(0, parents.length - 1), parents[parents.length - 1], @@ -438,11 +450,15 @@ .find( '> .treeview-item__panel > .treeview-item-flow .delete-button' ) - .click(function (event) { + .click(function(event) { event.preventDefault(); event.stopImmediatePropagation(); $target = $(event.target); + // If we clicked the span get the parent button + if ($target.prop('tagName') === 'SPAN') { + $target = $target.parent(); + } var menu = new TreeViewContextMenu(); menu.createDom(itemId, name); @@ -453,20 +469,20 @@ menu.addItem( '__REMOVE__', ss.i18n._t('PageSections.TreeView.RemoveAChild', 'Remove'), - function () { + function() { $treeView.removeItem(parents, itemId); menu.remove(); } ); - if ($target.data('used-count') < 2) { + if (Number($target.data('used-count')) <= 1) { menu.addItem( '', ss.i18n._t( 'PageSections.TreeView.DeleteAChild', 'Finally delete' ), - function () { + function() { $treeView.deleteItem(parents, itemId); menu.remove(); } @@ -474,12 +490,12 @@ var $li = $( '
                                                            • ' + - ss.i18n._t( - 'PageSections.TreeView.DeleteAChild', - 'Finally delete' - ) + - '
                                                            • ', - function () { + ss.i18n._t( + 'PageSections.TreeView.DeleteAChild', + 'Finally delete' + ) + + '', + function() { $treeView.deleteItem(parents, itemId); menu.remove(); } @@ -504,7 +520,7 @@ tolerance: 'pointer', greedy: true, - helper: function (event) { + helper: function(event) { var $panel = $item.find('> .treeview-item__panel'); var $helper = $("
                                                              "); $helper @@ -527,8 +543,8 @@ return $helper; }, - start: function () { - $('.ui-droppable').each(function () { + start: function() { + $('.ui-droppable').each(function() { var $drop = $(this); var $dropItem = $drop.closest('.treeview-item'); var $parentDropItem = $dropItem @@ -548,7 +564,7 @@ // or a same id if ( $dropItem.parents(".treeview-item[data-id='" + itemId + "']") - .length + .length ) { return; } @@ -559,8 +575,8 @@ if ( ($drop.hasClass('after') || $drop.hasClass('before')) && $dropItem - .siblings(".treeview-item[data-id='" + itemId + "']") - .not($item).length + .siblings(".treeview-item[data-id='" + itemId + "']") + .not($item).length ) { return; } @@ -609,7 +625,7 @@ $drop.show(); }); }, - stop: function (event, ui) { + stop: function(event, ui) { $('.ui-droppable').css('display', ''); // Show the previous elements. If the user made an invalid movement then // we want this to show anyways. If he did something valid the treeview will @@ -625,11 +641,11 @@ }); // Dropping targets - $item.find('.treeview-item-reorder .droppable').each(function () { + $item.find('.treeview-item-reorder .droppable').each(function() { $(this).droppable({ hoverClass: 'state-active', tolerance: 'pointer', - drop: function (event, ui) { + drop: function(event, ui) { $drop = $(this); $dropItem = $drop.closest('.treeview-item'); @@ -649,15 +665,16 @@ } var newParent = - type === 'child' ? - itemId : - parents.length > 0 ? - parents[parents.length - 1] : - ''; + type === 'child' + ? itemId + : parents.length > 0 + ? parents[parents.length - 1] + : ''; $treeView.reload({ url: url + '/move', - data: [{ + data: [ + { name: 'parents', value: oldParents }, @@ -683,7 +700,7 @@ // This is copy paste from SilverStripe GridField.js, modified to work for the TreeView // It updates the gridfield by sending the specified request // and using the response as the new content for the gridfield - reload: function (ajaxOpts, successCallback) { + reload: function(ajaxOpts, successCallback) { var self = this, form = this.closest('form'), focusedElName = this.find(':input:focus').attr('name'), // Save focused element for restoring after refresh @@ -691,10 +708,12 @@ if (!ajaxOpts) ajaxOpts = {}; if (!ajaxOpts.data) ajaxOpts.data = []; - ajaxOpts.data = ajaxOpts.data.concat(data).concat([{ - name: 'state', - value: self.data('state-id') - }]); + ajaxOpts.data = ajaxOpts.data.concat(data).concat([ + { + name: 'state', + value: self.data('state-id') + } + ]); // Include any GET parameters from the current URL, as the view state might depend on it. // For example, a list prefiltered through external search criteria might be passed to GridField. @@ -708,14 +727,16 @@ form.addClass('loading'); $.ajax( - $.extend({}, { + $.extend( + {}, + { headers: { 'X-Pjax': 'CurrentField' }, type: 'POST', url: this.data('url'), dataType: 'html', - success: function (data) { + success: function(data) { // Replace the grid field with response, not the form. // TODO Only replaces all its children, to avoid replacing the current scope // of the executing method. Means that it doesn't retrigger the onmatch() on the main container. @@ -731,7 +752,7 @@ // TODO: Don't know how original SilverStripe GridField magically calls self.onadd(); }, - error: function (e) { + error: function(e) { alert(i18n._t('Admin.ERRORINTRANSACTION')); form.removeClass('loading'); } diff --git a/src/PageElement.php b/src/PageElement.php index a305443..dfba364 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -175,54 +175,39 @@ public function getTreeViewPreview() } /** - * Gets all parents that this PageElement is rendered on. - * - * Adds the following properties to each parent: - * __PageSection: The PageSection on the parent that the PageElement is from. - * __PageElementVersion: The version of the PageElement being used. - * __PageElementPublishedVersion: The published version of the PageElement being used. - * @return \DataObject[] - */ - public function getAllSectionParents() { - $parents = ArrayList::create(); + * Gets all places that this PageElement is shown in. + * + * Returns a list of objects with the following properties: + * Parent: The name and class name of the root parent object. + * Section: The name of the section on the root object where this element is shown. + * Path: The names of the parent PageElements that lead to this element. + * @return \SilverStripe\ORM\ArrayList An array of info objects + */ + public function getAllUses() { + $uses = ArrayList::create(); foreach ($this->PageSections() as $section) { $p = $section->Parent(); + // Skip if the parent object doesn't exist (possibly archived) if (!$p || !$p->ID) { - // If our parent doesn't have an ID it's probably deleted/archived. - $p = $archived = Versioned::get_latest_version($section->__ParentClass, $section->__ParentID); - $p->__PageSection = $section; - $p->__PageElementVersion = $section->Elements()->filter("ID", $this->ID)->First()->Version; - $p->__PageElementPublishedVersion = "Not published"; - $parents->add($p); continue; } - $stage = Versioned::get_stage(); - Versioned::set_stage(Versioned::LIVE); - $pubSection = DataObject::get_by_id($section->ClassName, $section->ID); - $pubElem = $pubSection ? $pubSection->Elements()->filter("ID", $this->ID)->First() : null; - $p->__PageSection = $section; - $p->__PageElementVersion = $section->Elements()->filter("ID", $this->ID)->First()->Version; - $p->__PageElementPublishedVersion = $pubElem ? $pubElem->Version : "Not published"; - Versioned::set_stage($stage); - $parents->add($p); + $uses->add(DataObject::create([ + "Parent" => $p->Title . " (" . $p->ClassName . ")", + "Section" => $section->__Name, + "Path" => "", + ])); } - return $parents; - } - - /** - * Recursively gets all the parents of this element, in no particular order. - * @return \FLXLabs\PageSections\PageElement[] - */ - public function getAllParents() - { - $parents = ArrayList::create($this->Parents()->toList()); foreach ($this->Parents() as $parent) { - $parents->merge($parent->getAllParents()); + foreach ($parent->getAllUses() as $use) { + $use->Path = $use->Path . " -> " . $parent->Name; + $uses->add($use); + } } - return $parents; + + return $uses; } public function getCMSFields() @@ -242,24 +227,20 @@ public function getCMSFields() "Title" ); - // Create an array of all the sections this element is on - $parents = $this->getAllSectionParents(); + // Create an array of all places this PageElement is shown + $uses = $this->getAllUses(); - if ($parents->Count() > 0) { + if ($uses->Count() > 0) { $config = GridFieldConfig_Base::create() ->removeComponentsByType(GridFieldDataColumns::class) ->addComponent($dataColumns = new GridFieldDataColumns()); $dataColumns->setDisplayFields([ - "ID" => "ID", - "ClassName" => "Type", - "Title" => "Title", - "__PageSection.__Name" => "PageSection", - "__PageElementVersion" => "Element version", - "__PageElementPublishedVersion" => "Published element version", - "getPublishState" => "Parent State", + "Parent" => "Parent", + "Section" => "Section", + "Path" => "Path", ]); - $gridField = GridField::create("Pages", "Section Parents", $parents, $config); - $fields->addFieldToTab("Root.SectionParents", $gridField); + $gridField = GridField::create("Pages", "Uses", $uses, $config); + $fields->addFieldToTab("Root.Uses", $gridField); } return $fields; diff --git a/src/PageSection.php b/src/PageSection.php index 3b19cab..9a59d59 100644 --- a/src/PageSection.php +++ b/src/PageSection.php @@ -82,21 +82,7 @@ public function forTemplate() * @return string */ public function getName() { - if ($this->__Name) { - return $this->__Name; - } - - $parent = $this->Parent(); - // TODO: Find out why this happens - if (!method_exists($parent, "getPageSectionNames")) { - return null; - } - foreach ($parent->getPageSectionNames() as $sectionName) { - if ($parent->{"PageSection" . $sectionName . "ID"} === $this->ID) { - return $sectionName; - } - } - return null; + return $this->__Name; } /** diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index cf10cd0..3a9a8d8 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -126,7 +126,7 @@ public function updateCMSFields(FieldList $fields) { $fields->removeByName($name); $fields->removeByName($name . "ID"); - if ($this->owner->ID) { + if ($this->owner->ID && $this->owner->$name->ID) { $tv = new TreeView($name, $sectionName, $this->owner->$name); $fields->addFieldToTab("Root.PageSections.{$sectionName}", $tv); } diff --git a/src/TreeView.php b/src/TreeView.php index 71998c0..3bd9c57 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -46,11 +46,12 @@ class TreeView extends FormField ); - public function __construct($name, $title = null, $section = null) + public function __construct($name, $title = null, $section = null, $readonly = false) { parent::__construct($name, $title, null); $this->section = $section; + $this->readonly = $readonly; $this->context = singleton(PageElement::class)->getDefaultSearchContext(); if ($section) { @@ -62,6 +63,10 @@ public function __construct($name, $title = null, $section = null) } } + public function performReadonlyTransformation() { + return new TreeView($this->name, $this->title, $this->section, $this->readonly); + } + public function setValue($value, $data = null) { $this->section = $value; return $this; @@ -232,6 +237,9 @@ public function move($request) $newParent->Children()->Add($item, $sortArr); } + // Save the parent so the relation sort order is redone + $newParent->write(); + return $this->FieldHolder(); } @@ -419,11 +427,6 @@ public function doSearch($data, $form) $allowed = $this->section->getAllowedPageElements(); // Remove all disallowed classes $list = $list->filter("ClassName", $allowed); - // If we're viewing the search list on a PageElement, - // then we have to remove all parents as possible elements - /*if ($this->parent->ClassName === PageElement::class) { - $list = $list->subtract($this->parent->getAllParents()); - }*/ $list = new PaginatedList($list, $data); $data = $this->customise([ 'SearchForm' => $form, @@ -604,28 +607,31 @@ public function FieldHolder($properties = array()) $elems[$class] = singleton($class)->singular_name(); } - // Create the add new button at the very top - $addButton = TreeViewFormAction::create( - $this, - "AddActionBase", - null, - null, - null - ); - $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); - $addButton->addExtraClass("btn add-button font-icon-plus"); - if (!count($elems)) { - $addButton->setDisabled(true); - } - $addButton->setButtonContent(' '); - $content .= ArrayData::create([ - "Button" => $addButton - ])->renderWith("\FLXLabs\PageSections\TreeViewAddNewButton"); - - // Create the find existing button - $findExisting = TreeViewFormAction::create($this, 'FindExisting', 'Find existing'); - $findExisting->addExtraClass("btn font-icon-search tree-actions-findexisting"); - $content .= $findExisting->forTemplate(); + if (!$this->readonly) { + // Create the add new button at the very top + $addButton = TreeViewFormAction::create( + $this, + "AddActionBase", + null, + null, + null + ); + $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); + $addButton->addExtraClass("btn add-button font-icon-plus"); + if (!count($elems)) { + $addButton->setDisabled(true); + } + $addButton->setButtonContent(' '); + $content .= ArrayData::create([ + "Button" => $addButton + ])->renderWith("\FLXLabs\PageSections\TreeViewAddNewButton"); + + // Create the find existing button + $findExisting = TreeViewFormAction::create($this, 'FindExisting', 'Find existing'); + $findExisting->addExtraClass("btn font-icon-search tree-actions-findexisting"); + $content .= $findExisting->forTemplate(); + } + $content .= "
                                                              "; $list = $this->getItems()->sort($this->sortField)->toArray(); @@ -640,6 +646,7 @@ public function FieldHolder($properties = array()) 'fieldset', [ 'class' => 'treeview-pagesections pagesection-' . $this->getName(), + 'data-readonly' => $this->readonly, 'data-name' => $this->getName(), 'data-url' => $this->Link(), 'data-state-id' => $sessionId, @@ -725,7 +732,7 @@ private function renderTree($item, $parents, $opens, $isFirst) // Create a button to add a new child element // and save the allowed child classes on the button - if (count($classes)) { + if (!$this->readonly && count($classes)) { $addButton = TreeViewFormAction::create( $this, "AddAction".$item->ID, @@ -740,49 +747,52 @@ private function renderTree($item, $parents, $opens, $isFirst) } $addButton->setButtonContent(' '); } - // Create a button to add an element after - // and save the allowed child classes on the button - $addAfterButton = TreeViewFormAction::create( - $this, - "AddAfterAction".$item->ID, - null, - null, - null - ); - $addAfterButton->setAttribute("data-allowed-elements", - json_encode($parentElems, JSON_UNESCAPED_UNICODE) - ); - $addAfterButton->addExtraClass("btn add-after-button font-icon-plus"); - if (!count($parentElems)) { - $addAfterButton->setDisabled(true); - } - $addAfterButton->setButtonContent(' '); - // Create a button to delete and/or remove the element from the parent - $deleteButton = TreeViewFormAction::create( - $this, - "DeleteAction".$item->ID, - null, - null, - null - ); - $deleteButton->setAttribute( - "data-used-count", - $item->Parents()->Count() + $item->getAllSectionParents()->Count() - ); - $deleteButton->addExtraClass("btn delete-button font-icon-trash-bin"); - $deleteButton->setButtonContent('Delete'); + if (!$this->readonly) { + // Create a button to add an element after + // and save the allowed child classes on the button + $addAfterButton = TreeViewFormAction::create( + $this, + "AddAfterAction".$item->ID, + null, + null, + null + ); + $addAfterButton->setAttribute("data-allowed-elements", + json_encode($parentElems, JSON_UNESCAPED_UNICODE) + ); + $addAfterButton->addExtraClass("btn add-after-button font-icon-plus"); + if (!count($parentElems)) { + $addAfterButton->setDisabled(true); + } + $addAfterButton->setButtonContent(' '); - // Create a button to edit the record - $editButton = TreeViewFormAction::create( - $this, - "EditAction".$item->ID, - null, - null, - null - ); - $editButton->addExtraClass("btn edit-button font-icon-edit"); - $editButton->setButtonContent('Edit'); + // Create a button to delete and/or remove the element from the parent + $deleteButton = TreeViewFormAction::create( + $this, + "DeleteAction".$item->ID, + null, + null, + null + ); + $deleteButton->setAttribute( + "data-used-count", + $item->getAllUses()->Count() + ); + $deleteButton->addExtraClass("btn delete-button font-icon-trash-bin"); + $deleteButton->setButtonContent('Delete'); + + // Create a button to edit the record + $editButton = TreeViewFormAction::create( + $this, + "EditAction".$item->ID, + null, + null, + null + ); + $editButton->addExtraClass("btn edit-button font-icon-edit"); + $editButton->setButtonContent('Edit'); + } // Create the tree icon $icon = ''; @@ -807,6 +817,7 @@ private function renderTree($item, $parents, $opens, $isFirst) $treeButton->setButtonContent(' '); return ArrayData::create([ + "Readonly" => $this->readonly, "Item" => $item, "Tree" => $tree, "IsOpen" => $isOpen, @@ -816,10 +827,10 @@ private function renderTree($item, $parents, $opens, $isFirst) "AllowedElements" => json_encode($elems, JSON_UNESCAPED_UNICODE), "TreeButton" => $treeButton, "AddButton" => isset($addButton) ? $addButton : null, - "AddAfterButton" => $addAfterButton, - "EditButton" => $editButton, - "DeleteButton" => $deleteButton, - "UsedCount" => $item->Parents()->Count() + $item->getAllSectionParents()->Count(), + "AddAfterButton" => isset($addAfterButton) ? $addAfterButton : null, + "EditButton" => isset($editButton) ? $editButton : null, + "DeleteButton" => isset($deleteButton) ? $deleteButton : null, + "UsedCount" => $item->getAllUses()->Count() ])->renderWith("\FLXLabs\PageSections\TreeViewPageElement"); } diff --git a/templates/FLXLabs/PageSections/TreeViewPageElement.ss b/templates/FLXLabs/PageSections/TreeViewPageElement.ss index b4fa82f..a7f09aa 100644 --- a/templates/FLXLabs/PageSections/TreeViewPageElement.ss +++ b/templates/FLXLabs/PageSections/TreeViewPageElement.ss @@ -13,12 +13,14 @@ class="treeview-item__panel" >
                                                              + <% if not Readonly %>
                                                              <% if IsFirst %>
                                                              <% end_if %>
                                                              + <% end_if %>
                                                              From be953b4bf1ef1f992d13f82fa539c6bcac9345da Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Tue, 12 Mar 2019 17:03:26 +0100 Subject: [PATCH 60/82] chore(composer): Update requirements --- composer.json | 68 +++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index 16b73a4..7296965 100755 --- a/composer.json +++ b/composer.json @@ -1,31 +1,41 @@ { - "name": "flxlabs/silverstripe-pagesections", - "version": "0.1.2", - "description": "Adds configurable page sections and elements to your SilverStripe project.", - "type": "silverstripe-module", - "homepage": "http://github.com/flxlabs/silverstripe-pagesections", - "keywords": ["silverstripe", "sections", "elements", "page sections", "page elements"], - "license": "MIT", - "authors": [{ - "name": "Marco Crespi", - "email": "mrc@flxlabs.com" - }], - "support": { - "issues": "http://github.com/flxlabs/silverstripe-pagesections/issues" - }, - "repositories": [{ - "type": "vcs", - "url": "https://github.com/flxlabs/silverstripe-gridfield-betterbuttons" - }], - "require": { - "silverstripe/framework": "^4.0.1", - "symbiote/silverstripe-gridfieldextensions": "^3" - }, - "extra": { - "installer-name": "pagesections", - "expose": [ - "css", - "javascript" - ] - } + "name": "flxlabs/silverstripe-pagesections", + "version": "0.1.2", + "description": "Adds configurable page sections and elements to your SilverStripe project.", + "type": "silverstripe-module", + "homepage": "http://github.com/flxlabs/silverstripe-pagesections", + "keywords": [ + "silverstripe", + "sections", + "elements", + "page sections", + "page elements" + ], + "license": "MIT", + "authors": [ + { + "name": "Marco Crespi", + "email": "mrc@flxlabs.com" + } + ], + "support": { + "issues": "http://github.com/flxlabs/silverstripe-pagesections/issues" + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/flxlabs/silverstripe-gridfield-betterbuttons" + } + ], + "require": { + "silverstripe/framework": "^4.3.0", + "symbiote/silverstripe-gridfieldextensions": "^3" + }, + "extra": { + "installer-name": "pagesections", + "expose": [ + "css", + "javascript" + ] + } } From 3f85484ea008e5602fcd609df5875869908c828d Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Tue, 12 Mar 2019 18:08:52 +0100 Subject: [PATCH 61/82] fix(treeview): Fix treeview on archives page --- src/TreeView.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/TreeView.php b/src/TreeView.php index 3bd9c57..bc1f25d 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -648,7 +648,7 @@ public function FieldHolder($properties = array()) 'class' => 'treeview-pagesections pagesection-' . $this->getName(), 'data-readonly' => $this->readonly, 'data-name' => $this->getName(), - 'data-url' => $this->Link(), + 'data-url' => !$this->readonly ? $this->Link() : null, 'data-state-id' => $sessionId, 'data-allowed-elements' => json_encode($elems), ], @@ -723,12 +723,6 @@ private function renderTree($item, $parents, $opens, $isFirst) // There are two cases, either this GridField is on a page, // or it is on a PageElement and we're looking at the children $isAllowedRoot = in_array($item->ClassName, $parentClasses); - - // Create the tree icon - $icon = ''; - if ($item->Children() && $item->Children()->Count() > 0) { - $icon = ($isOpen === true ? 'font-icon-down-open' : 'font-icon-right-open'); - } // Create a button to add a new child element // and save the allowed child classes on the button @@ -796,7 +790,7 @@ private function renderTree($item, $parents, $opens, $isFirst) // Create the tree icon $icon = ''; - if ($item->Children() && $item->Children()->Count() > 0) { + if (!$this->readonly && $item->Children() && $item->Children()->Count() > 0) { $icon = ($isOpen === true ? 'font-icon-down-open' : 'font-icon-right-open'); } From 1b86bba0f507dd73853e1f9d5fd56475466dd328 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Mon, 18 Mar 2019 12:08:11 +0100 Subject: [PATCH 62/82] fix(page-section): Fix parent changing class --- src/PageSectionsExtension.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 3a9a8d8..4652b8c 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -100,6 +100,9 @@ public function onAfterWrite() if (!$this->owner->{$name . "ID"}) { // Restore or create a page section if we don't have one yet $this->restoreOrCreate($sectionName); + } else if ($this->owner->$name()->__ParentClass !== $this->owner->ClassName) { + $this->owner->$name()->__ParentClass = $this->owner->ClassName; + $this->owner->$name()->write(); } } } From a35ebaf47123e9aaa8f7f7196dedd4bf637e7123 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Mon, 18 Mar 2019 12:08:39 +0100 Subject: [PATCH 63/82] fix(treeview): Fix bug on form validation errors --- src/TreeView.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/TreeView.php b/src/TreeView.php index bc1f25d..7639777 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -68,6 +68,10 @@ public function performReadonlyTransformation() { } public function setValue($value, $data = null) { + if (!$value) { + return $this; + } + $this->section = $value; return $this; } From b99694af0f801eb73a7433922f34167c833f2883 Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Mon, 24 Jun 2019 13:43:53 +0200 Subject: [PATCH 64/82] fix(treeview): Fix moving items within same parent --- src/TreeView.php | 107 +++++++++++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 41 deletions(-) diff --git a/src/TreeView.php b/src/TreeView.php index 7639777..23aa940 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -63,11 +63,13 @@ public function __construct($name, $title = null, $section = null, $readonly = f } } - public function performReadonlyTransformation() { + public function performReadonlyTransformation() + { return new TreeView($this->name, $this->title, $this->section, $this->readonly); } - public function setValue($value, $data = null) { + public function setValue($value, $data = null) + { if (!$value) { return $this; } @@ -84,8 +86,7 @@ public function setValue($value, $data = null) { * this because the default behavior would write a NULL value into the relation. */ public function saveInto(DataObjectInterface $record) - { - } + { } /** * Recursively opens an item @@ -189,8 +190,10 @@ public function move($request) return $this->FieldHolder(); } - if (!isset($data["itemId"]) || !isset($data["parents"]) || - !isset($data["newParent"]) || !isset($data["sort"])) { + if ( + !isset($data["itemId"]) || !isset($data["parents"]) || + !isset($data["newParent"]) || !isset($data["sort"]) + ) { Controller::curr()->getResponse()->setStatusCode( 400, "Missing required data!" @@ -200,7 +203,6 @@ public function move($request) $itemId = intval($data["itemId"]); $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); - $path = array_merge($parents, [$itemId]); $item = PageElement::get()->byID($itemId); @@ -211,7 +213,7 @@ public function move($request) } else { $newParent = $this->section; } - + // Check if this element is allowed as a child on the new element $allowed = in_array($item->ClassName, $newParent->getAllowedPageElements()); if (!$allowed) { @@ -219,30 +221,46 @@ public function move($request) 400, "The type " . $item->ClassName . " is not allowed as a child of " . $newParent->ClassName ); - return $gridField->FieldHolder(); + return $this->FieldHolder(); } - // Remove the element from the current parent + // Get the current parent if (count($parents) == 0) { - $this->getItems()->removeByID($itemId); + $parent = $this->section; } else { $parent = PageElement::get()->byID($parents[count($parents) - 1]); - $parent->Children()->removeByID($itemId); } + // Get requested sort order $sort = intval($data["sort"]); $sortBy = $this->getSortField(); $sortArr = [$sortBy => $sort]; - // Add the element to the new parent - if ($newParent->ClassName == PageSection::class) { - $newParent->Elements()->Add($item, $sortArr); + // Check if we moved the element within the same parent + if ($parent->ClassName === $newParent->ClassName && $parent->ID === $newParent->ID) { + // Move the element around in the current parent + if ($newParent->ClassName == PageSection::class) { + $newParent->Elements()->Add($itemId, $sortArr); + } else { + $newParent->Children()->Add($itemId, $sortArr); + } } else { - $newParent->Children()->Add($item, $sortArr); - } + // Remove the element from the current parent + if (count($parents) == 0) { + $parent = $this->section; + $this->getItems()->removeByID($itemId); + } else { + $parent = PageElement::get()->byID($parents[count($parents) - 1]); + $parent->Children()->removeByID($itemId); + } - // Save the parent so the relation sort order is redone - $newParent->write(); + // Add the element to the new parent + if ($newParent->ClassName == PageSection::class) { + $newParent->Elements()->Add($item, $sortArr); + } else { + $newParent->Children()->Add($item, $sortArr); + } + } return $this->FieldHolder(); } @@ -277,13 +295,13 @@ public function add($request) ); return $this->FieldHolder(); } - + $this->getItems()->Add($element); } else { // ...otherwise add a completely new item $itemId = isset($data["itemId"]) ? intval($data["itemId"]) : null; $type = $data["type"]; - + $child = $type::create(); $child->Name = "New " . $child->singular_name(); @@ -305,11 +323,11 @@ public function add($request) ); return $this->FieldHolder(); } - + $child->write(); $item->Children()->Add($child, $sortArr); $item->write(); - + // Make sure we can see the child $this->openItem(array_merge($path, [$item->ID])); } else { @@ -433,8 +451,8 @@ public function doSearch($data, $form) $list = $list->filter("ClassName", $allowed); $list = new PaginatedList($list, $data); $data = $this->customise([ - 'SearchForm' => $form, - 'Items' => $list + 'SearchForm' => $form, + 'Items' => $list ]); return $data->renderWith("FLXLabs\PageSections\TreeViewFindExistingForm"); } @@ -495,7 +513,8 @@ public function DetailForm(PageElement $item, bool $loadData = true) * @param \SilverStripe\Control\HTTPRequest $request * @return string */ - public function detail($request) { + public function detail($request) + { $id = intval($request->requestVar("ID")); if ($id) { $request->getSession()->set("ElementID", $id); @@ -524,7 +543,8 @@ public function detail($request) { return $this->DetailForm($item, false); } - public function handleRequest(HTTPRequest $request) { + public function handleRequest(HTTPRequest $request) + { $this->setRequest($request); // Forward requests to the elements in the detail form to their respective controller @@ -544,7 +564,8 @@ public function handleRequest(HTTPRequest $request) { * @param \SilverStripe\Control\HTTPRequest $request * @return string */ - public function doSave($data, $form) { + public function doSave($data, $form) + { $id = intval($data["ID"]); $item = PageElement::get()->byID($id); @@ -700,10 +721,12 @@ private function renderTree($item, $parents, $opens, $isFirst) $tree = "[" . implode( ',', - array_map(function ($item) { - return $item->ID; - }, - $parents) + array_map( + function ($item) { + return $item->ID; + }, + $parents + ) ) . "]"; @@ -715,8 +738,8 @@ private function renderTree($item, $parents, $opens, $isFirst) } // Construct the array of all allowed child elements in parent slot - $parentClasses = count($parents) > 0 - ? $parents[count($parents) - 1]->getAllowedPageElements() + $parentClasses = count($parents) > 0 + ? $parents[count($parents) - 1]->getAllowedPageElements() : $this->section->getAllowedPageElements(); $parentElems = []; foreach ($parentClasses as $class) { @@ -733,7 +756,7 @@ private function renderTree($item, $parents, $opens, $isFirst) if (!$this->readonly && count($classes)) { $addButton = TreeViewFormAction::create( $this, - "AddAction".$item->ID, + "AddAction" . $item->ID, null, null, null @@ -751,12 +774,13 @@ private function renderTree($item, $parents, $opens, $isFirst) // and save the allowed child classes on the button $addAfterButton = TreeViewFormAction::create( $this, - "AddAfterAction".$item->ID, + "AddAfterAction" . $item->ID, null, null, null ); - $addAfterButton->setAttribute("data-allowed-elements", + $addAfterButton->setAttribute( + "data-allowed-elements", json_encode($parentElems, JSON_UNESCAPED_UNICODE) ); $addAfterButton->addExtraClass("btn add-after-button font-icon-plus"); @@ -768,7 +792,7 @@ private function renderTree($item, $parents, $opens, $isFirst) // Create a button to delete and/or remove the element from the parent $deleteButton = TreeViewFormAction::create( $this, - "DeleteAction".$item->ID, + "DeleteAction" . $item->ID, null, null, null @@ -783,7 +807,7 @@ private function renderTree($item, $parents, $opens, $isFirst) // Create a button to edit the record $editButton = TreeViewFormAction::create( $this, - "EditAction".$item->ID, + "EditAction" . $item->ID, null, null, null @@ -801,7 +825,7 @@ private function renderTree($item, $parents, $opens, $isFirst) // Create the tree field $treeButton = TreeViewFormAction::create( $this, - "TreeNavAction".$item->ID, + "TreeNavAction" . $item->ID, null, "dotreenav", ["element" => $item] @@ -891,4 +915,5 @@ private function closeItem($path) } } -class TreeView_Readonly extends TreeView {} +class TreeView_Readonly extends TreeView +{ } From 0c33dfe98b17e3d88127ff1b3f70fc7c443e040f Mon Sep 17 00:00:00 2001 From: "Marco (Valandur)" Date: Mon, 19 Aug 2019 17:02:31 +0200 Subject: [PATCH 65/82] fix(versioned): Fix for change in ss behaviour --- src/PageSectionsExtension.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 4652b8c..1e61519 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -35,7 +35,7 @@ public static function get_extra_config($class = null, $extensionClass = null) } foreach ($sections as $section) { - $name = "PageSection".$section; + $name = "PageSection" . $section; $has_one[$name] = PageSection::class; $names[] = $name; @@ -68,7 +68,7 @@ public function getAllowedPageElements($section = "Main") $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); $classes = array_diff($classes, [PageElement::class]); $ret = []; - foreach($classes as $class) { + foreach ($classes as $class) { if ($class::$canBeRoot) $ret[] = $class; } return $ret; @@ -95,7 +95,7 @@ public function onAfterWrite() $sections = $this->getPageSectionNames(); foreach ($sections as $sectionName) { - $name = "PageSection".$sectionName; + $name = "PageSection" . $sectionName; if (!$this->owner->{$name . "ID"}) { // Restore or create a page section if we don't have one yet @@ -113,18 +113,19 @@ public function onBeforeArchive() $sections = $this->getPageSectionNames(); foreach ($sections as $sectionName) { - $name = "PageSection".$sectionName; + $name = "PageSection" . $sectionName; $this->owner->{$name . "ID"} = 0; } } - public function updateCMSFields(FieldList $fields) { + public function updateCMSFields(FieldList $fields) + { $sections = $this->getPageSectionNames(); $fields->removeByName("__PageSectionCounter"); foreach ($sections as $sectionName) { - $name = "PageSection".$sectionName; + $name = "PageSection" . $sectionName; $fields->removeByName($name); $fields->removeByName($name . "ID"); @@ -189,7 +190,8 @@ public function __call($method, $arguments) } } - private function restoreOrCreate($sectionName) { + private function restoreOrCreate($sectionName) + { if (mb_strpos($sectionName, "PageSection") !== false) { throw new \Error("PageSection name should not contain 'PageSection' when restoring or creating!"); } @@ -197,7 +199,8 @@ private function restoreOrCreate($sectionName) { $name = "PageSection" . $sectionName; // Try restoring the section from the history - $archived = Versioned::get_latest_version(PageSection::class, $this->owner->{$name . "ID"}); + $id = $this->owner->{$name . "ID"}; + $archived = $id ? Versioned::get_latest_version(PageSection::class, $id) : null; if ($archived && $archived->ID) { // Update the back references $archived->ID = $this->owner->{$name . "ID"}; From b767ae192fcc3346ac2b27b427d5b5db460183c9 Mon Sep 17 00:00:00 2001 From: Felix Eggmann Date: Wed, 5 Feb 2020 19:02:01 +0100 Subject: [PATCH 66/82] fix(treeView): remove relations when deleting an element --- src/TreeView.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/TreeView.php b/src/TreeView.php index 23aa940..33f44de 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -404,6 +404,13 @@ public function delete($request) // Close the element in case it's open to avoid errors $this->closeItem($path); + // let's remove all relations + $this->getItems()->removeByID($itemId); + foreach ($parents as $parentId) { + $parent = PageElement::get()->byID($parentId); + if ($parent) $parent->Children()->removeByID($itemId); + } + // Delete the element $item->delete(); From 76169f1941e5496169297707a5b1c1a830c1f3f5 Mon Sep 17 00:00:00 2001 From: Felix Eggmann Date: Wed, 5 Feb 2020 19:02:22 +0100 Subject: [PATCH 67/82] feat(tasks): add db sanitation task --- src/PagesectionSanitizeTask.php | 76 +++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/PagesectionSanitizeTask.php diff --git a/src/PagesectionSanitizeTask.php b/src/PagesectionSanitizeTask.php new file mode 100644 index 0000000..c01ea9c --- /dev/null +++ b/src/PagesectionSanitizeTask.php @@ -0,0 +1,76 @@ +output(); + } + die; + } + + $stages = [Versioned::DRAFT]; + foreach ($stages as $stage) { + Versioned::set_stage($stage); + foreach (Page::get() as $page) { + echo "Page: " . $page->URLSegment . "
                                                              "; + foreach ($page->getPageSectionNames() as $sectionName) { + $section = $page->__get("PageSection{$sectionName}"); + echo "Section: " . $section->ID . "
                                                              "; + $trustedElementIds = $section->Elements()->Column("ID"); + $untrustedElementIds = PageSectionPageElementRel::get()->Filter("PageSectionID", $section->ID)->Column("ElementID"); + $toDelete = array_diff($untrustedElementIds, $trustedElementIds); + echo "trusted:
                                                              "; + echo implode($trustedElementIds, ", "); + echo "
                                                              untrusted:
                                                              "; + echo implode($untrustedElementIds, ", "); + if (count($toDelete)) { + echo "
                                                              to delete:
                                                              "; + echo implode($toDelete, ", "); + $section->Elements()->removeMany($toDelete); + } + } + echo "
                                                              "; + } + + foreach (PageElement::get() as $element) { + echo "Element: " . $element->ID . "
                                                              "; + $trustedElementIds = $element->Children()->Column("ID"); + $untrustedElementIds = PageElementSelfRel::get()->Filter("ParentID", $element->ID)->Column("ChildID"); + $toDelete = array_diff($untrustedElementIds, $trustedElementIds); + echo "trusted:
                                                              "; + echo implode($trustedElementIds, ", "); + echo "
                                                              untrusted:
                                                              "; + echo implode($untrustedElementIds, ", "); + if (count($toDelete)) { + echo "
                                                              to delete:
                                                              "; + echo implode($toDelete, ", "); + $element->Children()->removeMany($toDelete); + } + echo "
                                                              "; + } + + } + } +} From b4ec6ab5da0e3f7a81dd83f85dec2ba5414cd57f Mon Sep 17 00:00:00 2001 From: Felix Quartu Date: Wed, 26 May 2021 15:08:28 +0200 Subject: [PATCH 68/82] fix(pageSection): re-enable rendering in SS templates --- src/PageSection.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/PageSection.php b/src/PageSection.php index 9a59d59..3b48ea0 100644 --- a/src/PageSection.php +++ b/src/PageSection.php @@ -74,7 +74,11 @@ public function onAfterWrite() public function forTemplate() { - return $this->Elements()->Count(); + $elements = $this->Elements(); + return $this->renderWith( + "RenderChildren", + array("Elements" => $elements, "ParentList" => strval($this->ID)) + ); } /** From b0b606170ddb177ac411eb520fea0649f9f65d80 Mon Sep 17 00:00:00 2001 From: Felix Quartu Date: Tue, 8 Nov 2022 10:38:41 +0100 Subject: [PATCH 69/82] Update PageSectionsExtension.php fix(extension): avoid error if applied to non-versioned dataObject --- src/PageSectionsExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 1e61519..0430c3a 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -180,7 +180,7 @@ public function __call($method, $arguments) } // Fix for archived errors - if ($this->owner->isArchived()) { + if (method_exists($this->owner, 'isArchived') && $this->owner->isArchived()) { return new PageSection(); } From e48d9be5bf0d29ef8129485e4384c41f724e5f8a Mon Sep 17 00:00:00 2001 From: Felix Quartu Date: Wed, 16 Nov 2022 14:36:19 +0100 Subject: [PATCH 70/82] fix(elements): archive instead delete --- src/PageElement.php | 571 ++++++----- src/PageSectionsExtension.php | 2 +- src/TreeView.php | 1804 ++++++++++++++++----------------- 3 files changed, 1186 insertions(+), 1191 deletions(-) diff --git a/src/PageElement.php b/src/PageElement.php index dfba364..37e0818 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -4,299 +4,298 @@ use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Core\ClassInfo; -use SilverStripe\Forms\FieldList; -use SilverStripe\Forms\ReadonlyField; -use SilverStripe\Forms\GridField\GridFieldConfig; +use SilverStripe\Forms\GridField\GridField; use SilverStripe\Forms\GridField\GridFieldConfig_Base; -use SilverStripe\Forms\GridField\GridFieldButtonRow; -use SilverStripe\Forms\GridField\GridFieldToolbarHeader; use SilverStripe\Forms\GridField\GridFieldDataColumns; -use SilverStripe\Forms\GridField\GridFieldDetailForm; -use SilverStripe\Forms\GridField\GridFieldEditButton; -use SilverStripe\Forms\GridField\GridFieldFooter; -use SilverStripe\Forms\GridField\GridField; +use SilverStripe\Forms\ReadonlyField; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataObject; -use SilverStripe\Security\Member; use SilverStripe\Versioned\Versioned; -use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass; -use Symbiote\GridFieldExtensions\GridFieldAddExistingSearchButton; -use SilverStripe\Forms\Tab; - class PageElement extends DataObject { - private static $table_name = "FLXLabs_PageSections_PageElement"; - - protected static $defaultIsOpen = true; - public static $canBeRoot = true; - - public static function getSingularName() - { - return static::$singular_name; - } - public static function getPluralName() - { - return static::$plural_name; - } - public static function isOpenByDefault() - { - return static::$defaultIsOpen; - } - - public function canView($member = null) - { - return true; - } - public function canEdit($member = null) - { - return true; - } - public function canDelete($member = null) - { - return false; - } - public function canCreate($member = null, $context = []) - { - return true; - } - - private static $can_be_root = true; - - private static $db = [ - "Name" => "Varchar(255)", - "__Counter" => "Int" - ]; - - private static $many_many = [ - "Children" => [ - "through" => PageElementSelfRel::class, - "from" => "Parent", - "to" => "Child", - ] - ]; - - private static $belongs_many_many = [ - "Parents" => PageElement::class . ".Children", - "PageSections" => PageSection::class . ".Elements", - ]; - - private static $owns = [ - "Children", - ]; - - private static $cascade_deletes = [ - "Children", - ]; - - private static $summary_fields = [ - "GridFieldPreview", - ]; - - private static $searchable_fields = [ - "ClassName", - "Name", - "ID", - ]; - - // Returns all page element classes, without the base class - public static function getAllPageElementClasses() - { - $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); - $classes = array_diff($classes, [PageElement::class]); - return $classes; - } - - /** - * The classes of allowed child elements - * - * Gets a list of classnames which are valid child elements of this PageElement. - * @return string[] - */ - public function getAllowedPageElements() - { - return self::getAllPageElementClasses(); - } - - public function onBeforeWrite() - { - parent::onBeforeWrite(); - - // If a field changed then update the counter, unless it's the counter that changed - $changed = $this->getChangedFields(true, DataObject::CHANGE_VALUE); - if (count($changed) > 0 && (!isset($changed["__Counter"]) || $changed["__Counter"]["level"] <= 1)) { - $this->__Counter++; - } - - $elems = $this->Children()->Sort("SortOrder")->Column("ID"); - $count = count($elems); - for ($i = 0; $i < $count; $i++) { - $this->Children()->Add($elems[$i], [ "SortOrder" => ($i + 1) * 2, "__NewOrder" => true ]); - } - } - - public function onAfterWrite() - { - parent::onAfterWrite(); - - if (Versioned::get_stage() == Versioned::DRAFT && $this->isChanged("__Counter", DataObject::CHANGE_VALUE)) { - foreach ($this->PageSections() as $section) { - $section->__Counter++; - $section->write(); - } - - foreach ($this->Parents() as $parent) { - $parent->__Counter++; - $parent->write(); - } - } - } - - public function onAfterDelete() - { - parent::onAfterDelete(); - - if (Versioned::get_stage() == Versioned::DRAFT) { - foreach ($this->PageSections() as $section) { - $section->__Counter++; - $section->write(); - } - } - } - - /** - * Gets the preview of this PageElement in the TreeView. - * @return string - */ - public function getTreeViewPreview() - { - return $this->GridFieldPreview; - } - - /** - * Gets all places that this PageElement is shown in. - * - * Returns a list of objects with the following properties: - * Parent: The name and class name of the root parent object. - * Section: The name of the section on the root object where this element is shown. - * Path: The names of the parent PageElements that lead to this element. - * @return \SilverStripe\ORM\ArrayList An array of info objects - */ - public function getAllUses() { - $uses = ArrayList::create(); - - foreach ($this->PageSections() as $section) { - $p = $section->Parent(); - // Skip if the parent object doesn't exist (possibly archived) - if (!$p || !$p->ID) { - continue; - } - - $uses->add(DataObject::create([ - "Parent" => $p->Title . " (" . $p->ClassName . ")", - "Section" => $section->__Name, - "Path" => "", - ])); - } - - foreach ($this->Parents() as $parent) { - foreach ($parent->getAllUses() as $use) { - $use->Path = $use->Path . " -> " . $parent->Name; - $uses->add($use); - } - } - - return $uses; - } - - public function getCMSFields() - { - $fields = parent::getCMSFields(); - $fields->removeByName('Pages'); - $fields->removeByName('Parents'); - $fields->removeByName("PageSections"); - $fields->removeByName('__Counter'); - - $fields->removeByName("Children"); - - // Add our newest version as a readonly field - $fields->addFieldsToTab( - "Root.Main", - ReadonlyField::create("Version", "Version", $this->Version), - "Title" - ); - - // Create an array of all places this PageElement is shown - $uses = $this->getAllUses(); - - if ($uses->Count() > 0) { - $config = GridFieldConfig_Base::create() - ->removeComponentsByType(GridFieldDataColumns::class) - ->addComponent($dataColumns = new GridFieldDataColumns()); - $dataColumns->setDisplayFields([ - "Parent" => "Parent", - "Section" => "Section", - "Path" => "Path", - ]); - $gridField = GridField::create("Pages", "Uses", $uses, $config); - $fields->addFieldToTab("Root.Uses", $gridField); - } - - return $fields; - } - - /** - * Gets the list of all parents of this PageElement. - * @return string[] - */ - public function getParentIDs() - { - $IDArr = [$this->ID]; - foreach ($this->Parents() as $parent) { - $IDArr = array_merge($IDArr, $parent->getParentIDs()); - } - return $IDArr; - } - - /** - * Renders the children of this PageElement - * @param string[] $parents The list of parent IDs of this PageElement - * @return string - */ - public function renderChildren($parents = null) - { - return $this->renderWith( - "RenderChildren", - [ - "Elements" => $this->Children(), - "ParentList" => strval($this->ID) . "," . $parents, - ] - ); - } - - public function forTemplate($parentList = "") - { - $parents = ArrayList::create(); - $splits = explode(",", $parentList); - $num = count($splits); - for ($i = 0; $i < $num - 1; $i++) { - $parents->add(PageElement::get()->byID($splits[$i])); - } - $page = SiteTree::get()->byID($splits[$num - 1]); - - return $this->renderWith( - array_reverse($this->getClassAncestry()), - [ - "ParentList" => $parentList, - "Parents" => $parents, - "Page" => $page, - ] - ); - } - - public function replaceDefaultButtons() - { - return true; - } + private static $table_name = "FLXLabs_PageSections_PageElement"; + + protected static $defaultIsOpen = true; + public static $canBeRoot = true; + + public static function getSingularName() + { + return static::$singular_name; + } + public static function getPluralName() + { + return static::$plural_name; + } + public static function isOpenByDefault() + { + return static::$defaultIsOpen; + } + + public function canView($member = null) + { + return true; + } + public function canEdit($member = null) + { + return true; + } + public function canDelete($member = null) + { + return false; + } + public function canCreate($member = null, $context = []) + { + return true; + } + + private static $can_be_root = true; + + private static $db = [ + "Name" => "Varchar(255)", + "__Counter" => "Int", + ]; + + private static $many_many = [ + "Children" => [ + "through" => PageElementSelfRel::class, + "from" => "Parent", + "to" => "Child", + ], + ]; + + private static $belongs_many_many = [ + "Parents" => PageElement::class . ".Children", + "PageSections" => PageSection::class . ".Elements", + ]; + + private static $owns = [ + "Children", + ]; + + private static $cascade_deletes = [ + "Children", + ]; + + private static $summary_fields = [ + "GridFieldPreview", + ]; + + private static $searchable_fields = [ + "ClassName", + "Name", + "ID", + ]; + + // Returns all page element classes, without the base class + public static function getAllPageElementClasses() + { + $classes = array_values(ClassInfo::subclassesFor(PageElement::class)); + $classes = array_diff($classes, [PageElement::class]); + return $classes; + } + + /** + * The classes of allowed child elements + * + * Gets a list of classnames which are valid child elements of this PageElement. + * @return string[] + */ + public function getAllowedPageElements() + { + return self::getAllPageElementClasses(); + } + + public function onBeforeWrite() + { + parent::onBeforeWrite(); + + // If a field changed then update the counter, unless it's the counter that changed + $changed = $this->getChangedFields(true, DataObject::CHANGE_VALUE); + if (count($changed) > 0 && (!isset($changed["__Counter"]) || $changed["__Counter"]["level"] <= 1)) { + $this->__Counter++; + } + + $elems = $this->Children()->Sort("SortOrder")->Column("ID"); + $count = count($elems); + for ($i = 0; $i < $count; $i++) { + $this->Children()->Add($elems[$i], ["SortOrder" => ($i + 1) * 2, "__NewOrder" => true]); + } + } + + public function onAfterWrite() + { + parent::onAfterWrite(); + + if (Versioned::get_stage() == Versioned::DRAFT && $this->isChanged("__Counter", DataObject::CHANGE_VALUE)) { + foreach ($this->PageSections() as $section) { + $section->__Counter++; + $section->write(); + } + + foreach ($this->Parents() as $parent) { + $parent->__Counter++; + $parent->write(); + } + } + } + + public function onAfterDelete() + { + parent::onAfterDelete(); + + if (Versioned::get_stage() == Versioned::DRAFT) { + foreach ($this->PageSections() as $section) { + $section->__Counter++; + $section->write(); + } + } + } + + public function onAfterArchive() + { + if (Versioned::get_stage() == Versioned::DRAFT) { + foreach ($this->PageSections() as $section) { + $section->__Counter++; + $section->write(); + } + } + } + + /** + * Gets the preview of this PageElement in the TreeView. + * @return string + */ + public function getTreeViewPreview() + { + return $this->GridFieldPreview; + } + + /** + * Gets all places that this PageElement is shown in. + * + * Returns a list of objects with the following properties: + * Parent: The name and class name of the root parent object. + * Section: The name of the section on the root object where this element is shown. + * Path: The names of the parent PageElements that lead to this element. + * @return \SilverStripe\ORM\ArrayList An array of info objects + */ + public function getAllUses() + { + $uses = ArrayList::create(); + + foreach ($this->PageSections() as $section) { + $p = $section->Parent(); + // Skip if the parent object doesn't exist (possibly archived) + if (!$p || !$p->ID) { + continue; + } + + $uses->add(DataObject::create([ + "Parent" => $p->Title . " (" . $p->ClassName . ")", + "Section" => $section->__Name, + "Path" => "", + ])); + } + + foreach ($this->Parents() as $parent) { + foreach ($parent->getAllUses() as $use) { + $use->Path = $use->Path . " -> " . $parent->Name; + $uses->add($use); + } + } + + return $uses; + } + + public function getCMSFields() + { + $fields = parent::getCMSFields(); + $fields->removeByName('Pages'); + $fields->removeByName('Parents'); + $fields->removeByName("PageSections"); + $fields->removeByName('__Counter'); + + $fields->removeByName("Children"); + + // Add our newest version as a readonly field + $fields->addFieldsToTab( + "Root.Main", + ReadonlyField::create("Version", "Version", $this->Version), + "Title" + ); + + // Create an array of all places this PageElement is shown + $uses = $this->getAllUses(); + + if ($uses->Count() > 0) { + $config = GridFieldConfig_Base::create() + ->removeComponentsByType(GridFieldDataColumns::class) + ->addComponent($dataColumns = new GridFieldDataColumns()); + $dataColumns->setDisplayFields([ + "Parent" => "Parent", + "Section" => "Section", + "Path" => "Path", + ]); + $gridField = GridField::create("Pages", "Uses", $uses, $config); + $fields->addFieldToTab("Root.Uses", $gridField); + } + + return $fields; + } + + /** + * Gets the list of all parents of this PageElement. + * @return string[] + */ + public function getParentIDs() + { + $IDArr = [$this->ID]; + foreach ($this->Parents() as $parent) { + $IDArr = array_merge($IDArr, $parent->getParentIDs()); + } + return $IDArr; + } + + /** + * Renders the children of this PageElement + * @param string[] $parents The list of parent IDs of this PageElement + * @return string + */ + public function renderChildren($parents = null) + { + return $this->renderWith( + "RenderChildren", + [ + "Elements" => $this->Children(), + "ParentList" => strval($this->ID) . "," . $parents, + ] + ); + } + + public function forTemplate($parentList = "") + { + $parents = ArrayList::create(); + $splits = explode(",", $parentList); + $num = count($splits); + for ($i = 0; $i < $num - 1; $i++) { + $parents->add(PageElement::get()->byID($splits[$i])); + } + $page = SiteTree::get()->byID($splits[$num - 1]); + + return $this->renderWith( + array_reverse($this->getClassAncestry()), + [ + "ParentList" => $parentList, + "Parents" => $parents, + "Page" => $page, + ] + ); + } + + public function replaceDefaultButtons() + { + return true; + } } diff --git a/src/PageSectionsExtension.php b/src/PageSectionsExtension.php index 0430c3a..1e61519 100755 --- a/src/PageSectionsExtension.php +++ b/src/PageSectionsExtension.php @@ -180,7 +180,7 @@ public function __call($method, $arguments) } // Fix for archived errors - if (method_exists($this->owner, 'isArchived') && $this->owner->isArchived()) { + if ($this->owner->isArchived()) { return new PageSection(); } diff --git a/src/TreeView.php b/src/TreeView.php index 33f44de..74b1ee8 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -2,925 +2,921 @@ namespace FLXLabs\PageSections; -use SilverStripe\AssetAdmin\Forms\UploadField; use SilverStripe\Control\Controller; use SilverStripe\Control\HTTPRequest; -use SilverStripe\Control\Session; +use SilverStripe\Forms\FieldList; use SilverStripe\Forms\Form; use SilverStripe\Forms\FormAction; -use SilverStripe\Forms\DropdownField; -use SilverStripe\Forms\HiddenField; -use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FormField; -use SilverStripe\ORM\PaginatedList; -use SilverStripe\ORM\SS_List; +use SilverStripe\Forms\HiddenField; use SilverStripe\ORM\DataObjectInterface; +use SilverStripe\ORM\PaginatedList; use SilverStripe\View\ArrayData; use SilverStripe\View\HTML; use SilverStripe\View\Requirements; -use SilverStripe\View\ViewableData; -use SilverStripe\View\SSViewer; /** * Class TreeView */ class TreeView extends FormField { - /** - * @var string - */ - protected $sortField = 'SortOrder'; - protected $parent = null; - protected $context = null; - protected $opens = null; - - private static $allowed_actions = array( - 'index', - 'tree', - 'move', - 'add', - 'remove', - 'delete', - 'search', - 'detail', - ); - - - public function __construct($name, $title = null, $section = null, $readonly = false) - { - parent::__construct($name, $title, null); - - $this->section = $section; - $this->readonly = $readonly; - $this->context = singleton(PageElement::class)->getDefaultSearchContext(); - - if ($section) { - // Open default elements - $this->opens = new \stdClass(); - foreach ($this->getItems() as $item) { - $this->openRecursive($item); - } - } - } - - public function performReadonlyTransformation() - { - return new TreeView($this->name, $this->title, $this->section, $this->readonly); - } - - public function setValue($value, $data = null) - { - if (!$value) { - return $this; - } - - $this->section = $value; - return $this; - } - - /** - * Saves this TreeView into the specified record - * - * We do nothing here, because the TreeView saves all changes while editing, - * so there are no additional actions we have to perform here. We overwrite - * this because the default behavior would write a NULL value into the relation. - */ - public function saveInto(DataObjectInterface $record) - { } - - /** - * Recursively opens an item - * - * Recursively opens items if they have children and ->isOpenByDefault() returns true - */ - private function openRecursive($item, $parents = []) - { - if ($item->isOpenByDefault() && $item->Children()->Count()) { - $this->openItem( - array_merge( - array_map(function ($e) { - return $e->ID; - }, $parents), - [$item->ID] - ) - ); - foreach ($item->Children() as $child) { - $this->openRecursive($child, array_merge($parents, [$item])); - } - } - } - - /** - * Extracts info from an incoming request - * @param \SilverStripe\Control\HTTPRequest $request - * @return array - */ - private function pre($request) - { - $data = $request->requestVars(); - - // Protection against CSRF attacks - $token = $this->getForm()->getSecurityToken(); - if (!$token->checkRequest($request)) { - $this->httpError(400, _t( - "SilverStripe\\Forms\\Form.CSRF_FAILED_MESSAGE", - "There seems to have been a technical problem. Please click the back button, " . - "refresh your browser, and try again." - )); - return; - } - - // Restore state from session - $session = $request->getSession(); - if (isset($data["state"])) { - $this->opens = $session->get($data["state"]); - } - - return $data; - } - - public function index($request) - { - $this->pre($request); - return $this->FieldHolder(); - } - - /** - * This action is called when opening or closing an element in the tree - * @param \SilverStripe\Control\HTTPRequest $request - * @return string - */ - public function tree($request) - { - $data = $this->pre($request); - if (!$data) { - return $this->FieldHolder(); - } - - if (!isset($data["itemId"]) || !isset($data["parents"])) { - Controller::curr()->getResponse()->setStatusCode( - 400, - "Missing required data!" - ); - return $this->FieldHolder(); - } - - $itemId = intval($data["itemId"]); - $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); - $path = array_merge($parents, [$itemId]); - - if ($this->isOpen($path)) { - $this->closeItem($path); - } else { - $this->openItem($path); - } - - return $this->FieldHolder(); - } - - /** - * This action is called when an element in the tree is moved to another spot - * @param \SilverStripe\Control\HTTPRequest $request - * @return string - */ - public function move($request) - { - $data = $this->pre($request); - if (!$data) { - return $this->FieldHolder(); - } - - if ( - !isset($data["itemId"]) || !isset($data["parents"]) || - !isset($data["newParent"]) || !isset($data["sort"]) - ) { - Controller::curr()->getResponse()->setStatusCode( - 400, - "Missing required data!" - ); - return $this->FieldHolder(); - } - - $itemId = intval($data["itemId"]); - $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); - - $item = PageElement::get()->byID($itemId); - - // Get the new parent - $newParentId = $data["newParent"]; - if ($newParentId) { - $newParent = PageElement::get()->byID($newParentId); - } else { - $newParent = $this->section; - } - - // Check if this element is allowed as a child on the new element - $allowed = in_array($item->ClassName, $newParent->getAllowedPageElements()); - if (!$allowed) { - Controller::curr()->getResponse()->setStatusCode( - 400, - "The type " . $item->ClassName . " is not allowed as a child of " . $newParent->ClassName - ); - return $this->FieldHolder(); - } - - // Get the current parent - if (count($parents) == 0) { - $parent = $this->section; - } else { - $parent = PageElement::get()->byID($parents[count($parents) - 1]); - } - - // Get requested sort order - $sort = intval($data["sort"]); - $sortBy = $this->getSortField(); - $sortArr = [$sortBy => $sort]; - - // Check if we moved the element within the same parent - if ($parent->ClassName === $newParent->ClassName && $parent->ID === $newParent->ID) { - // Move the element around in the current parent - if ($newParent->ClassName == PageSection::class) { - $newParent->Elements()->Add($itemId, $sortArr); - } else { - $newParent->Children()->Add($itemId, $sortArr); - } - } else { - // Remove the element from the current parent - if (count($parents) == 0) { - $parent = $this->section; - $this->getItems()->removeByID($itemId); - } else { - $parent = PageElement::get()->byID($parents[count($parents) - 1]); - $parent->Children()->removeByID($itemId); - } - - // Add the element to the new parent - if ($newParent->ClassName == PageSection::class) { - $newParent->Elements()->Add($item, $sortArr); - } else { - $newParent->Children()->Add($item, $sortArr); - } - } - - return $this->FieldHolder(); - } - - /** - * This action is called when adding a new or an existing item - * @param \SilverStripe\Control\HTTPRequest $request - * @return string - */ - public function add($request) - { - $data = $this->pre($request); - if (!$data) { - return $this->FieldHolder(); - } - - if (!isset($data["type"]) && !isset($data["id"])) { - Controller::curr()->getResponse()->setStatusCode( - 400, - "Missing required data!" - ); - return $this->FieldHolder(); - } - - // If we have an id then add an existing item... - if (isset($data["id"])) { - $element = PageElement::get()->byID($data["id"]); - if (!$element) { - Controller::curr()->getResponse()->setStatusCode( - 400, - "Could not find PageElement with id " . $data['id'] - ); - return $this->FieldHolder(); - } - - $this->getItems()->Add($element); - } else { - // ...otherwise add a completely new item - $itemId = isset($data["itemId"]) ? intval($data["itemId"]) : null; - $type = $data["type"]; - - $child = $type::create(); - $child->Name = "New " . $child->singular_name(); - - $sort = isset($data["sort"]) ? intval($data["sort"]) : 0; - $sortBy = $this->getSortField(); - $sortArr = [$sortBy => $sort]; - - // If we have an itemId then we're adding to another element - // otherwise we're adding to the root - if ($itemId) { - $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); - $path = array_merge($parents, [$itemId]); - - $item = PageElement::get()->byID($itemId); - if (!in_array($type, $item->getAllowedPageElements())) { - Controller::curr()->getResponse()->setStatusCode( - 400, - "The type " . $type . " is not allowed as a child of " . $item->ClassName - ); - return $this->FieldHolder(); - } - - $child->write(); - $item->Children()->Add($child, $sortArr); - $item->write(); - - // Make sure we can see the child - $this->openItem(array_merge($path, [$item->ID])); - } else { - $child->write(); - $this->getItems()->Add($child, $sortArr); - } - } - - return $this->FieldHolder(); - } - - /** - * Action called when an item is removed from the TreeView - * @param \SilverStripe\Control\HTTPRequest $request - * @return string - */ - public function remove($request) - { - $data = $this->pre($request); - if (!$data) { - return $this->FieldHolder(); - } - - if (!isset($data["itemId"]) || !isset($data["parents"])) { - Controller::curr()->getResponse()->setStatusCode( - 400, - "Missing required data!" - ); - return $this->FieldHolder(); - } - - $itemId = intval($data["itemId"]); - $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); - - // We only need the parent directly above the child, all parents further up don't matter, - // because the relations are duplicated if the item is duplicated. - // If we have no parents then we're removing it from the root - if (count($parents) == 0) { - $this->getItems()->removeByID($itemId); - } else { - $parent = PageElement::get()->byID($parents[count($parents) - 1]); - $parent->Children()->removeByID($itemId); - } - - return $this->FieldHolder(); - } - - /** - * This action is called when an element is deleted - * @param \SilverStripe\Control\HTTPRequest $request - * @return string - */ - public function delete($request) - { - $data = $this->pre($request); - if (!$data) { - return $this->FieldHolder(); - } - - if (!isset($data["itemId"]) || !isset($data["parents"])) { - Controller::curr()->getResponse()->setStatusCode( - 400, - "Missing required data!" - ); - return $this->FieldHolder(); - } - - $itemId = intval($data["itemId"]); - $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); - $path = array_merge($parents, [$itemId]); - - $item = PageElement::get()->byID($itemId); - - // Close the element in case it's open to avoid errors - $this->closeItem($path); - - // let's remove all relations - $this->getItems()->removeByID($itemId); - foreach ($parents as $parentId) { - $parent = PageElement::get()->byID($parentId); - if ($parent) $parent->Children()->removeByID($itemId); - } - - // Delete the element - $item->delete(); - - return $this->FieldHolder(); - } - - /** - * This action is called when the find existing dialog is shown. - * @param \SilverStripe\Control\HTTPRequest $request - * @return string - */ - public function search($request) - { - $form = Form::create( - $this, - 'search', - $this->context->getFields(), - FieldList::create( - FormAction::create('doSearch', _t('GridFieldExtensions.SEARCH', 'Search')) - ->setUseButtonTag(true) - ->addExtraClass('btn btn-primary font-icon-search') - ) - ); - $form->addExtraClass('stacked add-existing-search-form form--no-dividers'); - $form->setFormMethod('GET'); - - // Check if we're requesting the form for the first time (we return the template) - // or if this is a submission (we return the form, so it calls the submitted action) - if (count($request->requestVars()) === 0) { - return $form->forAjaxTemplate(); - } - return $form; - } - - /** - * This action is called when a search is performed in the find existing dialog - * @param \SilverStripe\Control\HTTPRequest $request - * @return string - */ - public function doSearch($data, $form) - { - $list = $this->context->getQuery($data, false, false); - $allowed = $this->section->getAllowedPageElements(); - // Remove all disallowed classes - $list = $list->filter("ClassName", $allowed); - $list = new PaginatedList($list, $data); - $data = $this->customise([ - 'SearchForm' => $form, - 'Items' => $list - ]); - return $data->renderWith("FLXLabs\PageSections\TreeViewFindExistingForm"); - } - - /** - * Creates a detail edit form for the specified item - * @param \FLXLabs\PageSections\PageElement $item - * @param bool $loadData True if the data from $item should be loaded into the form, false otherwise. - * @return \SilverStripe\Forms\Form - */ - public function DetailForm(PageElement $item, bool $loadData = true) - { - $canEdit = $item->canEdit(); - $canDelete = $item->canDelete(); - - $actions = new FieldList(); - if ($canEdit) { - $actions->push(FormAction::create('doSave', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Save', 'Save')) - ->setUseButtonTag(true) - ->addExtraClass('btn-primary font-icon-save')); - } - if ($canDelete) { - $actions->push(FormAction::create('doDelete', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Delete', 'Delete')) - ->setUseButtonTag(true) - ->addExtraClass('btn-outline-danger btn-hide-outline font-icon-trash-bin action-delete')); - } - - $fields = $item->getCMSFields(); - $fields->addFieldToTab("Root.Main", HiddenField::create("ID", "ID", $item->ID)); - - $form = Form::create( - $this, - 'detail', - $fields, - $actions - ); - if ($loadData) { - $form->loadDataFrom($item, Form::MERGE_DEFAULT); - } - - $form->setTemplate([ - 'type' => 'Includes', - 'SilverStripe\\Admin\\LeftAndMain_EditForm', - ]); - $form->addExtraClass( - 'view-detail-form cms-content cms-edit-form center fill-height flexbox-area-grow' - ); - if ($form->Fields()->hasTabSet()) { - $form->Fields()->findOrMakeTab('Root')->setTemplate('SilverStripe\\Forms\\CMSTabSet'); - $form->addExtraClass('cms-tabset'); - } - - return $form; - } - - /** - * This action is called when the detail form for an item is opened. - * @param \SilverStripe\Control\HTTPRequest $request - * @return string - */ - public function detail($request) - { - $id = intval($request->requestVar("ID")); - if ($id) { - $request->getSession()->set("ElementID", $id); - } else { - $id = $request->getSession()->get("ElementID"); - } - - // This is a request to show the form so we return it as a template so - // that SilverStripe doesn't think this is already a submission - // (it would call the first action on the form) - if ($request->isGET()) { - $item = PageElement::get()->byID($id); - if (!$item) { - return $this->httpError(404); - } - $form = $this->DetailForm($item); - // Save the id of the page element on this form's security token - //$request->getSession()->set("_tv_df_" . $form->getSecurityToken()->getValue(), $id); - return $form->forTemplate(); - } - - // If it's a POST request then it's a submission and we have to get the ID - // from the session using the form's security token. - //$id = $request->getSession()->get("_tv_df_" . $request->requestVar("SecurityID")); - $item = PageElement::get()->byID($id); - return $this->DetailForm($item, false); - } - - public function handleRequest(HTTPRequest $request) - { - $this->setRequest($request); - - // Forward requests to the elements in the detail form to their respective controller - if ($request->match('detail/$ID!')) { - $id = $request->getSession()->get("ElementID"); - $item = PageElement::get()->byID($id); - $form = $this->DetailForm($item); - $request->shift(1); - return $form->getRequestHandler()->handleRequest($request); - } - - return parent::handleRequest($request); - } - - /** - * This action is called when the detail form is submitted (saved/deleted) - * @param \SilverStripe\Control\HTTPRequest $request - * @return string - */ - public function doSave($data, $form) - { - $id = intval($data["ID"]); - $item = PageElement::get()->byID($id); - - $form->saveInto($item); - $item->write(); - } - - /** - * Get base items - * - * Gets all the top level items of this TreeView. - * @return \SilverStripe\ORM\ArrayList - */ - public function getItems() - { - return $this->section->Elements(); - /*return $this->parent->ClassName == PageSection::class ? - $this->parent->Elements() : $this->parent->Children();*/ - } - - /** - * Gets the sort field - * - * Gets the name of the field by which items are sorted. - * @return string - */ - public function getSortField() - { - return $this->sortField; - } - - /** - * Gets the directory name of this module - * - * @return string - */ - public static function getModuleDir() - { - return basename(dirname(__DIR__)); - } - - /** - * Renders this TreeView as an HTML tag - * @param array $properties The additional properties for the TreeView - * @return string - */ - public function FieldHolder($properties = array()) - { - $moduleDir = self::getModuleDir(); - Requirements::css($moduleDir . "/css/TreeView.css"); - Requirements::javascript($moduleDir . "/javascript/TreeView.js"); - Requirements::add_i18n_javascript($moduleDir . '/javascript/lang', false, true); - - // Ensure $id doesn't contain only numeric characters - $sessionId = 'ps_tv_' . substr(md5(serialize($this->opens)), 0, 8); - $session = Controller::curr()->getRequest()->getSession(); - $session->set($sessionId, $this->opens); - - $content = '
                                                              '; - - $classes = $this->section->getAllowedPageElements(); - $elems = []; - foreach ($classes as $class) { - $elems[$class] = singleton($class)->singular_name(); - } - - if (!$this->readonly) { - // Create the add new button at the very top - $addButton = TreeViewFormAction::create( - $this, - "AddActionBase", - null, - null, - null - ); - $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); - $addButton->addExtraClass("btn add-button font-icon-plus"); - if (!count($elems)) { - $addButton->setDisabled(true); - } - $addButton->setButtonContent(' '); - $content .= ArrayData::create([ - "Button" => $addButton - ])->renderWith("\FLXLabs\PageSections\TreeViewAddNewButton"); - - // Create the find existing button - $findExisting = TreeViewFormAction::create($this, 'FindExisting', 'Find existing'); - $findExisting->addExtraClass("btn font-icon-search tree-actions-findexisting"); - $content .= $findExisting->forTemplate(); - } - - $content .= "
                                                              "; - - $list = $this->getItems()->sort($this->sortField)->toArray(); - - $first = true; - foreach ($list as $item) { - $content .= $this->renderTree($item, [], $this->opens, $first); - $first = false; - } - - return HTML::createTag( - 'fieldset', - [ - 'class' => 'treeview-pagesections pagesection-' . $this->getName(), - 'data-readonly' => $this->readonly, - 'data-name' => $this->getName(), - 'data-url' => !$this->readonly ? $this->Link() : null, - 'data-state-id' => $sessionId, - 'data-allowed-elements' => json_encode($elems), - ], - $content - ); - } - - public function Field($properties = array()) - { - return $this->FieldHolder($properties); - } - - /** - * Renders an item tree - * - * Renders the specified item and it's children - * @param PageElement $item The item to render - * @param string[] $parents The hierarchy of parents this item is a child of - * @param \Stdclass $opens The local open state for the item - * @param boolean $isFirst True if this is the first item of the direct parent, false otherwise - */ - private function renderTree($item, $parents, $opens, $isFirst) - { - $childContent = null; - $level = count($parents) + 1; - $isOpen = isset($opens->{$item->ID}) && $item->Children()->Count() > 0; - - // Render children if we are open - if ($isOpen) { - $children = $item->Children()->Sort($this->sortField); - $first = true; - foreach ($children as $child) { - $childContent .= $this->renderTree( - $child, - array_merge($parents, [$item]), - $opens->{$item->ID}, - $first - ); - $first = false; - } - } - - // Get the list of parents of this element as an array of ids - // (already converted to json/a string) - $tree = "[" . - implode( - ',', - array_map( - function ($item) { - return $item->ID; - }, - $parents - ) - ) . - "]"; - - // Construct the array of all allowed child elements - $classes = $item->getAllowedPageElements(); - $elems = []; - foreach ($classes as $class) { - $elems[$class] = singleton($class)->singular_name(); - } - - // Construct the array of all allowed child elements in parent slot - $parentClasses = count($parents) > 0 - ? $parents[count($parents) - 1]->getAllowedPageElements() - : $this->section->getAllowedPageElements(); - $parentElems = []; - foreach ($parentClasses as $class) { - $parentElems[$class] = singleton($class)->singular_name(); - } - - // Find out if this item is allowed as a root item - // There are two cases, either this GridField is on a page, - // or it is on a PageElement and we're looking at the children - $isAllowedRoot = in_array($item->ClassName, $parentClasses); - - // Create a button to add a new child element - // and save the allowed child classes on the button - if (!$this->readonly && count($classes)) { - $addButton = TreeViewFormAction::create( - $this, - "AddAction" . $item->ID, - null, - null, - null - ); - $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); - $addButton->addExtraClass("btn add-button font-icon-plus"); - if (!count($elems)) { - $addButton->setDisabled(true); - } - $addButton->setButtonContent(' '); - } - - if (!$this->readonly) { - // Create a button to add an element after - // and save the allowed child classes on the button - $addAfterButton = TreeViewFormAction::create( - $this, - "AddAfterAction" . $item->ID, - null, - null, - null - ); - $addAfterButton->setAttribute( - "data-allowed-elements", - json_encode($parentElems, JSON_UNESCAPED_UNICODE) - ); - $addAfterButton->addExtraClass("btn add-after-button font-icon-plus"); - if (!count($parentElems)) { - $addAfterButton->setDisabled(true); - } - $addAfterButton->setButtonContent(' '); - - // Create a button to delete and/or remove the element from the parent - $deleteButton = TreeViewFormAction::create( - $this, - "DeleteAction" . $item->ID, - null, - null, - null - ); - $deleteButton->setAttribute( - "data-used-count", - $item->getAllUses()->Count() - ); - $deleteButton->addExtraClass("btn delete-button font-icon-trash-bin"); - $deleteButton->setButtonContent('Delete'); - - // Create a button to edit the record - $editButton = TreeViewFormAction::create( - $this, - "EditAction" . $item->ID, - null, - null, - null - ); - $editButton->addExtraClass("btn edit-button font-icon-edit"); - $editButton->setButtonContent('Edit'); - } - - // Create the tree icon - $icon = ''; - if (!$this->readonly && $item->Children() && $item->Children()->Count() > 0) { - $icon = ($isOpen === true ? 'font-icon-down-open' : 'font-icon-right-open'); - } - - // Create the tree field - $treeButton = TreeViewFormAction::create( - $this, - "TreeNavAction" . $item->ID, - null, - "dotreenav", - ["element" => $item] - ); - $treeButton->addExtraClass("tree-button treeview-item__treeswitch__button btn " . ($isOpen ? "is-open" : "is-closed")); - if (!$item->Children()->Count()) { - $treeButton->addExtraClass(" is-end"); - $treeButton->setDisabled(true); - } - $treeButton->addExtraClass($icon); - $treeButton->setButtonContent(' '); - - return ArrayData::create([ - "Readonly" => $this->readonly, - "Item" => $item, - "Tree" => $tree, - "IsOpen" => $isOpen, - "IsFirst" => $isFirst, - "Children" => $childContent, - "AllowedRoot" => $isAllowedRoot, - "AllowedElements" => json_encode($elems, JSON_UNESCAPED_UNICODE), - "TreeButton" => $treeButton, - "AddButton" => isset($addButton) ? $addButton : null, - "AddAfterButton" => isset($addAfterButton) ? $addAfterButton : null, - "EditButton" => isset($editButton) ? $editButton : null, - "DeleteButton" => isset($deleteButton) ? $deleteButton : null, - "UsedCount" => $item->getAllUses()->Count() - ])->renderWith("\FLXLabs\PageSections\TreeViewPageElement"); - } - - /** - * Checks if the specified item is open - * - * @param string[] $path The hierarchy of item ids, the last being the item to check. - * @return boolean - */ - private function isOpen($path) - { - $opens = $this->opens; - foreach ($path as $itemId) { - if (!isset($opens->{$itemId})) { - return false; - } - - $opens = $opens->{$itemId}; - } - - return true; - } - - /** - * Opens an item - * - * Opens the item at the specified path - * @param string[] $path The hierarchy of item ids, the last being the item to open. - */ - private function openItem($path) - { - $opens = $this->opens; - foreach ($path as $itemId) { - if (!isset($opens->{$itemId})) { - $opens->{$itemId} = new \stdClass(); - } - - $opens = $opens->{$itemId}; - } - } - - /** - * Closes an item - * - * Closes the item at the specified path - * @param string[] $path The hierarchy of item ids, the last being the item to close. - */ - private function closeItem($path) - { - $opens = $this->opens; - for ($i = 0; $i < count($path) - 1; $i++) { - if (!isset($opens->{$path[$i]})) { - return; - } - - $opens = $opens->{$path[$i]}; - } - - unset($opens->{$path[count($path) - 1]}); - } + /** + * @var string + */ + protected $sortField = 'SortOrder'; + protected $parent = null; + protected $context = null; + protected $opens = null; + + private static $allowed_actions = array( + 'index', + 'tree', + 'move', + 'add', + 'remove', + 'delete', + 'search', + 'detail', + ); + + public function __construct($name, $title = null, $section = null, $readonly = false) + { + parent::__construct($name, $title, null); + + $this->section = $section; + $this->readonly = $readonly; + $this->context = singleton(PageElement::class)->getDefaultSearchContext(); + + if ($section) { + // Open default elements + $this->opens = new \stdClass(); + foreach ($this->getItems() as $item) { + $this->openRecursive($item); + } + } + } + + public function performReadonlyTransformation() + { + return new TreeView($this->name, $this->title, $this->section, $this->readonly); + } + + public function setValue($value, $data = null) + { + if (!$value) { + return $this; + } + + $this->section = $value; + return $this; + } + + /** + * Saves this TreeView into the specified record + * + * We do nothing here, because the TreeView saves all changes while editing, + * so there are no additional actions we have to perform here. We overwrite + * this because the default behavior would write a NULL value into the relation. + */ + public function saveInto(DataObjectInterface $record) + {} + + /** + * Recursively opens an item + * + * Recursively opens items if they have children and ->isOpenByDefault() returns true + */ + private function openRecursive($item, $parents = []) + { + if ($item->isOpenByDefault() && $item->Children()->Count()) { + $this->openItem( + array_merge( + array_map(function ($e) { + return $e->ID; + }, $parents), + [$item->ID] + ) + ); + foreach ($item->Children() as $child) { + $this->openRecursive($child, array_merge($parents, [$item])); + } + } + } + + /** + * Extracts info from an incoming request + * @param \SilverStripe\Control\HTTPRequest $request + * @return array + */ + private function pre($request) + { + $data = $request->requestVars(); + + // Protection against CSRF attacks + $token = $this->getForm()->getSecurityToken(); + if (!$token->checkRequest($request)) { + $this->httpError(400, _t( + "SilverStripe\\Forms\\Form.CSRF_FAILED_MESSAGE", + "There seems to have been a technical problem. Please click the back button, " . + "refresh your browser, and try again." + )); + return; + } + + // Restore state from session + $session = $request->getSession(); + if (isset($data["state"])) { + $this->opens = $session->get($data["state"]); + } + + return $data; + } + + public function index($request) + { + $this->pre($request); + return $this->FieldHolder(); + } + + /** + * This action is called when opening or closing an element in the tree + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function tree($request) + { + $data = $this->pre($request); + if (!$data) { + return $this->FieldHolder(); + } + + if (!isset($data["itemId"]) || !isset($data["parents"])) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "Missing required data!" + ); + return $this->FieldHolder(); + } + + $itemId = intval($data["itemId"]); + $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); + $path = array_merge($parents, [$itemId]); + + if ($this->isOpen($path)) { + $this->closeItem($path); + } else { + $this->openItem($path); + } + + return $this->FieldHolder(); + } + + /** + * This action is called when an element in the tree is moved to another spot + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function move($request) + { + $data = $this->pre($request); + if (!$data) { + return $this->FieldHolder(); + } + + if ( + !isset($data["itemId"]) || !isset($data["parents"]) || + !isset($data["newParent"]) || !isset($data["sort"]) + ) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "Missing required data!" + ); + return $this->FieldHolder(); + } + + $itemId = intval($data["itemId"]); + $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); + + $item = PageElement::get()->byID($itemId); + + // Get the new parent + $newParentId = $data["newParent"]; + if ($newParentId) { + $newParent = PageElement::get()->byID($newParentId); + } else { + $newParent = $this->section; + } + + // Check if this element is allowed as a child on the new element + $allowed = in_array($item->ClassName, $newParent->getAllowedPageElements()); + if (!$allowed) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "The type " . $item->ClassName . " is not allowed as a child of " . $newParent->ClassName + ); + return $this->FieldHolder(); + } + + // Get the current parent + if (count($parents) == 0) { + $parent = $this->section; + } else { + $parent = PageElement::get()->byID($parents[count($parents) - 1]); + } + + // Get requested sort order + $sort = intval($data["sort"]); + $sortBy = $this->getSortField(); + $sortArr = [$sortBy => $sort]; + + // Check if we moved the element within the same parent + if ($parent->ClassName === $newParent->ClassName && $parent->ID === $newParent->ID) { + // Move the element around in the current parent + if ($newParent->ClassName == PageSection::class) { + $newParent->Elements()->Add($itemId, $sortArr); + } else { + $newParent->Children()->Add($itemId, $sortArr); + } + } else { + // Remove the element from the current parent + if (count($parents) == 0) { + $parent = $this->section; + $this->getItems()->removeByID($itemId); + } else { + $parent = PageElement::get()->byID($parents[count($parents) - 1]); + $parent->Children()->removeByID($itemId); + } + + // Add the element to the new parent + if ($newParent->ClassName == PageSection::class) { + $newParent->Elements()->Add($item, $sortArr); + } else { + $newParent->Children()->Add($item, $sortArr); + } + } + + return $this->FieldHolder(); + } + + /** + * This action is called when adding a new or an existing item + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function add($request) + { + $data = $this->pre($request); + if (!$data) { + return $this->FieldHolder(); + } + + if (!isset($data["type"]) && !isset($data["id"])) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "Missing required data!" + ); + return $this->FieldHolder(); + } + + // If we have an id then add an existing item... + if (isset($data["id"])) { + $element = PageElement::get()->byID($data["id"]); + if (!$element) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "Could not find PageElement with id " . $data['id'] + ); + return $this->FieldHolder(); + } + + $this->getItems()->Add($element); + } else { + // ...otherwise add a completely new item + $itemId = isset($data["itemId"]) ? intval($data["itemId"]) : null; + $type = $data["type"]; + + $child = $type::create(); + $child->Name = "New " . $child->singular_name(); + + $sort = isset($data["sort"]) ? intval($data["sort"]) : 0; + $sortBy = $this->getSortField(); + $sortArr = [$sortBy => $sort]; + + // If we have an itemId then we're adding to another element + // otherwise we're adding to the root + if ($itemId) { + $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); + $path = array_merge($parents, [$itemId]); + + $item = PageElement::get()->byID($itemId); + if (!in_array($type, $item->getAllowedPageElements())) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "The type " . $type . " is not allowed as a child of " . $item->ClassName + ); + return $this->FieldHolder(); + } + + $child->write(); + $item->Children()->Add($child, $sortArr); + $item->write(); + + // Make sure we can see the child + $this->openItem(array_merge($path, [$item->ID])); + } else { + $child->write(); + $this->getItems()->Add($child, $sortArr); + } + } + + return $this->FieldHolder(); + } + + /** + * Action called when an item is removed from the TreeView + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function remove($request) + { + $data = $this->pre($request); + if (!$data) { + return $this->FieldHolder(); + } + + if (!isset($data["itemId"]) || !isset($data["parents"])) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "Missing required data!" + ); + return $this->FieldHolder(); + } + + $itemId = intval($data["itemId"]); + $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); + + // We only need the parent directly above the child, all parents further up don't matter, + // because the relations are duplicated if the item is duplicated. + // If we have no parents then we're removing it from the root + if (count($parents) == 0) { + $this->getItems()->removeByID($itemId); + } else { + $parent = PageElement::get()->byID($parents[count($parents) - 1]); + $parent->Children()->removeByID($itemId); + } + + return $this->FieldHolder(); + } + + /** + * This action is called when an element is deleted + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function delete($request) + { + $data = $this->pre($request); + if (!$data) { + return $this->FieldHolder(); + } + + if (!isset($data["itemId"]) || !isset($data["parents"])) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "Missing required data!" + ); + return $this->FieldHolder(); + } + + $itemId = intval($data["itemId"]); + $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); + $path = array_merge($parents, [$itemId]); + + $item = PageElement::get()->byID($itemId); + + // Close the element in case it's open to avoid errors + $this->closeItem($path); + + // let's remove all relations + $this->getItems()->removeByID($itemId); + foreach ($parents as $parentId) { + $parent = PageElement::get()->byID($parentId); + if ($parent) { + $parent->Children()->removeByID($itemId); + } + + } + + // Delete the element + $item->doArchive(); + + return $this->FieldHolder(); + } + + /** + * This action is called when the find existing dialog is shown. + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function search($request) + { + $form = Form::create( + $this, + 'search', + $this->context->getFields(), + FieldList::create( + FormAction::create('doSearch', _t('GridFieldExtensions.SEARCH', 'Search')) + ->setUseButtonTag(true) + ->addExtraClass('btn btn-primary font-icon-search') + ) + ); + $form->addExtraClass('stacked add-existing-search-form form--no-dividers'); + $form->setFormMethod('GET'); + + // Check if we're requesting the form for the first time (we return the template) + // or if this is a submission (we return the form, so it calls the submitted action) + if (count($request->requestVars()) === 0) { + return $form->forAjaxTemplate(); + } + return $form; + } + + /** + * This action is called when a search is performed in the find existing dialog + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function doSearch($data, $form) + { + $list = $this->context->getQuery($data, false, false); + $allowed = $this->section->getAllowedPageElements(); + // Remove all disallowed classes + $list = $list->filter("ClassName", $allowed); + $list = new PaginatedList($list, $data); + $data = $this->customise([ + 'SearchForm' => $form, + 'Items' => $list, + ]); + return $data->renderWith("FLXLabs\PageSections\TreeViewFindExistingForm"); + } + + /** + * Creates a detail edit form for the specified item + * @param \FLXLabs\PageSections\PageElement $item + * @param bool $loadData True if the data from $item should be loaded into the form, false otherwise. + * @return \SilverStripe\Forms\Form + */ + public function DetailForm(PageElement $item, bool $loadData = true) + { + $canEdit = $item->canEdit(); + $canDelete = $item->canDelete(); + + $actions = new FieldList(); + if ($canEdit) { + $actions->push(FormAction::create('doSave', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Save', 'Save')) + ->setUseButtonTag(true) + ->addExtraClass('btn-primary font-icon-save')); + } + if ($canDelete) { + $actions->push(FormAction::create('doDelete', _t('SilverStripe\\Forms\\GridField\\GridFieldDetailForm.Delete', 'Delete')) + ->setUseButtonTag(true) + ->addExtraClass('btn-outline-danger btn-hide-outline font-icon-trash-bin action-delete')); + } + + $fields = $item->getCMSFields(); + $fields->addFieldToTab("Root.Main", HiddenField::create("ID", "ID", $item->ID)); + + $form = Form::create( + $this, + 'detail', + $fields, + $actions + ); + if ($loadData) { + $form->loadDataFrom($item, Form::MERGE_DEFAULT); + } + + $form->setTemplate([ + 'type' => 'Includes', + 'SilverStripe\\Admin\\LeftAndMain_EditForm', + ]); + $form->addExtraClass( + 'view-detail-form cms-content cms-edit-form center fill-height flexbox-area-grow' + ); + if ($form->Fields()->hasTabSet()) { + $form->Fields()->findOrMakeTab('Root')->setTemplate('SilverStripe\\Forms\\CMSTabSet'); + $form->addExtraClass('cms-tabset'); + } + + return $form; + } + + /** + * This action is called when the detail form for an item is opened. + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function detail($request) + { + $id = intval($request->requestVar("ID")); + if ($id) { + $request->getSession()->set("ElementID", $id); + } else { + $id = $request->getSession()->get("ElementID"); + } + + // This is a request to show the form so we return it as a template so + // that SilverStripe doesn't think this is already a submission + // (it would call the first action on the form) + if ($request->isGET()) { + $item = PageElement::get()->byID($id); + if (!$item) { + return $this->httpError(404); + } + $form = $this->DetailForm($item); + // Save the id of the page element on this form's security token + //$request->getSession()->set("_tv_df_" . $form->getSecurityToken()->getValue(), $id); + return $form->forTemplate(); + } + + // If it's a POST request then it's a submission and we have to get the ID + // from the session using the form's security token. + //$id = $request->getSession()->get("_tv_df_" . $request->requestVar("SecurityID")); + $item = PageElement::get()->byID($id); + return $this->DetailForm($item, false); + } + + public function handleRequest(HTTPRequest $request) + { + $this->setRequest($request); + + // Forward requests to the elements in the detail form to their respective controller + if ($request->match('detail/$ID!')) { + $id = $request->getSession()->get("ElementID"); + $item = PageElement::get()->byID($id); + $form = $this->DetailForm($item); + $request->shift(1); + return $form->getRequestHandler()->handleRequest($request); + } + + return parent::handleRequest($request); + } + + /** + * This action is called when the detail form is submitted (saved/deleted) + * @param \SilverStripe\Control\HTTPRequest $request + * @return string + */ + public function doSave($data, $form) + { + $id = intval($data["ID"]); + $item = PageElement::get()->byID($id); + + $form->saveInto($item); + $item->write(); + } + + /** + * Get base items + * + * Gets all the top level items of this TreeView. + * @return \SilverStripe\ORM\ArrayList + */ + public function getItems() + { + return $this->section->Elements(); + /*return $this->parent->ClassName == PageSection::class ? + $this->parent->Elements() : $this->parent->Children();*/ + } + + /** + * Gets the sort field + * + * Gets the name of the field by which items are sorted. + * @return string + */ + public function getSortField() + { + return $this->sortField; + } + + /** + * Gets the directory name of this module + * + * @return string + */ + public static function getModuleDir() + { + return basename(dirname(__DIR__)); + } + + /** + * Renders this TreeView as an HTML tag + * @param array $properties The additional properties for the TreeView + * @return string + */ + public function FieldHolder($properties = array()) + { + $moduleDir = self::getModuleDir(); + Requirements::css($moduleDir . "/css/TreeView.css"); + Requirements::javascript($moduleDir . "/javascript/TreeView.js"); + Requirements::add_i18n_javascript($moduleDir . '/javascript/lang', false, true); + + // Ensure $id doesn't contain only numeric characters + $sessionId = 'ps_tv_' . substr(md5(serialize($this->opens)), 0, 8); + $session = Controller::curr()->getRequest()->getSession(); + $session->set($sessionId, $this->opens); + + $content = '
                                                              '; + + $classes = $this->section->getAllowedPageElements(); + $elems = []; + foreach ($classes as $class) { + $elems[$class] = singleton($class)->singular_name(); + } + + if (!$this->readonly) { + // Create the add new button at the very top + $addButton = TreeViewFormAction::create( + $this, + "AddActionBase", + null, + null, + null + ); + $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); + $addButton->addExtraClass("btn add-button font-icon-plus"); + if (!count($elems)) { + $addButton->setDisabled(true); + } + $addButton->setButtonContent(' '); + $content .= ArrayData::create([ + "Button" => $addButton, + ])->renderWith("\FLXLabs\PageSections\TreeViewAddNewButton"); + + // Create the find existing button + $findExisting = TreeViewFormAction::create($this, 'FindExisting', 'Find existing'); + $findExisting->addExtraClass("btn font-icon-search tree-actions-findexisting"); + $content .= $findExisting->forTemplate(); + } + + $content .= "
                                                              "; + + $list = $this->getItems()->sort($this->sortField)->toArray(); + + $first = true; + foreach ($list as $item) { + $content .= $this->renderTree($item, [], $this->opens, $first); + $first = false; + } + + return HTML::createTag( + 'fieldset', + [ + 'class' => 'treeview-pagesections pagesection-' . $this->getName(), + 'data-readonly' => $this->readonly, + 'data-name' => $this->getName(), + 'data-url' => !$this->readonly ? $this->Link() : null, + 'data-state-id' => $sessionId, + 'data-allowed-elements' => json_encode($elems), + ], + $content + ); + } + + public function Field($properties = array()) + { + return $this->FieldHolder($properties); + } + + /** + * Renders an item tree + * + * Renders the specified item and it's children + * @param PageElement $item The item to render + * @param string[] $parents The hierarchy of parents this item is a child of + * @param \Stdclass $opens The local open state for the item + * @param boolean $isFirst True if this is the first item of the direct parent, false otherwise + */ + private function renderTree($item, $parents, $opens, $isFirst) + { + $childContent = null; + $level = count($parents) + 1; + $isOpen = isset($opens->{$item->ID}) && $item->Children()->Count() > 0; + + // Render children if we are open + if ($isOpen) { + $children = $item->Children()->Sort($this->sortField); + $first = true; + foreach ($children as $child) { + $childContent .= $this->renderTree( + $child, + array_merge($parents, [$item]), + $opens->{$item->ID}, + $first + ); + $first = false; + } + } + + // Get the list of parents of this element as an array of ids + // (already converted to json/a string) + $tree = "[" . + implode( + ',', + array_map( + function ($item) { + return $item->ID; + }, + $parents + ) + ) . + "]"; + + // Construct the array of all allowed child elements + $classes = $item->getAllowedPageElements(); + $elems = []; + foreach ($classes as $class) { + $elems[$class] = singleton($class)->singular_name(); + } + + // Construct the array of all allowed child elements in parent slot + $parentClasses = count($parents) > 0 + ? $parents[count($parents) - 1]->getAllowedPageElements() + : $this->section->getAllowedPageElements(); + $parentElems = []; + foreach ($parentClasses as $class) { + $parentElems[$class] = singleton($class)->singular_name(); + } + + // Find out if this item is allowed as a root item + // There are two cases, either this GridField is on a page, + // or it is on a PageElement and we're looking at the children + $isAllowedRoot = in_array($item->ClassName, $parentClasses); + + // Create a button to add a new child element + // and save the allowed child classes on the button + if (!$this->readonly && count($classes)) { + $addButton = TreeViewFormAction::create( + $this, + "AddAction" . $item->ID, + null, + null, + null + ); + $addButton->setAttribute("data-allowed-elements", json_encode($elems, JSON_UNESCAPED_UNICODE)); + $addButton->addExtraClass("btn add-button font-icon-plus"); + if (!count($elems)) { + $addButton->setDisabled(true); + } + $addButton->setButtonContent(' '); + } + + if (!$this->readonly) { + // Create a button to add an element after + // and save the allowed child classes on the button + $addAfterButton = TreeViewFormAction::create( + $this, + "AddAfterAction" . $item->ID, + null, + null, + null + ); + $addAfterButton->setAttribute( + "data-allowed-elements", + json_encode($parentElems, JSON_UNESCAPED_UNICODE) + ); + $addAfterButton->addExtraClass("btn add-after-button font-icon-plus"); + if (!count($parentElems)) { + $addAfterButton->setDisabled(true); + } + $addAfterButton->setButtonContent(' '); + + // Create a button to delete and/or remove the element from the parent + $deleteButton = TreeViewFormAction::create( + $this, + "DeleteAction" . $item->ID, + null, + null, + null + ); + $deleteButton->setAttribute( + "data-used-count", + $item->getAllUses()->Count() + ); + $deleteButton->addExtraClass("btn delete-button font-icon-trash-bin"); + $deleteButton->setButtonContent('Delete'); + + // Create a button to edit the record + $editButton = TreeViewFormAction::create( + $this, + "EditAction" . $item->ID, + null, + null, + null + ); + $editButton->addExtraClass("btn edit-button font-icon-edit"); + $editButton->setButtonContent('Edit'); + } + + // Create the tree icon + $icon = ''; + if (!$this->readonly && $item->Children() && $item->Children()->Count() > 0) { + $icon = ($isOpen === true ? 'font-icon-down-open' : 'font-icon-right-open'); + } + + // Create the tree field + $treeButton = TreeViewFormAction::create( + $this, + "TreeNavAction" . $item->ID, + null, + "dotreenav", + ["element" => $item] + ); + $treeButton->addExtraClass("tree-button treeview-item__treeswitch__button btn " . ($isOpen ? "is-open" : "is-closed")); + if (!$item->Children()->Count()) { + $treeButton->addExtraClass(" is-end"); + $treeButton->setDisabled(true); + } + $treeButton->addExtraClass($icon); + $treeButton->setButtonContent(' '); + + return ArrayData::create([ + "Readonly" => $this->readonly, + "Item" => $item, + "Tree" => $tree, + "IsOpen" => $isOpen, + "IsFirst" => $isFirst, + "Children" => $childContent, + "AllowedRoot" => $isAllowedRoot, + "AllowedElements" => json_encode($elems, JSON_UNESCAPED_UNICODE), + "TreeButton" => $treeButton, + "AddButton" => isset($addButton) ? $addButton : null, + "AddAfterButton" => isset($addAfterButton) ? $addAfterButton : null, + "EditButton" => isset($editButton) ? $editButton : null, + "DeleteButton" => isset($deleteButton) ? $deleteButton : null, + "UsedCount" => $item->getAllUses()->Count(), + ])->renderWith("\FLXLabs\PageSections\TreeViewPageElement"); + } + + /** + * Checks if the specified item is open + * + * @param string[] $path The hierarchy of item ids, the last being the item to check. + * @return boolean + */ + private function isOpen($path) + { + $opens = $this->opens; + foreach ($path as $itemId) { + if (!isset($opens->{$itemId})) { + return false; + } + + $opens = $opens->{$itemId}; + } + + return true; + } + + /** + * Opens an item + * + * Opens the item at the specified path + * @param string[] $path The hierarchy of item ids, the last being the item to open. + */ + private function openItem($path) + { + $opens = $this->opens; + foreach ($path as $itemId) { + if (!isset($opens->{$itemId})) { + $opens->{$itemId} = new \stdClass(); + } + + $opens = $opens->{$itemId}; + } + } + + /** + * Closes an item + * + * Closes the item at the specified path + * @param string[] $path The hierarchy of item ids, the last being the item to close. + */ + private function closeItem($path) + { + $opens = $this->opens; + for ($i = 0; $i < count($path) - 1; $i++) { + if (!isset($opens->{$path[$i]})) { + return; + } + + $opens = $opens->{$path[$i]}; + } + + unset($opens->{$path[count($path) - 1]}); + } } class TreeView_Readonly extends TreeView -{ } +{} From 36d616749d58301e44cfccc1f84f506a6ee2f4c9 Mon Sep 17 00:00:00 2001 From: Severin Hauser Date: Tue, 10 Jan 2023 15:39:57 +0100 Subject: [PATCH 71/82] fix(treeview): load jQuery migrate if used jQuery version is >= 3 --- javascript/TreeView.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/javascript/TreeView.js b/javascript/TreeView.js index db8acdd..88076b8 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -1,4 +1,12 @@ (function($) { + // Silverstripe <= 4.12 use jQuery 3.6 which breaks jQuery UI's 'dialog' function. + // If we detect a jQuery version > 3, we lazy load jQuery migrate + if ($().jquery.startsWith('3')) { + const migrateScript = document.createElement('script'); + migrateScript.src = 'https://code.jquery.com/jquery-migrate-3.4.0.min.js'; + document.body.appendChild(migrateScript); + } + function TreeViewContextMenu() { this.createDom = function(id, name) { this.$menu = $( From 9b2308173dd485bf2a3f509488ea983b65d4debd Mon Sep 17 00:00:00 2001 From: Severin Hauser Date: Mon, 13 Feb 2023 11:58:49 +0100 Subject: [PATCH 72/82] chore(js): remove jQuery version check https://github.com/silverstripe/silverstripe-admin/releases/tag/1.12.1 has fixed the regression --- javascript/TreeView.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/javascript/TreeView.js b/javascript/TreeView.js index 88076b8..db8acdd 100644 --- a/javascript/TreeView.js +++ b/javascript/TreeView.js @@ -1,12 +1,4 @@ (function($) { - // Silverstripe <= 4.12 use jQuery 3.6 which breaks jQuery UI's 'dialog' function. - // If we detect a jQuery version > 3, we lazy load jQuery migrate - if ($().jquery.startsWith('3')) { - const migrateScript = document.createElement('script'); - migrateScript.src = 'https://code.jquery.com/jquery-migrate-3.4.0.min.js'; - document.body.appendChild(migrateScript); - } - function TreeViewContextMenu() { this.createDom = function(id, name) { this.$menu = $( From e746f677aab903772a8768f242b6a0bdfe86aaeb Mon Sep 17 00:00:00 2001 From: Severin Hauser Date: Wed, 15 Mar 2023 13:35:10 +0100 Subject: [PATCH 73/82] chore: change package structure for vendormodule type --- {css => client/css}/TreeView.css | 0 {javascript => client/javascript}/TreeView.js | 0 {javascript => client/javascript}/lang/de.js | 0 {javascript => client/javascript}/lang/en.js | 0 composer.json | 21 ++++++++----------- resources/.htaccess | 11 ---------- resources/.method | 1 - .../silverstripe/framework/client/images | 1 - .../silverstripe/framework/client/styles | 1 - .../framework/src/Dev/Install/client | 1 - .../silverstripe-gridfieldextensions/css | 1 - .../javascript | 1 - src/TreeView.php | 17 +++------------ 13 files changed, 12 insertions(+), 43 deletions(-) rename {css => client/css}/TreeView.css (100%) rename {javascript => client/javascript}/TreeView.js (100%) rename {javascript => client/javascript}/lang/de.js (100%) rename {javascript => client/javascript}/lang/en.js (100%) delete mode 100644 resources/.htaccess delete mode 100644 resources/.method delete mode 120000 resources/silverstripe/framework/client/images delete mode 120000 resources/silverstripe/framework/client/styles delete mode 120000 resources/silverstripe/framework/src/Dev/Install/client delete mode 120000 resources/symbiote/silverstripe-gridfieldextensions/css delete mode 120000 resources/symbiote/silverstripe-gridfieldextensions/javascript diff --git a/css/TreeView.css b/client/css/TreeView.css similarity index 100% rename from css/TreeView.css rename to client/css/TreeView.css diff --git a/javascript/TreeView.js b/client/javascript/TreeView.js similarity index 100% rename from javascript/TreeView.js rename to client/javascript/TreeView.js diff --git a/javascript/lang/de.js b/client/javascript/lang/de.js similarity index 100% rename from javascript/lang/de.js rename to client/javascript/lang/de.js diff --git a/javascript/lang/en.js b/client/javascript/lang/en.js similarity index 100% rename from javascript/lang/en.js rename to client/javascript/lang/en.js diff --git a/composer.json b/composer.json index 7296965..cdc1384 100755 --- a/composer.json +++ b/composer.json @@ -1,8 +1,7 @@ { "name": "flxlabs/silverstripe-pagesections", - "version": "0.1.2", "description": "Adds configurable page sections and elements to your SilverStripe project.", - "type": "silverstripe-module", + "type": "silverstripe-vendormodule", "homepage": "http://github.com/flxlabs/silverstripe-pagesections", "keywords": [ "silverstripe", @@ -21,21 +20,19 @@ "support": { "issues": "http://github.com/flxlabs/silverstripe-pagesections/issues" }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/flxlabs/silverstripe-gridfield-betterbuttons" - } - ], "require": { "silverstripe/framework": "^4.3.0", - "symbiote/silverstripe-gridfieldextensions": "^3" + "symbiote/silverstripe-gridfieldextensions": "^3", + "silverstripe/vendor-plugin": "^1.0" + }, + "autoload": { + "psr-4": { + "FlxLabs\\PageSections\\": "src/" + } }, "extra": { - "installer-name": "pagesections", "expose": [ - "css", - "javascript" + "client" ] } } diff --git a/resources/.htaccess b/resources/.htaccess deleted file mode 100644 index ad0b01d..0000000 --- a/resources/.htaccess +++ /dev/null @@ -1,11 +0,0 @@ -# Block .method file - - Order Allow,Deny - Deny from all - - -# Block 404s - - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule .* - [R=404,L] - diff --git a/resources/.method b/resources/.method deleted file mode 100644 index 4d18c3e..0000000 --- a/resources/.method +++ /dev/null @@ -1 +0,0 @@ -auto \ No newline at end of file diff --git a/resources/silverstripe/framework/client/images b/resources/silverstripe/framework/client/images deleted file mode 120000 index e06f810..0000000 --- a/resources/silverstripe/framework/client/images +++ /dev/null @@ -1 +0,0 @@ -../../../../vendor/silverstripe/framework/client/images \ No newline at end of file diff --git a/resources/silverstripe/framework/client/styles b/resources/silverstripe/framework/client/styles deleted file mode 120000 index 8ecbb77..0000000 --- a/resources/silverstripe/framework/client/styles +++ /dev/null @@ -1 +0,0 @@ -../../../../vendor/silverstripe/framework/client/styles \ No newline at end of file diff --git a/resources/silverstripe/framework/src/Dev/Install/client b/resources/silverstripe/framework/src/Dev/Install/client deleted file mode 120000 index 66216fe..0000000 --- a/resources/silverstripe/framework/src/Dev/Install/client +++ /dev/null @@ -1 +0,0 @@ -../../../../../../vendor/silverstripe/framework/src/Dev/Install/client \ No newline at end of file diff --git a/resources/symbiote/silverstripe-gridfieldextensions/css b/resources/symbiote/silverstripe-gridfieldextensions/css deleted file mode 120000 index 0c11010..0000000 --- a/resources/symbiote/silverstripe-gridfieldextensions/css +++ /dev/null @@ -1 +0,0 @@ -../../../vendor/symbiote/silverstripe-gridfieldextensions/css \ No newline at end of file diff --git a/resources/symbiote/silverstripe-gridfieldextensions/javascript b/resources/symbiote/silverstripe-gridfieldextensions/javascript deleted file mode 120000 index 66f251e..0000000 --- a/resources/symbiote/silverstripe-gridfieldextensions/javascript +++ /dev/null @@ -1 +0,0 @@ -../../../vendor/symbiote/silverstripe-gridfieldextensions/javascript \ No newline at end of file diff --git a/src/TreeView.php b/src/TreeView.php index 74b1ee8..b6b70fc 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -600,16 +600,6 @@ public function getSortField() return $this->sortField; } - /** - * Gets the directory name of this module - * - * @return string - */ - public static function getModuleDir() - { - return basename(dirname(__DIR__)); - } - /** * Renders this TreeView as an HTML tag * @param array $properties The additional properties for the TreeView @@ -617,10 +607,9 @@ public static function getModuleDir() */ public function FieldHolder($properties = array()) { - $moduleDir = self::getModuleDir(); - Requirements::css($moduleDir . "/css/TreeView.css"); - Requirements::javascript($moduleDir . "/javascript/TreeView.js"); - Requirements::add_i18n_javascript($moduleDir . '/javascript/lang', false, true); + Requirements::css("flxlabs/silverstripe-pagesections:client/css/TreeView.css"); + Requirements::javascript("flxlabs/silverstripe-pagesections:client/javascript/TreeView.js"); + Requirements::add_i18n_javascript('flxlabs/silverstripe-pagesections:client/javascript/lang', false, true); // Ensure $id doesn't contain only numeric characters $sessionId = 'ps_tv_' . substr(md5(serialize($this->opens)), 0, 8); From bbbe1bd9fa73641747ca1a23c057293862ea2063 Mon Sep 17 00:00:00 2001 From: Severin Hauser Date: Wed, 15 Mar 2023 13:47:48 +0100 Subject: [PATCH 74/82] fix(treeview): add extra styling for 4.12 compatibility --- client/css/TreeView.css | 35 +++++++++++++++++++++++++++- client/img/sprite-sprites-32x32.png | Bin 0 -> 21762 bytes client/javascript/TreeView.js | 2 ++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 client/img/sprite-sprites-32x32.png diff --git a/client/css/TreeView.css b/client/css/TreeView.css index bcb8761..cadd844 100644 --- a/client/css/TreeView.css +++ b/client/css/TreeView.css @@ -32,7 +32,7 @@ } .treeview-item:last-child { - margin-bottome: 0; + margin-bottom: 0; } .treeview-item-flow { @@ -336,3 +336,36 @@ .treeview-item-reorder .ui-droppable.state-active svg path { fill: #008A00; } + +/** + * Dialog + */ +.pagesections-dialog { + z-index: 1005; +} + +.pagesections-dialog button.ui-dialog-titlebar-close { + position: absolute; + top: -5px; + right: -13px; + width: 30px; + height: 30px; + z-index: 100000; + background: transparent; + border: none; +} +.pagesections-dialog button.ui-dialog-titlebar-close { + border: none; +} + +.pagesections-dialog button.ui-dialog-titlebar-close > .ui-icon-closethick { + margin: 1px; + top: 0; + left: 0; + background-image: url(../img/sprite-sprites-32x32.png); +} +.pagesections-dialog + button.ui-dialog-titlebar-close + > .ui-icon-closethick:hover { + background-image: url(../img/sprite-sprites-32x32.png); +} diff --git a/client/img/sprite-sprites-32x32.png b/client/img/sprite-sprites-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..304c3f69a68bf90977523d7e613540f9bff3ddcd GIT binary patch literal 21762 zcmV)=K!m@EP)9!*xx8c7Ic6v!C_YfLr|aE1lj1RLY!u`JjIgUzzQvKSj|KwyN84Gv(Q zF<`&~8xUXtDzkgcTes?*zs@-oZ{50C zChW#}m#Z|oqoYHEz2Zj#R0llz=%Y9AhqwWO_=6J6zvh~2Zf!Jzwr$(?Om=p5r&_=`-b)vwxO8ac3l=Q6q^zuL4e>)d*8^_4=_Zbb82&z_urp4cv_=a6FwFnMAO3I!TEMT9`ivPfHX`nxgJK6@WEL)5 zcu9GAxnB(#W*qPJ_AhJKu3aY`b<|O_+uGU=4a$}c(Yx=ydu6|V{oV-1r#sB?z=g5Q z$%Jvdmu`l!^g`7z_GTj2R8cDKlOK|$f<7I2GZM&1;1idC-x>K9{pE+}AbgpB9S$tS z<%#CWtM-MXdw$hf0#9CbImkWkAenaN^TB73O5sbyIBBF%9YBiaph)xN87} z*o{jSfLjIKFiq#}41kOTG7`v0AR~d!cfAxR)%N)qTvHKZg$ARN+4II1TA z5+U-?#B;z%_6XqmWc=7B(9u@_=f{AN1nK&h84>lQ8m3G7LMH8i1XgzG-?9M0^0zg^K9_-D@D+0HVxQV7&&SGl0&_dlf{cH|(qe z75G8&GXOFY$Vea~f$k;n<%d7P@qm4J{if+f5b-4t-N<0O;$8yb1`uVgJwmU6=nSAU z^Iip!84q+;feQQ}`JXs|lx-kZP+YW1#(m-v2(1&2JLHfLu$Qz2CCFweP?iWvv<&NT zexb4=kzrW0nu_?vh~M1S-hRaD)vL$*36czi08~^|n1aH>T7eO zd_B~&3lh`y`!wzx*z}C^=jU>XcO>yJ<@4ugL_K%h? zXRFCyk6lOt**Q5gr%#*q(t(DCgoqXu73DHdkGUfw)Sg~P8%f5+svw!XX-WHu1MqF2 zYSgHFSoDY4xw-ul)eBn32x`J3=rM2KuyJFLHaGbI`V<#`h!$`@8l7v{{&(xpTShQ0 z0Q0x&_X+PCyUpWB-~#~j18DW?&;}%2l4FiOdIx|v!t8qdT9|JGY{VbbyeV9;5O zq4+Eio~Z*;Cg1=A1JlPD#<2V{HobK83-a^X47WAl^+bv6!9O1W=6R2xKNyL5fiP8C z7$F-BRJZaP_uj)0Om7JYbjQzNs2(FSKS{R%pW6Nd2j*ku^GtqTUKuF+894=mZ46`t z!gI3%T{Va74Gj%Dpt9%v3`qu}oRZ8KFZus-V?tjAEYYFk#=V6>$#H6Eu2MoBoGI-> zN&f;>T?*S-xN_C18LqU6#DxI#?c4V_PE2)b?jpc(1N447Motf$c;boEVT4@9?Xe31 z!0@WNw5+VMD+#cQLIOWJ@q`mD?bD|Z$An2WUa?3+0Km8m3|8WIE=rnDgF@ZsSn>DF zi1^Q=#6_rfC5W%Y0QNrQ84*h~nd=Y$FwDkU!0wk{e%XKhpTtf-{q$=3bLY-oOJCh* z&p6{IgvaW2PyAQ_1`83LRyZ*V;h5-p-XaPFda2E&#a`Crqf=Z|8fa2n`dQ%$O!1 z{(u*cHiPj_WR+iJq+2u;Y^%hWJm>xxW^ZpJ=oQ!V!{`$RpuY3KC zH+=Cke@ZGy1ORl;*7EbOyzJP*Pm{D0<=}(bO6~AH+O>PtZnl7VL7E zA(`sxYIn2g%HW6#0m#eGKMRW#_ksDfY-mv5cj0bJ@wQApvsw|n@HqKX_h4I0D8V!%mwb4GI{a^*fSJm3FCr!aWKHW zLIa`iT(kpzW5uaDCM!oXe>78aApwAHw*+&6Z1jL92m5~7HU=eY+FDw^gQw~*Vr(!r zXmn^h4A&1ZH|TLy6ae7!EJ|__0EFOs%d+_x5q%E#^SQT(iW(AHO?uc!vV&A89qQ*=^lN(>8|FW9K$Cni1sB@pzFJ?5B`z<4(H z3CP|t0Qt^4|M*9j&2_Skk`ut8haUQ6809Zu7i{NcEeN(?xUCDnM$Cw>T(NTH6G5;V zP744f))PNj0AY*j3C9%kWB_P8&`FS(C4B~C{u8iVb0liPVCZ%?hF=Tu9PyoX>(@Wi zDMJq1eDG$-EHt{_z)IeDQK6tUhKkg114{f$vNko^D&y`;T*=XajL1V z=91k-tJkb~CRqSEcuer-E`s%dYl^Ter_5ObbTE~ZCQg+6MTLbwDJ?C19xD2Q5=6;IXi3?D zVH>c){p<98GQJ>2Pxu)uK>iuT@<@0eRzbIJ+jjfn#fyEWN_6t%$rleEJoxtjC0#SS zYz)mWd+DW@e#tZnOw)JgOG}9E^c5J=6zKkKX23cDMSIJH@#AlR4ful%bW)d~mi(UM z295(DiFJ6ga;!-)Pn$I>f(Gb91TTO+oC>k_M~UkJ;x+g$K~hiQDXMeZU$QE20&s~c(YRy* zWO~C-TmqfCnLjX9 z{PQAqOxvcy5a*`~AS3}7F^rbZOetoMiQ16TN;zqTCBT?~8$W*h zOo;z+Jd{|9&PT?4yv_DrrsgNp zb3fteFaU^l$OvnsUZu!rp9w1gP{ z8+5x|5A4)4B95>9xy9217)}Ymx)(MaIB*80U3oDy+wh#IAL3t+iBIn(-XHSYeHUfC zox9T&&p6shGCyZ6h#eCInPEk}7%S>&Q-KoziV)-(3MX67Lzx{HZUByiepDK|V4oHO z$pZ-HPD3~YAf5z5iw;3aFI~En>jLgQ9l?YNhMf%&I{I8J&lKaW3zOB+!3R~js22y= zG-H5T4lixh&07LO=c1Ub=&@Z$A#7ST*TYV+2rDbzZ8OG*w^|-NV zlDbZ|rHovC)Au`m``q*K+g`9I(L{eqp+(|ITO`lSo8PN*k1jD`^8ua-V(fG0{SS62 zg)XV2aFdTh0HDKc-oC@U^42>C9Yh3Yt?&uCX$Fv&{yH~>vh*w~o=4%p6GDjKA#|%T zkNx3K2UQA@ER;MOHW0x$vlS~O?=M_r-d*s)K?4zi4a)GF%QKV)d!fI)A*=R1lWCH< zoW0@pox9C5FU>tDQg8z3fF#~|FY&`Lk}Py6xf=jRYWo|S%mZ_t=yfSL0k9Xuo)NQb zR(AsDLp}8 zKw=L-%R)^k$j>#GU3~sQV{QT9e->&T)o|`vy>Ino8gvm+0R{zC!-kqujy(LJmYzT& z*=YCM+FM|FJ$_j#SfHfCIT0A%$;TaMCRdF-=%pvxPlPEM)8^=$y`kJWNdKI@p^Nte zd3nVLnwxn9*va_OFTzh9-?Dib;#~A^#w^pwMEq`5u;Ipkt@!WXJo&B7J9l4NTvRl+ zf4_dFqM{s!=M|s{iSR)uw{)4^d#dr<(^ybg@baVy6Mk~s{CD5z3V^DhO8_6vne&;y ze*4-7`xh0Co;?0gGjPBFY;I$R)tNM-xcz6{`VD5;s?}}%Dk~m4V&a7BC%*n>mwQd( z0(kn&Gp|_tm%lu4?2PGoW5$k=PKuqDBJT`=BRIQ)f)`}W`yVVa8@FwH^WxwA?xL|@ z_(G?(Wu@Z;@bp<{U9suK7iXV-+AK4me?Okk*d^mwFkHr=nBM`Px3req?_am{$ThBsc9^ue=CF!D8{ z$Bi{x-+%u+Nfh|!16cgnW3vXZp4uIwvc zwaj7tb%-N?EId<=Wll;g6knjEL87jN_NauQvL)-0i&r&ch{+C>m}Wf4u}x;gF`4{J zwe~N6#gu*Z63*jH)8Y@s=QSk~svUktCb0uwHsu(BgjzNq0Jv}S4mu2E^@7SgVC{Qh z4?Dj1T|=L)^b23~YlmM&z3c?Q!tKX)VcgR8=}L$@s0Vr>i0EL3PD?txg)lE z7oK#o{w6Y@gBZnUP z((tyHGo~MLM7SSdQ;h9g`Dl*opZ~Kk4p}=@1pStrbFOJxy~;GNSYfizp3t!{JaYK? z=bksGJo3nKhh1^SJB-)r9s;oBvByq&>1$tm?fkRP%*MX4P>U#?Oj|2TjH(uBWJpXL zMBdSiCn?!ECd=EQ&3IP5`ER~uwiOgU|FsW4JQqEpm)uqd-v$^vsIsfYkzASbuh#@VP zety?Yjcdj094~(Mvp1}tH}6~h`t==IHDZLRtgMjptht!MX>}V6laKNC?c2`{@n!>b zjz!Kt9X@U94`#kG&nb4&PY8el`P-z4gO{yYd*QyOrt^w&awcPs;XoYKj8@e)v8ZOv zc*?pA6@1Z{;lqD%@up2u)il4$PYOVgz<(Gs7!{}rKU>i;=z$*VVNA=ui;+MuZCBwQ z1L%rqGWQffr&^E~(Ae5K1*JX)za#MDp^2O!b7M1mxc6WS3vLJu zw-DE@i`x!p&gI|udhWvC|KYM72lii4l$$rLva}?-6zzFFsv+w?PH;B|P+e>4_nF%L z`!=C^zA}2~ko*5>_1d^qkhIkXx&(mP@yu7h^PLCFv$BpHGh~qIS5}IXIeB)y?MM_P zt0M^>Xlyc@cI`52w{EL0FE0N1q=5r&J9_mRXU*sq04AsxuX*vshh~f#Svdl&AX_=E zCu>G!9`xB7vMJsVi7Z{W!ECLqefGkqp1SmqFMX-g6kX{!CGhmwXJ7pB3orco*vS*~ zDzTwC^e%FcW1O&bV35dCtQD-Ut$Xdlr=LCxvt#ttQKtYp)dI|pXRdkSh1thWnUq(7 zcZrf^P8QNvz@IX@$_&lTnf2=R*FV&PSHfH_1OW5H+?T)k%|~a98c_~o69dysoN%_| z;1lRTqryJod0!`{EFph~O`2d@RXHurM|3&bv(>fN{XN^PvY#L}x)@-j3dIz*Se9fmeUiG-DjF z?w>C)t#|{EqocOAcGJIKUsJMr?c{gwx{Hg^e-O~#KVJfy-+5Xjw9bT ztohWw_@YD&^cb}_HOmx@%*mKPy1JMCBI%h=9hnSAHOB1t0*zDO2e9N9zc{Y4Pf=bj z1|4dlk_c4`R%LcP@Kdw%VIH<&q_hLBy2_UP%B$p>@jGWfY_{M3Q%N5jJ7NrD`t|Q` zYF4b6iP>>T4ZaF6JDyomR20+1kSrYPHbuSzqNh`F_n9%%6d!ez#F6=q2Y((_jl|l2 zED#4UnEGN^6&ZrqReIZ(01yu=$j{UG&c1^9`OnRcpFJS8L70O$uXp_XXHMoML;yUi z0NOu)m=6^p0yAB)-`6}HQ$!`!M^w2 z6`$9Pr$N$QT#n6nTYEfD#MHORvUfZ6eTc!4)~&xTngq5py3{*KR_wPEMO4~x#}`?Uig23`A|&a-0x z;M=)bWo4_1`t|ddXUM<@F!k$SUx3#rcOAgS1@>y{WSI_qLb>-WaR->Q^br^=8yfn& zW;|WlnP-`7q}OFS#${o8zIDr1(|^*WzhQRFDBndnA?nfb@F zG{<{5H3wr##dlBSf=f~uj2pve}7Pa>Z6YyGwmDSSjKp5YeE1P z&zUpnrOPk>$7$22l@u1_ORvVnbZqO%B!bnhU?XGiG66ax*bu?#)SUM?ty{cgsi_$_ z=oeS6Ui}SCuK)tY!23g&U%ssBth0Xd;qv9A7^*fT8sREhKs`!Ke8ls9ka;ee;~hJ9 znaxej8)w~n@9m-V-bDxi<4*gp|N6nsw)Q_QUA<0hA!rXS0`cP53v!(J?&>wo<3IoT zua7c#pI{~PiFu`2($3K2`eO=w$1xuI7Ff8874o-rQL{3h&*;!L--dgriW7WCm zUVi2iPsH6UAF_cULClWxU;XZPf7Z5U_17n3WYe##9Cl}q1EKy@ha^Bl(_bTc$u;Pb zKi;u(Q~y4FzVX%i`d5N6dJNB|4(;F9lwn9-fCVZV7n!q# zV;*Z}+6$sy@9K?+Uy zZT()7Gol^?2!cr^tN>D>k(Ru)qfYLmTVFdHksP`7*g-w7z4^mgYr7-h56NN@4w-Y^ ziL)DI1Q1yrr=NJsTqlTlD&QiZ0I;C#{GTA`DDLQRfCj(@Np2y&Lmufr_~cw0 zf&a%3HxIqNV$b6UUhYSF0XKk<3OdXO%a-6-L%FH0sW#=Minaj=P8mPhtlzRJq>i%U3aDw1DJ`imHBi^|${z77m*@*{t2L9yGJCm!yJcSc^I;$}51c9oLv$!y@T34V~xd(r%D{92-DM znbi{LFk`Dmv+#Z#!SH(HrVXYHMoHr=D=C9CXryI0KDGu@C^MA^vvvp!W^lMT60q$6 z(<=y@SEWLd9cJ`M3#1%1goteEJs%4!wyGknhyXMl&KU_}{Mf3;-1=)bQtZYPzm2Qg zTZiWZ8%*8aTC-k!TyI)$YVnM4!)6R>r3F>~;b+E*C7uLidBf_?TKyrS2DbfDSb(u5 zW$D=mRFv73m7q;%!)58XUlVr&AQzP!pniyFfn~)drlzhIx-K;}weT_hs-_QJ*VNWn z-NO&sL?zPNHUY#))8X6z(0oAKNN(GKs^M&a@U9n(iez{W1q@zVMaTrDw^Fdu;C*Wo zlzPk;X{zv( z!gw?j1HerNg4%+p0Ksz3cp(On0@`?6+ID-*cuIj%3D^XgQ3{?7B-IXhMraLiwD7Quo7zAI{Weqmi zcd!Z*1gl|Ihx_xDPQ$w=%N#qYT|3kg2`Zr%4*>x&^?5DT5%9H~^eRh@osj4tkNooN(i1 zwT=It+Ex3}>zI4qWj>Z{NRv2XT@V1*@v-P&Dr+~bSbyn_kA%6$=1lUscFU^u$iga9 zi{AnKWLefK5?63!h%GYmIM&&7F;R++i_v7xy+qzrCPj*=W)DihrdR0Rvjejb?45`%?$v0CM!F*u*g#Z zA?@P!U$v52T}#$Nx*4@ zPEg4rJB`ATVdMNlB*YK7H#0DHbU8?9uin z1zM5wZ{*s~XSyNrO8cMg_P26s+LQs1`QXaZi+}n3t8Z7LF8?#v-_6&4JAp_EK;%4I z@;}{i?fv!Gw#B_gT+pV|7o=pHH^zg-#eK~A-@o62crGdlA3)^18?f~{E9reB&;0P3 z`}mpkedE^l=kAdW&e4XbBWuW?@A}{O@jYRD zF%TKE*@a4Jw9DnASsV6oo5sclIYfmF_^P)uV_qJvLO=vTFnkH>+1szZ?}D4}2MFr~ zK;&GO-sCP^nA{g7`6eyTBA|~~jZjLKo*=oW1n(pRe;f7>unD)<^rcY#_|QtA!;F0X zKX3R?4^+Y)^}b@oO4Gmp0KC>$fYS55bE<;7Z)gjAUc|u&!Ejvxo85EpOxKev87Tk} zkA6^L_=CXVhtDwYzWY8tq*4kUbG4W1Q5CVYx**DM3#5f>5dtpZ zd>i1>Iw^{=z*^dJ^b}Py7#r0kl#y?fMANB-Ld z-@WH1Ua1PiC|F85mRcpD7OZug@r+|y{qY{Y{v&x5lvY9d!7$U(XrTt->8XI{P+JMG z^wv`OCLQZBt0PVdf*QzJKLkoUuttX*2=ktmlug$a9a~OYSd~CFBdrRejR)(ns(|&I zr6VJ4Xvk0<(`iH09UT@75)flj8}OKuh_!Mna@&|Ntfd16kw%!g10#+y`hYWXZRiBdwU4yI8COiQUWH{0fx`RKZ$BSvods}W+m5h4Vc*nlk&>pT-{ z8=!_si7>sj1Iw7;*cxhIU~M2C9jd@ifUOdRdDObeSnEA4Sgl>Hnb%}apI|gSrA0fS zM0ii*c^ykf8zZd+emlTYvUFCL_MK7@nF!X_5b*Xvk$AZDEVZ2O9+wX3gMt)ZYe6ns zIJR!KMYJ0fRpj=bt(5ow*K2w6Hh-ebIq zSj8_ly?-ilD-pH{wrz0H;k|G*Z10Hd-CHm350e>x+Td_yJPOVdCQs6!qc|C+z9}7b zUD0W3!c#l&WLnA5(bkUl()q<-zM7@g$aXqPP#YOu$EM-EJiL`p02aJ$teBQk3p%Fd zXGzGD@fLDhdjb1)4z28{hgPhTuog^kyiev7vlT6g)1s_6+S?EKlIw~Nj|;-4ba|Ak z0CKtYhGGo5=+F$-6=)AMjU9CGD%w^MnCMs&!H=cGGYJ5|mq4+p+p_+eTkrc#AW!cH z>$k24lVZw_NchZ{K7V1QAtKF=M#ksI-1}<{F3k)wSIjLpNpkp8=}})Fn{sx>+k311v#PQoQ^&l7`BALjz$|DZGeJ4d(0I$m4(}XFaPmx zI5Y0#+yKzJa>&II4##=w2XhC31mnYD4qEI`P6r_2``Q=8#>zu0JHg~B8@BV|wH+O? zgW(}5bUfjQ*X$qkKI0k3rkWD5{Y5elt(XP*SbF-lYqfI+5^rmZ4$*BtVAI)Av7OrK z2$=vV&3G=Z3Yd{4=eLVg&#_%A?=y~v9oadA9fT?~=3@aQ9|hrXZU9Kg;k6FONrXRT zNQp2^v2!rObpa03$WWzb(_0njT*1LV`pReMCZC-8;wN-|(X&l{6ekIC#KbgY%xgOj zh)(O6p2K`fQUE0PMAR5}F7IOdJ#+$vxmIEtA1rO4@& zuw39DiSwtbQG1{&v{tb60>Y!IdDXXdNw0k0PQ^HUwk5QpRk2bH46|_bS!$M&QVXg= zRlqd1D*6okfExf*Y)fWmNKDPaBumJ6mXM`p*czgpCFx8^(=(mupAkq(FV;CDkvNy7 zgA%-w@`7iJaT6T%$vJ}6eNY;dgi3~KY}n={O=Rrk+%|yJL~aeys)NMY`Z3OyOj|`u zLerB23NC749>!ZKNQpWDh$jINvE4mohu{=9f7;v<1f9lH{8R^j8prcX^%`X|k`d}7hUvz1} zJMDA6clB-kO8ZP+R?OCndJHzXBuW+V9@D5gY_CTtG=eD?so8DYqd+u0 z^8&Fy_lQ#qC~|F9t%#{d3PDS)3)2LblVfWK8CrX=4U#K8t7p);0eFo@P_!yyor2Tq zHI4*>VGG`>#+g3QD8&KLkxL?M?X;oMk`WYjN{P@>w7jMySVgH4o0e-twzUkV7c~WX z#y9}9ep}xXwPOG>)y{`Z>9q9fx3sV;rv2EdUu=4|3F_PcFeaq_Sj7qpX)n>{S2NPL z79L$Qq68=zTXNe~$ELTE>&^(FBZ`X1RrIP7ij$0N2`K^FFHjYffTp8Q8nNja4+;3S z&nbb9)^>bP*_ND=AQP6BKVFj&s~3GTp`o!>*k;;FE?#<}0s1N#CjisdQNR+a5$Vc_ zTC`?XgQU;&^n>Bp^jxOD_CFqc1luW|P#SIk?OZ;w159mzR0?W%BoJgsQS(R8Dk=J! zo?i~V_TPW@ILJQI@%DoYx&hD;$<4*#HTZEwi;WM_Q^f2c$=Cv<&V$dC(CK43M#j0h zd6Gxdf9LiGaN6$HUvw;g&Sy>#!Dh*SzaVS;xtEyY!6OxATKs+6Hg>G|^JAcudA}ee zP*K4}x89oac>^J;iCt`c`iDP!L23bvY6vc7CzdZgsZi)ENl5_+RcqbpVbB#7?K7Ch z2f&KO-@$TPSPKAknGqU!#zNav2jesx>m!o@kB!%B7D&VIo?7OYPfzm|PLd#ykf}}^ zZOrLoY%{?ye{_t~c={SYs|rUg;73Q_6|X=<+R~VLDv(YD88KSxRj2e>YVxFu+CV@B zyz%NfE4tCS*n6}MhhgCUX@wB?R;wxv+j!jGj&f-lgjrgqG21~*<2y=7TJG^aodED9 z;6?I=6(QRCEe+;|i4}}83)ZYLGjXmx-Fqunnj>+DGTnR}pma2j5u$s0=~8nXPUxe1 z^TQ9#iHF&9&0k-*5Kn1_OFR`DmO!8@l@u}m6fL9E5he{iG|Nf=n~o`v;HiA7f?0U5 zvS|H!VcZ8jBBekA#pqrs1r1XQ4D)6UERV5U}8 zNqEuPwPw0kBR&9-nW_#fP9N#ji22KwnWH^*yaRP80O1u2AXWmx#rqe91r-EtBpX%$ z!mASil%8T!7gWJVAAc6-f)$%L3u7J;Oeqiq)j=s}IH(Rv zf$>3gkfze-Jq-&0nbj}>Zm0{YU>rVxLAPSlCNsgS5z9~`CVMqv34lyhb)ZH}_iDrf z)QFj$I^Ksmls>tTC!GMm;2>AT5&$4~y?;ckE|stXpiiEp8C3x;g2aO=SdFvhc#mg| zQVJTT6d2BD?eNsW@FK5)Dq!vK)InxSUkpA}4IvKRRD?_%RKXaWQA@Xa%N8?k*f0sN zK-HWua-@Vm0+30n4m40xy#{I#s^&CL9Unj)N?&*dRUnJZAf*U}YA^g#`jmh_<0hYm zhK76*OMq!m1#7VhP&2RcY6O!m_i6;g%e)4P;l*A9#W1Cyd38ueUgXL9Vgro_4q!

                                                              r$FeFCn*YmChve$O^`aP?kOlw0npskl${IfEyh~F!Eoe7p5#RU z#Z7f}TSrcvddI>=qb>1OCAF9Fs9la4bd zx?_#z*Lh~8G(_lzhWzsSlV>+!-IwjOI0Vo*^Pi3LZrCQ<-b0Hj?Y#Aoi_N+9By0)U z$GbN^vA}s;qy>OnO;}>BZ{&79&C9+a%s`uJu6XmuYYnq|*Ekz*^AdzTvvSt#FWrSR zD{sM>mA6OFtmHfqPLF`%`Y@JHMKKyypYd`-mTS2L%WL}f$EWQ!&c;X6>yZ#oIgX38 z060&xw1sH8K_~zg^{JaqdkCw|XP$KPUu9;iHX!_U`OKD-+N2m+Du#*B#xYDWv&3{1 zzxVXUOT$HMxrKPgnUy2V{d#7lX9M;Tii)1Z(+MKZnG}2!Z^r{DnW(^1F`{h=(*=Uk zA??ta6!|4XPU(M9V5Pj%lrAUqXB z=LMnyFt$e6`;xE#tlC8dXn0st#p>DWp;lTc`L!UulJIOzYM$2;wu=o=1q9ODAUqXB zZ2%e%v;(~lf{mW?w2a$Y?I-tGelbD?LEpyNW?Ao3O0n_LINb5>tIxESuZdtvSys4PX-0LBeQc@5h{*!+f^bVe8v4SDjm+h(_O z)f~5V2I#MDGmgU~?;hjz$5@7W-w#Vdd(9yCp?>%N`>(w4mRrm-x8EKDA>A<_pKwP5 zpJMRSvi!AT_Y{5ZqG|yF`Xq!EPT%M2e~tK{9)AsZ5#|6s8^zy#d^33;f7n;dy~Ufc z!2Jhok^#_J7uiJSMvbcHcSr>Qt({^GN!D*0%`*X3k3NnPw5MQMydpj%~OO(Lc(4gCBy4jU-HD< z>$dMedgW;W#5Bdd4MJ4l@e}}fkSsn5XeHp4NUk*;Q_7Si0b0sa81JvI56z5&qVEk5 zq^~@s7BuYNFI9rf)CECss70r7yp}jrRlTP}GNzOJtj2+GFdaU=XOt%a6d$Or-dZ$a z!X1+1ApOI)kvEe!3zbL7taQK=Kf3x8G1Sw zJaWVce=Zng&ae}JPF7|3j6`oOSYQU?!|B_1?lc4X_4EB5yLOqwxqCilLh70e{%-Ev zSl)8&1VAGc@goUAoG^2y%<0C;=f&`?JNQ)6X!vdHb{(_g*ty_{F=OgJbU<(dpakfs zf2u$w(GztD9sE<8ybd}i0Pg)4<6}yIc;&6Pf_Wr-%F#!gb=Xy@YN0y%Lp?j;_v_o& z?ApECOvQ^1Iv3RU31SI30ibaSP1h@NTGJ6A_R8CDo6|7YUx3}x1ULv^c%p4kW%Su7 z(MISK3?)a$|D-7wG5_2Eym=c-&&)^R9RVFuk?2fpSSQmZ*uoualtC#B^Y)^O3YBIe z06^SM*O53hE5cG*IRtO?aK0Q!H8ygLd#9_J-5F4_`s- zgq{~;!!mtJLw)Lh8FtYUC_#`;QbwqW$%twId~+1fpoGtoi8|oN+5qnbrJ)iKiFq}P zB!kXv1D@E3oJ`+bw8#vEShvH%>2E`Wq}OT#vDb{R;vQs5035YYLFNp{1)v5M z1{^aoHvkRcjx9()03=fjATGP3LTF`~!CA`<&eoAolrfq00a|coeSl^` z1CeGHtsh&gL>DPC>jRnf0oo6pU1hMgl+=hQvp(RD-@L&`W_^HTyv+K5&i_AE>jRvw z%Usd(WpxDgUsp+bzCx1V`arBS{A&Q>`r)ydJND}O0B1ciXGEz*8iCSN3hYUHb$uXq zF39WPno$XsZs^#pC)E_|T%H-bS|9LB&lxkN5n~fF;Di+Dl%`w+{y8TmiLMXCt{M3@ z06>(+akK#dqbho3eSoFbnJ`z%$jqN1a|I10?bY=Gu9lpLl`^)JWFA`9_2Lf7`T&`C z2O_gRkXawdtPgmN->2{UhPI&&w9O1=)(0}{1L!)%Drg{?^#PHE_I8j+BAXb}zCO?s z5dX9G4{*RCkKSmSNvcDn$1!12&kbYt59ny8ryy9HQrbV@m%4|N5a7v304ax$AXN}7 zh!!eg0Aw1JKq?@Tmq5(=fHGGReuJfWC|i96mwC-eZY_aWp)o$#Bp)0qFXG5)mfIrPr~(-1m%D@5O?Mw?5zmz$zo9 z4=_y?M7(O~2H>^Z=>kEK`|HoV2skGIe=e_W0Wzp)dnyT^W`H?^P5}C>KWKzZ+*1(D zt27x7IwyeOTs|!zz%fX(TL7`o`qKd-mZpMmcE_%{d=P}}6#xZ|-H#fcBSFW z{s9#?{7>KdKmgn)6BIj2mGyt$Wq3^Yh27A3=J1=$ve-1vS`eG4tLyjgG*e*k7mWF* zyKj?mL!88G=iX$HHgd#w4d3?lT}l#o56nU7f^lcub6e4r>u|tct7*hb^7NbdvOM~f zZ*3ZA_U~^tFO~enteJb$ZAky@kpU1sfOr4HT!Ncb_$B2goO9o8#gqTVw1Qbz0QT-} zHg6Ao(5(KefLMRj_skPyY$_P?8dW}Y?JzMG6Iz^UA2m6awRehyAw4T2(w z&@~B@iNr^_SR=%a+@rqz^!Gd3@CtYmu3>+J*?`Xi48gg@g*c_14Ef_0K$lPEbnyI6 zPypbt#oYE*d?*noDkS6bFm|4B%E@NQ+*eKCfdfr{Y-X2I2h`>QLw?Ff0mu;$9qry5 z*m^eszqPK$Oqq46S@n;9nC&>~ArS`v=8YekD^9xAJYoUhTbcLqS`rtQ{$+AWXjdEApZp&_!(^MHN_biT#g|7wj$y0HIG*1&~Mq*M|?D4dB!l z@Qg}8Y=C0vSv5>d@X=IUGYs1RUV=E-5`e&^Io4W`Z2=r434l8!5HLdUC4hJ{*r@Q* z6SBN4Es;PA#8F#aa)R7f000!RNklx_B|CpWLbkSFsC031_XR9aSw-Ou=J z&_Qr{`Gtl&$w~k;;DgO|)i|=J<={vl58p^4Pagq?6nN`EQ>*DySy_Q`U2+4Go(d}` zI|t)OBd0=A0>vx0nw7Kv>-+v7p{InC$+vE&Sx(*@l#+4g?D}M+mr46SO9KAXELbc| zw=cnU((}Y14M%uArspyr4BnHLFc`rwM#)u&U;Er`jX2P(0I&SwofGk6({+1|ms_^r zwefycRc6U=F1yW@-VJ~$F=*((G`jxpVRX2&rPC>SjkTipc5yV8vsTtzUaJCNJ-)li5nfri(ul9Y>=4G z*<_Ry^y4y$cSA>6q!mX5@g#tK3RZtXKOUdB@gW+~HGyDyCjc2TdlJyWD*a?#mL~y@ zHFX#sOT!5Oz~y6msTyHWdZZc4oY(OH_@iVUE%!tM!Mp(AbWQ+}80HC_uG0{szLP>1 zk<3f+TR*v403)>X1koRLh+ncU%WDS&L||bmNXIP!q_Cy8RZ+vijERJUDu@T)UoHz0 zq{VjNpEB;LzrVK5@B}Ua0-Arl*~|>`Vb}>k3`X>4mun7A!@#Clw9+WPBQ?| zi9Lb7O27#KA`%ry{q=y5a3W3tXkO-Fn9maEBBU2+6XF5zR1j?ilL5fZ3fh8_DZ>h& zzye^jI5z;)0_2iM?$RQXAg&W~NXN5{MFL(PhG_$I`J_wwo^u0WL&StwH4~9~zGmvm zXFfhX;eG*?8pAF4(Fwgk$K(V6ey{<4cZTgi)bEP1-ebO~1S}`g;lnGsr$CSNHN6u+ zQwx?)L{vbH%wo;(6LOver1V}MhWX^|i@Edg;W`*3>3c3702!3_NCGIKr~phUJv!34 zpzZJG^Z9R?=jPn;EH-BR#N+QCkYM?Q4}e5p)G14lxDf(?XSAB9w6u@;s(I5q{pg)9 zgZcg6o@3tW5?6$}09`Ahf%x^5iPGpkeftN3 z67UO}j&I$;{+-{l^O<-7XEnsVA#<)fadu-frsu2;poag}*K5`=hOH-Ik0z>No4N6c z5AZF!*UXzgoV8ZtU49$@_zo$mLJiIoqSNo!*;bJ)BY!sD0<_m!@9nqjPM!UwyYVf% zTk$QsA4b1r7Yo8~5<@poEb4|kr|9gr>+BXi4P)^`;$?Rg((zi;>!;+Nx^dQQd>&~Q zKBzZF2s!?_0r2xe;7$HYR2lPH!|^~U0P=HvP%nrho(fPUSz=9!z2VR|8PiexWNsa; zfcu8+mGfJ6!_7nbEjurh@3{elRM25QShmEJm6e<7nrc&CT5k5#>@j5}xUSt}W=zEE zeqxGI5OKa`SMPHT1l#~f&{F{eGbiJ0Iz){gI$R_%X7~teh{H$9wWSx8gtZ4s3X;q2 zFl7~mrlX*2O@OZ-bOXRg&#bVi220qMlKGNTN8np_&eqipARqyLW^VCEi$%y310_L7 zQ^rp=>$hwQsiUm80&3c0N=qtC4b(M#@-)~AQj0M$S7#Ro02dRYMp)dZ*o+xfWfs8} z4x2dHtlh9)YDYyyg{den7j;yWR{&f)t}AeDeXT*BF(cb|b}ZJn>s#b$jc~hL1Oxy1lh# zz4*A^wBFQWZF$4yb<%EVjUde+V-3fXfLN^MZ>|0iQG<3!1Yk?b(zB7OD1+`r3Q?QT zhRf2i-?CH2>bn6@Un)7I{BZRuQ&wDJYU*mC>rzuw3%|4!4H2))a9v|{4?k!Vl}KwP z0Ai%+aBcu-KA>$Rx9va`-=ao-8W_oxV+kJyZ$=FshAT*AD9$58f+}gKY=cE!+H3Yi z0%9A?$abI(pMwdNo>IX6p~J>aGzLi3)nL74R)7ANelaBVNfJTs}(x&eO0I_A$j;s+{t1pvgwOv%fjI;^fKm1>I zifu5C;wEeGy}k1v5x;*>#`E>jSl$ zR;<7D#z&k@r)Esnuidh0J@RDECrnmwne_i3NI)9XG@Ut-thlz~dxtMbJAkfo2rgzP zOCgo?DD;)gF90}~qho)5FdREG(I8Z!XxYZ0ChwIL(#ed%!TFlLX4F99J802N+0M8<5w@yyZWAyqEI z7z}eCp!_JQLNF}BKD+{tF@Z7-2bBH6cx0so!RrI~xFG?upnM%PBmrLV27g2F8DtV- zCS~3SB_J~rug79*BaWzMsTp5_E=Ed?^evc>HUc15Hwa)bXbpxGnC}4c6rdpF!O=<> z03NX*T5b-ju#EXpW&r?3WKL#Gvmd)YDb9S}r&M4*WI#s%c|1QGj#8o22$X9SC6FZJ z2mtudI|C^k$VjB@86m1k7*&xmGxH~aktQ2)%vcT;6(GJ3M9+JB0f^$&ba)&i5-b8U z(!rvsNRzY(nml-99Dv}i2Y}xiBr<*w<4^N%HkBp)&EWE3X5z4EW?1DIJaj~ zA51BcVgR7AFnP%ZfL0Wb}1g*-f7fR6k8#|MopAkpn> z*k`hvvgJFJx!Jkq?@L}WqX&#JhmD*f1k}=nI`R-q0hzMoWC9;#z{yHT4U_<=g8j{Q zN0YvZjqFHE02B+*ZP~KL?B286?5*Ex_U^+E{^PGbY?g0Ygp#%k5i%9ILbe1%CKNrH zP~7CtcojR1mG`JJ@(>AtCmphR67T@<>#cn1bpT&(EyNpC1qB7BvANOQ@${Xr`W(}@ ztkR6^Kf;XYKgtX&>u>sFWuz~b=gNx8zyuR2FfPo+n^)jUreXt#6ZOGY04f0mkS(YH zLUaU_1i=T83Zw`#;aqIb+flRA?5y2oK3KcRR}Nd zs2MkKxGg;k8~c4jHp4`hReL(>u4e-zj9*?yp2=>_llRH;@J?BNe!l(Xc)nhHKGW!r z`Kw!U&EB0kW@&YfIde=cY(xN1_+C5U86l-Gdh}?irwW3;ln{jYnE?I@u`h%h6#Dcj zlyE@-uk)ZTrU77BVjc`b)PO1>jS=c4Z9pUq#TgPZ`*|@3xv;x znw$CE8#b$G-LN{|if7Vukxq5>50*SDP$k57{ElnQ@g z#}<;jI(^?T7YgfB%-LD*JM!xEeZ#854!fiGSw}f!zHb`C_tpK1~@Rs3Q zcKl%8ZhTlN)}MsW%ys94RAT4e*xfgTAKe3Ix+6CjqE{4Mf~VqAPBW zd!&;T0DfMS%P9I>V4maqh9?|fR6#%jhw;ODo@2aTuU@kzZs{qNfq2A2 z$2`qmp*%9a=av9ffFxYYiFLua@$%s^EXUxOaJpDu(~1qmQGpDNW0_D~%pCkJ`tUIH?bCEzCk_@hh4*ZibKnl9%Az_~l?uG+u~SbDOhXL^0aOIo@f5DUTy zfNxj^RWRAB@BBOfU824gfV4={<(vSxs=!Pt!d*C=LE!slt}gbrxBTs#2z z%%mmYmm2t^i}lA286xrVeBSf`U~RRta|1yC9c+k}u39Bx-vKF5{6YU%bW+_pX^!HI zuH68z?kK3ehUkRD>`6Q0h7J{8v3_>|gcfMJ(!~Q{#RfH`!+8Llq~Za{-`9XW#Z-l7 zjJ^-4kRY!KkA8Q~OOcojWQ`d!<-)Vhn!fVmkFhk%<<@MhesUEQYk>5z>clWl*y8=J zN~gd)u-IMNjf)H!GH&u`KJ(L^7++x^nKt+HU;p~=k)cCB<6<9_UvXAp;i1_>hJ4PI zFqOED{rgw7ZQnj0G(ztdkY*47XHe7=pPPjykho5V`}DnAz)}Sz5GrM6eITz6Rnw9&m*g%~10q2m6;^Igrk1FJv_9a}(j%w!fRg~A1Xy-%G~|w#)SQ?VNVug* z9vmO2)O`TDqcT|l9PWwp1KzcIU?@;LJmkaCQ5XQGO}x*W+`^lT6TbY%KhKx^WK5#M zj{zGd4`Pn{enAd*p(f&lC;rKPVLZHmpN0L~0s{GI(DFq9Y$D~RSCkG|Y{|QPzu=zx zMw*HWe8YTDtC=!spBYo-_%H&CfO3!-8E0b?d3Ii|$;LMOY;Lnhd|PviX=}pXW4sT- zNl6D@%OI$3-!G`IYc~7$Wt**Aa?GMdea%rv7Mim^%VqwslaliB4fYD>U}tq+X^F|l zE8_e*16^BVqiMohy3Kp*O>0AwY0pAchp|)lVsGv3qHB>i=Ynhrh_02ZBOuHlMfLXE zxn|thLNj4}a1#ne4P8Ry5k9`bQGf;I{Y@SYzsSN4e7fxXR;0mMwS4geyRqAGwoM22 z8EZpN?sDd%w;VqZ04{xZW>l!S48nAI7@_ksMQ2 z9x+3QWEq@=f{jgC_;yL288W)Sk1&@_X}!kvd!MQEVFubHcnrRn9|A|bI9ZZbIhzVGj2+iDZrcAS$Oipk9w?Mxy3C1 z0Q<=HZ#4rd>P*#$efUxXY61%z@BM-sZ>&KHx^`0CSp!k99B8HwUTBKoO`Do+2;)Ew z%3vStwr#I5eeuc{&O|fy)y6E^Wy}%Nc*K|h5GEo4u>o8X*@zJpU2_lI6(Fqtu%wRt z5mUc8+cfMFvoftsV056(loqy|V(hTD4U*+SgO9ZV|NVmP+uhIk1AHZDnWN;=4&X%> zsG}GMQ{)2;CD6FP9a6x-hzIa007$k=JAi#CQ5y(ULpH^pZZ>XoZ#D@500%N*$x&0G zhAezm2#XMT5z_)>1lMq&!|dMOZtC{1YO@7o>uAhG{1X*;t7f8t;QIxeHwWu`h;63} zGNK*iq2{&XBDVyuec|aK!Es@95A?o$6PD^>A7suS)eJBG`vsT(Y)dCbJ&mxt5W~3% pfWOT91)28?!j_S_`UEBLe*rzRD~#sxia!7V002ovPDHLkV1kG$MG61_ literal 0 HcmV?d00001 diff --git a/client/javascript/TreeView.js b/client/javascript/TreeView.js index db8acdd..20221dd 100644 --- a/client/javascript/TreeView.js +++ b/client/javascript/TreeView.js @@ -287,6 +287,7 @@ var dialog = $('

                                                              ') .appendTo('body') .dialog({ + dialogClass: 'pagesections-dialog', modal: true, resizable: false, width: 500, @@ -367,6 +368,7 @@ var dialog = $('
                                                              ') .appendTo('body') .dialog({ + dialogClass: 'pagesections-dialog', modal: false, resizable: false, width: $(window).width() * 0.9, From 3e29da10d31d3d0c8e9dd680b0aad7056fb512f7 Mon Sep 17 00:00:00 2001 From: Severin Hauser Date: Thu, 16 Mar 2023 12:10:08 +0100 Subject: [PATCH 75/82] chore: update composer dependencies, re-add version --- composer.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cdc1384..db6b4ac 100755 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "flxlabs/silverstripe-pagesections", "description": "Adds configurable page sections and elements to your SilverStripe project.", + "version": "0.2.0", "type": "silverstripe-vendormodule", "homepage": "http://github.com/flxlabs/silverstripe-pagesections", "keywords": [ @@ -23,7 +24,8 @@ "require": { "silverstripe/framework": "^4.3.0", "symbiote/silverstripe-gridfieldextensions": "^3", - "silverstripe/vendor-plugin": "^1.0" + "silverstripe/vendor-plugin": "^1.0", + "silverstripe/versioned": "^1" }, "autoload": { "psr-4": { @@ -34,5 +36,11 @@ "expose": [ "client" ] + }, + "config": { + "allow-plugins": { + "composer/installers": true, + "silverstripe/vendor-plugin": true + } } } From 6f255f44c42faab7343590a07cbc9f0a474ede9e Mon Sep 17 00:00:00 2001 From: Severin Hauser Date: Tue, 21 Mar 2023 11:31:26 +0100 Subject: [PATCH 76/82] feat: reduce number of created versions --- .gitignore | 3 +++ src/PageElement.php | 24 ++++++++++++++++++++---- src/PageElementSelfRel.php | 12 ++++++++++-- src/PageSection.php | 7 ++++++- src/PageSectionPageElementRel.php | 12 ++++++++++-- 5 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3698562 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +resources/ \ No newline at end of file diff --git a/src/PageElement.php b/src/PageElement.php index 37e0818..19bb5dd 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -131,12 +131,20 @@ public function onAfterWrite() if (Versioned::get_stage() == Versioned::DRAFT && $this->isChanged("__Counter", DataObject::CHANGE_VALUE)) { foreach ($this->PageSections() as $section) { $section->__Counter++; - $section->write(); + if ($section->isLiveVersion()) { + $section->write(); + } else { + $section->writeWithoutVersion(); + } } foreach ($this->Parents() as $parent) { $parent->__Counter++; - $parent->write(); + if ($parent->isLiveVersion()) { + $parent->write(); + } else { + $parent->writeWithoutVersion(); + } } } } @@ -148,7 +156,11 @@ public function onAfterDelete() if (Versioned::get_stage() == Versioned::DRAFT) { foreach ($this->PageSections() as $section) { $section->__Counter++; - $section->write(); + if ($section->isLiveVersion()) { + $section->write(); + } else { + $section->writeWithoutVersion(); + } } } } @@ -158,7 +170,11 @@ public function onAfterArchive() if (Versioned::get_stage() == Versioned::DRAFT) { foreach ($this->PageSections() as $section) { $section->__Counter++; - $section->write(); + if ($section->isLiveVersion()) { + $section->write(); + } else { + $section->writeWithoutVersion(); + } } } } diff --git a/src/PageElementSelfRel.php b/src/PageElementSelfRel.php index 0006c3c..4fa9787 100644 --- a/src/PageElementSelfRel.php +++ b/src/PageElementSelfRel.php @@ -36,7 +36,11 @@ public function onAfterWrite() if (!$this->__NewOrder && Versioned::get_stage() == Versioned::DRAFT) { $this->Parent()->__Counter++; - $this->Parent()->write(); + if ($this->Parent()->isLiveVersion()) { + $this->Parent()->write(); + } else { + $this->Parent()->writeWithoutVersion(); + } } } @@ -46,7 +50,11 @@ public function onAfterDelete() if (Versioned::get_stage() == Versioned::DRAFT) { $this->Parent()->__Counter++; - $this->Parent()->write(); + if ($this->Parent()->isLiveVersion()) { + $this->Parent()->write(); + } else { + $this->Parent()->writeWithoutVersion(); + } } } } diff --git a/src/PageSection.php b/src/PageSection.php index 3b48ea0..5f8e379 100644 --- a/src/PageSection.php +++ b/src/PageSection.php @@ -68,7 +68,12 @@ public function onAfterWrite() if (!$this->__isNew && Versioned::get_stage() == Versioned::DRAFT && $this->isChanged("__Counter", DataObject::CHANGE_VALUE)) { $this->Parent()->__PageSectionCounter++; - $this->Parent()->write(); + // Only create a new version when the previous one is e published one. + if ($this->Parent()->isLiveVersion()) { + $this->Parent()->write(); + } else { + $this->Parent()->writeWithoutVersion(); + } } } diff --git a/src/PageSectionPageElementRel.php b/src/PageSectionPageElementRel.php index 8649880..c4f1e3a 100644 --- a/src/PageSectionPageElementRel.php +++ b/src/PageSectionPageElementRel.php @@ -36,7 +36,11 @@ public function onAfterWrite() if (!$this->__NewOrder && Versioned::get_stage() == Versioned::DRAFT) { $this->PageSection()->__Counter++; - $this->PageSection()->write(); + if ($this->PageSection()->isLiveVersion()) { + $this->PageSection()->write(); + } else { + $this->PageSection()->writeWithoutVersion(); + } } } @@ -46,7 +50,11 @@ public function onAfterDelete() if (Versioned::get_stage() == Versioned::DRAFT) { $this->PageSection()->__Counter++; - $this->PageSection()->write(); + if ($this->PageSection()->isLiveVersion()) { + $this->PageSection()->write(); + } else { + $this->PageSection()->writeWithoutVersion(); + } } } } From a8c25796b968632d3c9402e50142844cccbcc244 Mon Sep 17 00:00:00 2001 From: Severin Hauser Date: Wed, 22 Mar 2023 09:51:30 +0100 Subject: [PATCH 77/82] feat: move add existing to context menus fixes #8 --- client/css/TreeView.css | 5 + client/javascript/TreeView.js | 52 +++++++- client/javascript/lang/de.js | 3 +- client/javascript/lang/en.js | 1 + src/TreeView.php | 123 ++++++++++++------ .../PageSections/TreeViewFindExistingForm.ss | 4 +- 6 files changed, 136 insertions(+), 52 deletions(-) diff --git a/client/css/TreeView.css b/client/css/TreeView.css index cadd844..8665d72 100644 --- a/client/css/TreeView.css +++ b/client/css/TreeView.css @@ -337,6 +337,11 @@ fill: #008A00; } +.add-existing-search-results > .add-existing-search-items { + margin-left: 0; + margin-right: 0; +} + /** * Dialog */ diff --git a/client/javascript/TreeView.js b/client/javascript/TreeView.js index 20221dd..a318ef1 100644 --- a/client/javascript/TreeView.js +++ b/client/javascript/TreeView.js @@ -12,6 +12,11 @@ this.addLabel = function(label) { this.$menu.append("
                                                            • " + label + '
                                                            • '); }; + this.addAddExistingButton = function(label, onClick = function() {}) { + var $li = $(""); + $li.click(onClick); + this.$menu.append($li); + } this.addItem = function(type, label, onClick = function() {}) { var $li = $("
                                                            • " + label + '
                                                            • '); $li.click(onClick); @@ -162,7 +167,11 @@ '.add-existing-search-dialog-treeview .add-existing-search-items a' ).entwine({ onclick: function() { - var link = this.closest('.add-existing-search-items').data('add-link'); + var items = this.closest('.add-existing-search-items'); + var link = items.data('add-link'); + var parents = items.data('add-parents'); + var itemId = items.data('add-item-id'); + var sort = items.data('add-sort'); var id = this.data('id'); var dialog = this.closest('.add-existing-search-dialog-treeview') @@ -176,6 +185,18 @@ { name: 'id', value: id + }, + { + name: 'parents', + value: parents || [] + }, + { + name: 'itemId', + value: itemId || null + }, + { + name: 'sort', + value: sort } ] }, @@ -282,8 +303,7 @@ var name = $treeView.data('name'); var url = $treeView.data('url'); - // Setup find existing button - $treeView.find('button[name=action_FindExisting]').click(function() { + function showFindExistingDialog(parents, itemId, sort) { var dialog = $('
                                                              ') .appendTo('body') .dialog({ @@ -298,14 +318,21 @@ .remove(); } }); - - var url = $.get($treeView.data('url') + '/search'); + var search = new URLSearchParams(); + search.append('parents', JSON.stringify(parents)); + if (sort) { + search.append('sort', sort); + } + if (itemId) { + search.append('itemId', itemId); + } + var url = $.get($treeView.data('url') + '/search?' + search.toString()); dialog .addClass('add-existing-search-dialog-treeview') .data('treeview', $treeView) .data('origUrl', url) .loadDialog(url); - }); + } // Add new button at the very top $treeView @@ -324,6 +351,10 @@ menu.addLabel( ss.i18n._t('PageSections.TreeView.AddAChild', 'Add a child') ); + menu.addAddExistingButton(ss.i18n._t('PageSection.TreeView.FindExisting', 'Find existing'), function() { + showFindExistingDialog([], null, 1); + menu.remove(); + }); $.each(elems, function(key, value) { menu.addItem(key, value, function() { $treeView.addItem([], null, key, 1); @@ -405,6 +436,10 @@ menu.addLabel( ss.i18n._t('PageSections.TreeView.AddAChild', 'Add a child') ); + menu.addAddExistingButton(ss.i18n._t('PageSection.TreeView.FindExisting', 'Find existing'), function() { + showFindExistingDialog(parents, itemId, 1); + menu.remove(); + }); $.each(elems, function(key, value) { menu.addItem(key, value, function() { $treeView.addItem(parents, itemId, key, 1); @@ -432,7 +467,10 @@ 'Add new element' ) ); - + menu.addAddExistingButton(ss.i18n._t('PageSection.TreeView.FindExisting', 'Find existing'), function() { + showFindExistingDialog(parents.slice(0, parents.length - 1), parents[parents.length - 1], $item.data('sort') + 1); + menu.remove(); + }); $.each(elems, function(key, value) { menu.addItem(key, value, function() { $treeView.addItem( diff --git a/client/javascript/lang/de.js b/client/javascript/lang/de.js index 89b4d9a..a967bd9 100644 --- a/client/javascript/lang/de.js +++ b/client/javascript/lang/de.js @@ -3,7 +3,8 @@ if (typeof(ss) === 'undefined' || typeof(ss.i18n) === 'undefined') { console.error('Class ss.i18n not defined'); // eslint-disable-line no-console } } else { - ss.i18n.addDictionary('en', { + ss.i18n.addDictionary('de', { + "PageSections.GridField.FindExisting": "Bestehendes Element suchen", "PageSections.GridField.AddAChild": "Unterelement hinzufügen", "PageSections.GridField.Delete": "Löschen", "PageSections.GridField.DeleteAChild": "Endgültig löschen", diff --git a/client/javascript/lang/en.js b/client/javascript/lang/en.js index 615f3ee..21f6747 100644 --- a/client/javascript/lang/en.js +++ b/client/javascript/lang/en.js @@ -4,6 +4,7 @@ if (typeof(ss) === 'undefined' || typeof(ss.i18n) === 'undefined') { } } else { ss.i18n.addDictionary('en', { + "PageSections.GridField.FindExisting": "Find existing", "PageSections.GridField.AddAChild": "Add a child", "PageSections.GridField.Delete": "Delete", "PageSections.GridField.DeleteAChild": "Finally delete", diff --git a/src/TreeView.php b/src/TreeView.php index b6b70fc..7fe0abb 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -25,6 +25,9 @@ class TreeView extends FormField */ protected $sortField = 'SortOrder'; protected $parent = null; + /** + * @var \SilverStripe\ORM\Search\SearchContext + */ protected $context = null; protected $opens = null; @@ -280,53 +283,58 @@ public function add($request) // If we have an id then add an existing item... if (isset($data["id"])) { - $element = PageElement::get()->byID($data["id"]); - if (!$element) { + $existing = true; + $child = PageElement::get()->byID($data["id"]); + if (!$child) { Controller::curr()->getResponse()->setStatusCode( 400, "Could not find PageElement with id " . $data['id'] ); return $this->FieldHolder(); } - - $this->getItems()->Add($element); + $type = $child->ClassName; } else { // ...otherwise add a completely new item - $itemId = isset($data["itemId"]) ? intval($data["itemId"]) : null; + $existing = false; $type = $data["type"]; $child = $type::create(); - $child->Name = "New " . $child->singular_name(); - - $sort = isset($data["sort"]) ? intval($data["sort"]) : 0; - $sortBy = $this->getSortField(); - $sortArr = [$sortBy => $sort]; - - // If we have an itemId then we're adding to another element - // otherwise we're adding to the root - if ($itemId) { - $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); - $path = array_merge($parents, [$itemId]); - - $item = PageElement::get()->byID($itemId); - if (!in_array($type, $item->getAllowedPageElements())) { - Controller::curr()->getResponse()->setStatusCode( - 400, - "The type " . $type . " is not allowed as a child of " . $item->ClassName - ); - return $this->FieldHolder(); - } + } + + $itemId = isset($data["itemId"]) ? intval($data["itemId"]) : null; + $sort = isset($data["sort"]) ? intval($data["sort"]) : 0; + $sortBy = $this->getSortField(); + $sortArr = [$sortBy => $sort]; + + // If we have an itemId then we're adding to another element + // otherwise we're adding to the root + if ($itemId) { + $parents = array_values(array_filter(explode(",", $data["parents"]), 'strlen')); + $path = array_merge($parents, [$itemId]); + $item = PageElement::get()->byID($itemId); + if (!in_array($type, $item->getAllowedPageElements())) { + Controller::curr()->getResponse()->setStatusCode( + 400, + "The type " . $type . " is not allowed as a child of " . $item->ClassName + ); + return $this->FieldHolder(); + } + + if (!$existing) { $child->write(); - $item->Children()->Add($child, $sortArr); - $item->write(); + } + $item->Children()->Add($child, $sortArr); + $item->write(); - // Make sure we can see the child - $this->openItem(array_merge($path, [$item->ID])); - } else { + // Make sure we can see the child (an its children) + $this->openRecursive($child, [$item]); + } else { + if ($existing) { $child->write(); - $this->getItems()->Add($child, $sortArr); + $this->openRecursive($child); } + $this->getItems()->Add($child, $sortArr); } return $this->FieldHolder(); @@ -413,6 +421,15 @@ public function delete($request) return $this->FieldHolder(); } + protected function getTarget($itemId) + { + $target = $this->section; + if ($itemId && $targetElement = PageElement::get()->byID($itemId)) { + $target = $targetElement; + } + return $target; + } + /** * This action is called when the find existing dialog is shown. * @param \SilverStripe\Control\HTTPRequest $request @@ -420,6 +437,21 @@ public function delete($request) */ public function search($request) { + $parents = $request->getVar('parents') ?? []; + $itemId = $request->getVar('itemId'); + $sort = $request->getVar('sort') ?? 9999; + $target = $this->getTarget($itemId); + + $fields = $this->context->getFields(); + if ($classField = $fields->fieldByName('ClassName')) { + $allowed = $target->getAllowedPageElements(); + $classField->setSource(array_combine($allowed, $allowed)); + } + + $fields->push(HiddenField::create('parents', 'parents', $parents)); + $fields->push(HiddenField::create('itemId', 'itemId', $itemId)); + $fields->push(HiddenField::create('sort', 'sort', $sort)); + $form = Form::create( $this, 'search', @@ -435,7 +467,11 @@ public function search($request) // Check if we're requesting the form for the first time (we return the template) // or if this is a submission (we return the form, so it calls the submitted action) - if (count($request->requestVars()) === 0) { + $extraRequestVars = array_filter($request->requestVars(), function ($name) { + return !in_array($name, ['parents', 'sort', 'itemId']); + }, ARRAY_FILTER_USE_KEY); + + if (count($extraRequestVars) === 0) { return $form->forAjaxTemplate(); } return $form; @@ -448,14 +484,24 @@ public function search($request) */ public function doSearch($data, $form) { - $list = $this->context->getQuery($data, false, false); - $allowed = $this->section->getAllowedPageElements(); + $list = $this->context->getQuery([ + 'ClassName' => $data['ClassName'], + 'Name' => $data['Name'], + ], false, false); + $allowed = $this->getTarget($data['itemId'])->getAllowedPageElements(); // Remove all disallowed classes - $list = $list->filter("ClassName", $allowed); + $list = $list->filter(["ClassName" => $allowed, "Name:not" => [null, '']]); + $sql = $list->dataQuery()->getFinalisedQuery()->sql($parameters); + $list = new PaginatedList($list, $data); $data = $this->customise([ 'SearchForm' => $form, 'Items' => $list, + 'AddArguments' => [ + 'Parents' => $data['parents'], + 'ItemID' => $data['itemId'], + 'Sort' => $data['sort'], + ], ]); return $data->renderWith("FLXLabs\PageSections\TreeViewFindExistingForm"); } @@ -585,8 +631,6 @@ public function doSave($data, $form) public function getItems() { return $this->section->Elements(); - /*return $this->parent->ClassName == PageSection::class ? - $this->parent->Elements() : $this->parent->Children();*/ } /** @@ -642,11 +686,6 @@ public function FieldHolder($properties = array()) $content .= ArrayData::create([ "Button" => $addButton, ])->renderWith("\FLXLabs\PageSections\TreeViewAddNewButton"); - - // Create the find existing button - $findExisting = TreeViewFormAction::create($this, 'FindExisting', 'Find existing'); - $findExisting->addExtraClass("btn font-icon-search tree-actions-findexisting"); - $content .= $findExisting->forTemplate(); } $content .= "
                                                              "; diff --git a/templates/FLXLabs/PageSections/TreeViewFindExistingForm.ss b/templates/FLXLabs/PageSections/TreeViewFindExistingForm.ss index 969fc94..f5893bd 100644 --- a/templates/FLXLabs/PageSections/TreeViewFindExistingForm.ss +++ b/templates/FLXLabs/PageSections/TreeViewFindExistingForm.ss @@ -3,10 +3,10 @@ $SearchForm

                                                              <%t GridFieldExtensions.RESULTS "Results" %>

                                                              <% if $Items %> -
                                                                +
                                                                  <% loop $Items %>
                                                                • - $Title + $Title ($ClassName)
                                                                • <% end_loop %>
                                                                From 3bbf4506c0a6be1a2e85e087ffaa403dcfed553e Mon Sep 17 00:00:00 2001 From: Severin Hauser Date: Wed, 22 Mar 2023 09:55:59 +0100 Subject: [PATCH 78/82] chore: remove version field from composer.json fixes #7 --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index db6b4ac..d85bd06 100755 --- a/composer.json +++ b/composer.json @@ -1,7 +1,6 @@ { "name": "flxlabs/silverstripe-pagesections", "description": "Adds configurable page sections and elements to your SilverStripe project.", - "version": "0.2.0", "type": "silverstripe-vendormodule", "homepage": "http://github.com/flxlabs/silverstripe-pagesections", "keywords": [ From 57dc2c3b0e174604f0af74b336fecee9d783607a Mon Sep 17 00:00:00 2001 From: Severin Hauser Date: Wed, 22 Mar 2023 10:08:59 +0100 Subject: [PATCH 79/82] fix: add missing styling for add existing menu --- client/css/TreeView.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/css/TreeView.css b/client/css/TreeView.css index 8665d72..d315325 100644 --- a/client/css/TreeView.css +++ b/client/css/TreeView.css @@ -227,6 +227,15 @@ border-top: 1px solid #ccc; } +.treeview-menu li.treeview-menu__add-existing { + position: relative; +} +.treeview-menu li.treeview-menu__add-existing::before { + position: absolute; + left: 7px; + top: 12px; +} + /** * Orderable rows */ From 1f9cf97a12fe595cad6495dc4c77ad962c23b31e Mon Sep 17 00:00:00 2001 From: Severin Hauser Date: Mon, 27 Mar 2023 17:50:18 +0200 Subject: [PATCH 80/82] feat: reorder add menu, move name and version to uses tab --- .gitignore | 3 ++- client/javascript/TreeView.js | 28 ++++++++++++++++------------ src/PageElement.php | 15 ++++++++------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 3698562..e3bb871 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ vendor/ composer.lock -resources/ \ No newline at end of file +resources/ +.vscode \ No newline at end of file diff --git a/client/javascript/TreeView.js b/client/javascript/TreeView.js index a318ef1..c231728 100644 --- a/client/javascript/TreeView.js +++ b/client/javascript/TreeView.js @@ -351,16 +351,16 @@ menu.addLabel( ss.i18n._t('PageSections.TreeView.AddAChild', 'Add a child') ); - menu.addAddExistingButton(ss.i18n._t('PageSection.TreeView.FindExisting', 'Find existing'), function() { - showFindExistingDialog([], null, 1); - menu.remove(); - }); $.each(elems, function(key, value) { menu.addItem(key, value, function() { $treeView.addItem([], null, key, 1); menu.remove(); }); }); + menu.addAddExistingButton(ss.i18n._t('PageSection.TreeView.FindExisting', 'Find existing'), function() { + showFindExistingDialog([], null, 1); + menu.remove(); + }); menu.show(event.pageX, event.pageY); }); @@ -436,16 +436,16 @@ menu.addLabel( ss.i18n._t('PageSections.TreeView.AddAChild', 'Add a child') ); - menu.addAddExistingButton(ss.i18n._t('PageSection.TreeView.FindExisting', 'Find existing'), function() { - showFindExistingDialog(parents, itemId, 1); - menu.remove(); - }); $.each(elems, function(key, value) { menu.addItem(key, value, function() { $treeView.addItem(parents, itemId, key, 1); menu.remove(); }); }); + menu.addAddExistingButton(ss.i18n._t('PageSection.TreeView.FindExisting', 'Find existing'), function() { + showFindExistingDialog(parents, itemId, 1); + menu.remove(); + }); menu.show(event.pageX, event.pageY); }); @@ -467,10 +467,6 @@ 'Add new element' ) ); - menu.addAddExistingButton(ss.i18n._t('PageSection.TreeView.FindExisting', 'Find existing'), function() { - showFindExistingDialog(parents.slice(0, parents.length - 1), parents[parents.length - 1], $item.data('sort') + 1); - menu.remove(); - }); $.each(elems, function(key, value) { menu.addItem(key, value, function() { $treeView.addItem( @@ -482,6 +478,14 @@ menu.remove(); }); }); + menu.addAddExistingButton(ss.i18n._t('PageSection.TreeView.FindExisting', 'Find existing'), function() { + showFindExistingDialog( + parents.slice(0, parents.length - 1), + parents[parents.length - 1], + $item.data('sort') + 1 + ); + menu.remove(); + }); menu.show(event.pageX, event.pageY); }); diff --git a/src/PageElement.php b/src/PageElement.php index 19bb5dd..2b0a4d0 100755 --- a/src/PageElement.php +++ b/src/PageElement.php @@ -8,6 +8,7 @@ use SilverStripe\Forms\GridField\GridFieldConfig_Base; use SilverStripe\Forms\GridField\GridFieldDataColumns; use SilverStripe\Forms\ReadonlyField; +use SilverStripe\Forms\TextField; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataObject; use SilverStripe\Versioned\Versioned; @@ -228,18 +229,18 @@ public function getAllUses() public function getCMSFields() { $fields = parent::getCMSFields(); - $fields->removeByName('Pages'); - $fields->removeByName('Parents'); - $fields->removeByName("PageSections"); - $fields->removeByName('__Counter'); + $fields->removeByName(["Name", "Pages", "Parents", "PageSections", "__Counter"]); $fields->removeByName("Children"); // Add our newest version as a readonly field $fields->addFieldsToTab( - "Root.Main", - ReadonlyField::create("Version", "Version", $this->Version), - "Title" + "Root.Uses", + [ + TextField::create("Name", "Name", $this->Name), + ReadonlyField::create("Version", "Version", $this->Version), + ], + "Uses" ); // Create an array of all places this PageElement is shown From 57bbd823719258669cbb5fd60456587bd05d8ca6 Mon Sep 17 00:00:00 2001 From: Severin Hauser Date: Tue, 28 Mar 2023 10:46:26 +0200 Subject: [PATCH 81/82] fix: open element when adding new item to it --- src/TreeView.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/TreeView.php b/src/TreeView.php index 7fe0abb..b4063ef 100644 --- a/src/TreeView.php +++ b/src/TreeView.php @@ -327,12 +327,17 @@ public function add($request) $item->Children()->Add($child, $sortArr); $item->write(); - // Make sure we can see the child (an its children) - $this->openRecursive($child, [$item]); + // Make sure we can see the child (and its children) + if ($existing) { + $this->openRecursive($child, [$item]); + } else { + $this->openItem(array_merge($path, [$item->ID])); + } } else { if ($existing) { - $child->write(); $this->openRecursive($child); + } else { + $child->write(); } $this->getItems()->Add($child, $sortArr); } From 5e1fb75f653a0aba30f675db7b11d21c0e48aaac Mon Sep 17 00:00:00 2001 From: Severin Hauser Date: Wed, 19 Apr 2023 13:52:38 +0200 Subject: [PATCH 82/82] chore: update readme --- README.md | 213 +++++++++++++++--------------------------------------- 1 file changed, 60 insertions(+), 153 deletions(-) diff --git a/README.md b/README.md index 347fcfd..a515dfe 100755 --- a/README.md +++ b/README.md @@ -1,168 +1,75 @@ -# Page Sections module - -**Adds configurable page sections and elements to your SilverStripe project.** +# Silverstripe Pagesections +**Elemental alternative for configurable page sections and elements.** ## Introduction -This module provides page sections for SilverStripe 3.+ projects. -Page sections are areas on a page where CMS users can add their own content -in a structured way. -Pages can have none, one or more page sections attached to them. Each page section -is made up of various page elements, which themselves can or cannot have other -page elements as children. The page elements can be edited in a tree-like gridview -on the page or the page elements. - +This module provides page sections for SilverStripe 4.x projects. Page sections are areas on a page where CMS users can add their own content in a structured way. Pages can have none, one or more page sections attached to them. Each page section is made up of various page elements, which themselves can or cannot have other page elements as children. -## Setup +## Installation -You can either add this module to your composer file using -``` +```sh composer require flxlabs/silverstripe-pagesections ``` -or download the git repository and add a folder called `pagesections` to the top level -of your project and drop the code in there. - -> By default the module is configured to do nothing, and also doesn't add any relations. -Please refer to the **Configure** section for more information. - -## Configure +Add the extension to the DataObject that should contain a PageSection: -> By default this module does **NOT** add any relations or data to any of your pages. -Follow the steps below to start working with page sections. - -### Pages & Page sections +```yml +Page: + extensions: + - PageSectionsExtension + +``` -Your pages can have none (default), one or more page sections, which are areas on -your page where CMS users can add various page elements to customize the look and feel -of the page in a structered/limited way. +By default the DataObject will have a PageSection called `Main`. To add additional sections, or change the name of the default section, specify them in the `page_sections` key. -1. Add the following extensions to your `mysite/_config/config.yaml` on any pages -where you wish to have page sections: - ``` - Page: - extensions: - - PageSectionsExtension - - VersionedRelationsExtension - ``` - This will by default add **one** page section called `Main` to your page(s) - > **Make sure that the *VersionedRelationsExtension* comes after the *PageSectionsExtension*** +```yml +Page: + extensions: + - PageSectionsExtension + page_sections: + - Main + - Aside +``` -1. If you want more than one page section add the following to your `mysite/_config/config.yaml`: - ``` - Page: - extensions: - - PageSectionsExtension - - VersionedRelationsExtension - page_sections: - - Top - - Middle - - Bottom - ``` - Each of the listed names under the `page_sections` key will add a page section of that name to - the page. This will also override the default `Main` page section. - -1. Hit up `/dev/build?flush=1` in your browser to rebuild the database. You should see that new -relations are created for each of your page classes that you added page sections to. - - -### Page elements - -Page elements are the actual elements that are added to the page sections and then displayed -on the page. By default this package only contains the base class `PageElement` and not any -actually implemented page elements, but you can take a look at the `examples` folder to see -how some common page elements would look. - -Page elements by default have `Children`, which are other page elements, and a `Title`, which -is a `Varchar(255)`. - - -#### PHP - -Below we will demonstrate how to create a simple page element. - -1. Create a new file in your `mysite/code` folder called `TextElement.php` - -1. Add the following code to that file: - ```php - 'HTMLText', - ); - - public function getCMSFields() { - $fields = parent::getCMSFields(); - $fields->removeByName('Children'); - - return $fields; - } - - public static function getAllowedPageElements() { - return array(); - } - - public function getGridFieldPreview() { - return $this->Content; - } - } - ``` - -1. Go to `/dev/build?flush=1` in your browser to load the new class and create database entries. - -The class we just created adds the `TextElement` page element, which has a `Content` field that -allows adding html content, and it also removes the `Children` field from it's own CMS fields, -which means that we cannot add any children to this element. This is also enforced in the -`getAllowedPageElements()` function, which returns a list of all the class names of allowed -child elements. - -The `getGridFieldPreview()` function returns the content that should be displayed in the gridfield -when editing the page elements. - - -#### Template - -Since this module comes with a default template for the `PageElement` class the `TextElement` -we just created will already render on your page, but most likely not the way you want it to. - -There are several ways to customize the look of page elements, analog to how SilverStripe -page templates work. - -You can use the following variables/function in any of your page element template files: - -| Variable/Function | Description | -|---|---| -| $ClassName | This is the classname of the current page element. | -| $Page | This is the current page that the page element is being displayed on. | -| $Parents | This is an array of the parents of this page element, traveling up the hierarchy. | -| $Layout | This will render the content of the actual page element type, analog to the `$Layout` variable used in the main `Page.ss` file. | -| $RenderChildren($ParentList) | This will loop through all the children of this page element and render them | - -Following is an example building on the `TextElement` which was added above. - -1. Add a file called `PageElement.ss` to your `/themes/{name}/templates` folder. - **This is the main template file for all your page sections.** - - As an example let's use the following content: - ``` -
                                                                -

                                                                $Title

                                                                - $Layout -
                                                                - $RenderChildren($ParentList) -
                                                                -
                                                                - ``` +Make sure to run `dev/build` and flush. + +## Usage + +Defining an element: + +```php + 'HTMLText', + ]; + + // Page elements can have other page elements as children. + // Use this method to restrict allowed childre. + public function getAllowedPageElements() + { + return [ + // YourElement::class + ]; + } + + // This will be used to preview the content in the CMS editor + public function getGridFieldPreview() + { + return $this->dbObject('Content')->Summary(); + } +} +``` -1. Add a file called `TextElement.ss` to your `/themes/{name}/templates/Layout` folder. - Now let's add the following content to that file: - ``` - $Content - ``` +To render an element, create a Template. To render a page section use the `RenderPageElements` method exposed by the PageSectionsExtension: -Following the above two steps will add a template for all page elements, as well as a specific -layout template for the `TextElement` page element. +```html +
                                                                + $RenderPageSection('SectionName') +
                                                                +```