diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..723ef36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.idea
\ No newline at end of file
diff --git a/_config.php b/_config.php
new file mode 100755
index 0000000..b3d9bbc
--- /dev/null
+++ b/_config.php
@@ -0,0 +1 @@
+Elements()->renderWith(static::class.'_AccordionArea');
+ }
+}
diff --git a/src/Elements/CustomSnippetElement.php b/src/Elements/CustomSnippetElement.php
new file mode 100755
index 0000000..db81241
--- /dev/null
+++ b/src/Elements/CustomSnippetElement.php
@@ -0,0 +1,42 @@
+replaceField(
+ 'HTML',
+ TextareaField::create('HTML')
+ );
+
+ return $fields;
+ }
+}
diff --git a/src/Elements/ElementListExtension.php b/src/Elements/ElementListExtension.php
new file mode 100644
index 0000000..41fe7bf
--- /dev/null
+++ b/src/Elements/ElementListExtension.php
@@ -0,0 +1,15 @@
+owner->getRequest()->param('ID');
+
+ if (!$id) {
+ user_error('No element ID supplied', E_USER_ERROR);
+ return false;
+ }
+
+ /** @var SiteTree $elementOwner */
+ //$elementOwner = $this->owner->data();
+
+ /*$elementalAreaRelations = $this->owner->getElementalRelations();
+
+ if (!$elementalAreaRelations) {
+ user_error(get_class($this->owner) . ' has no ElementalArea relationships', E_USER_ERROR);
+ return false;
+ }*/
+
+ $element = BaseElement::get()->byID($id);
+
+ if ($element) {
+ return $element->getController();
+ }
+
+ user_error('Element $id not found', E_USER_ERROR);
+ return false;
+ }
+}
diff --git a/src/Elements/EmptyPageController.php b/src/Elements/EmptyPageController.php
new file mode 100644
index 0000000..9ba50f9
--- /dev/null
+++ b/src/Elements/EmptyPageController.php
@@ -0,0 +1,23 @@
+dataRecord->Title = SiteConfig::current_site_config()->getField('Title');
+
+ $this->setFailover($this->dataRecord);
+ }
+
+ public static function DefaultContainer()
+ {
+ return \Page::DefaultContainer();
+ }
+}
diff --git a/src/Elements/InstagramElement.php b/src/Elements/InstagramElement.php
new file mode 100755
index 0000000..1aecbc2
--- /dev/null
+++ b/src/Elements/InstagramElement.php
@@ -0,0 +1,103 @@
+ 'Varchar(255)',
+ 'Tag' => 'Varchar(255)',
+ 'DisplayProfile' => 'Boolean(0)',
+ 'DisplayBiography' => 'Boolean(0)',
+ 'DisplayGallery' => 'Boolean(0)',
+ 'DisplayCaptions' => 'Boolean(0)',
+ ];
+
+ private static $defaults = [
+ 'DisplayGallery' => true,
+ ];
+
+ public function getType()
+ {
+ return self::$singular_name;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAttributes(): array
+ {
+ return [
+ 'data-username' => $this->Username,
+ 'data-display-profile' => $this->DisplayProfile,
+ 'data-display-biography' => $this->DisplayBiography,
+ 'data-display-gallery' => $this->DisplayGallery,
+ 'data-display-captions' => $this->DisplayCaptions,
+ 'data-items' => 12,
+ ];
+ }
+ /**
+ * Custom attributes to process.
+ *
+ * @param array $attributes
+ *
+ * @return string
+ */
+ public function AttributesHTML($attributes = null): string
+ {
+ if (!$attributes) {
+ $attributes = $this->getAttributes();
+ }
+
+ $attributes = (array) $attributes;
+
+ $attributes = array_filter($attributes, static function ($v) {
+ return ($v || $v === 0 || $v === '0');
+ });
+
+ // Create markup
+ $parts = [];
+
+ foreach ($attributes as $name => $value) {
+ if ($value === true) {
+ $value = $name;
+ } else if (is_scalar($value)) {
+ $value = (string) $value;
+ } else {
+ $value = json_encode($value);
+ }
+
+ $parts[] = sprintf('%s=%s', Convert::raw2att($name), Convert::raw2att($value));
+ }
+
+ return implode(' ', $parts);
+ }
+
+ public function FeedLink()
+ {
+ return 'https://www.instagram.com/'.($this->Username ? $this->Username : 'explore/tags/'.$this->Tag).'/';
+ }
+
+ public function FeedTitle()
+ {
+ return ($this->Username ? '@'.$this->Username : '#'.$this->Tag);
+ }
+}
diff --git a/src/Elements/MapElement.php b/src/Elements/MapElement.php
new file mode 100755
index 0000000..3b8796a
--- /dev/null
+++ b/src/Elements/MapElement.php
@@ -0,0 +1,98 @@
+remove('HTML');
+
+ $tab = $fields->findOrMakeTab('Root.MapPins');
+ $tab->setTitle('Description');
+ $tab->push(HTMLEditorField::create('HTML', 'Content'));
+
+ $fields->addFieldsToTab('Root.Main', [
+ NumericField::create('MapZoom', 'Initial Map Zoom (enter a number from 0 to 24)'),
+ GridField::create(
+ 'Locations',
+ 'Locations',
+ $this->owner->Locations(),
+ $cfg = GridFieldConfig_RelationEditor::create(100)
+ )
+ ]);
+
+ $cfg->getComponentByType(GridFieldDataColumns::class)->setFieldFormatting([
+ 'ShowAtMap' => static function ($v, $obj) {
+ return $v ? 'YES' : 'NO';
+ }
+ ]);
+ $cfg->addComponent(new BulkManager());
+
+ return $fields;
+ }
+
+ public static function MapAPIKey(): string
+ {
+ $type = self::config()->get('map_type');
+
+ switch ($type) {
+ case 'mapbox':
+ $key = MapboxField::getAccessToken();
+ break;
+ case 'google-maps':
+ $cfg = Config::inst()->get(GoogleMapField::class, 'default_options');
+ $key = $cfg['api_key'];
+ break;
+ default:
+ $key = '';
+ break;
+ }
+
+ return $key;
+ }
+}
diff --git a/src/Elements/SliderElement.php b/src/Elements/SliderElement.php
new file mode 100755
index 0000000..0e7302d
--- /dev/null
+++ b/src/Elements/SliderElement.php
@@ -0,0 +1,133 @@
+ 'Int',
+ ];
+
+ private static $extensions = [
+ FlexSlider::class,
+ ];
+
+ private static $owns = [
+ 'Slides',
+ ];
+
+ private $items;
+
+ public function getType()
+ {
+ return self::$singular_name;
+ }
+
+ public function getSlideWidth()
+ {
+ return self::config()->get('slide_width');
+ }
+
+ public function getSlideHeight()
+ {
+ return self::config()->get('slide_height');
+ }
+
+ public function getCMSFields()
+ {
+ $fields = parent::getCMSFields();
+
+ // remove in case you don't need to provide this functionality
+ $fields->removeByName([
+ 'ConfigHD',
+ 'Animation',
+ 'Loop',
+ 'Animate',
+ 'ThumbnailNav',
+ 'SliderControlNav',
+ 'SliderDirectionNav',
+ 'CarouselControlNav',
+ 'CarouselDirectionNav',
+ 'CarouselThumbnailCt',
+ ]);
+
+ $fields->addFieldsToTab('Root.Settings', [
+ NumericField::create('Interval', 'Auto-play Interval'),
+ ]);
+
+ $grid = $fields->dataFieldByName('Slides');
+ if ($grid) {
+ $config = $grid->getConfig();
+
+ $columns = new GridFieldEditableColumns();
+ $columns->setDisplayFields([
+ 'Hide' => [
+ 'title' => 'Hide it?',
+ 'field' => CheckboxField::class,
+ ],
+ ]);
+
+ $config->addComponent($columns);
+ }
+
+ return $fields;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getSlideShow()
+ {
+ if ($this->items) {
+ return $this->items;
+ }
+
+ $date = date('Y-m-d H:i:s');
+ $this->items = $this->Slides()->filter([
+ 'Hide' => '0',
+ ])->filterByCallback(static function ($item, $list) use ($date) {
+ $on = $item->getField('DateOn');
+ $off = $item->getField('DateOff');
+
+ return ($on <= $date) && (!$off || $off > $date);
+ })->sort('SortOrder');
+
+ return $this->items;
+ }
+
+ public function onBeforeWrite()
+ {
+ parent::onBeforeWrite();
+
+ if (!$this->getField('Interval')) {
+ $this->setField('Interval', 5000);
+ }
+ }
+}
diff --git a/src/Elements/TeamMembersElement.php b/src/Elements/TeamMembersElement.php
new file mode 100755
index 0000000..574f1ba
--- /dev/null
+++ b/src/Elements/TeamMembersElement.php
@@ -0,0 +1,30 @@
+sort('RAND()');
+ }
+}
diff --git a/src/Extensions/BaseElementEx.php b/src/Extensions/BaseElementEx.php
new file mode 100755
index 0000000..3bc6552
--- /dev/null
+++ b/src/Extensions/BaseElementEx.php
@@ -0,0 +1,89 @@
+owner;
+ parent::updateCMSFields($fields);
+ $tab = $fields->findOrMakeTab('Root.Settings');
+
+ $tab->push(LiteralField::create(
+ 'ClassName',
+ '
'
+ .'
Class
'
+ .'
'.$obj->getField('ClassName').'
'
+ .'
'
+ ));
+
+ if($this->owner->ID) {
+ $tab->push(TreeDropdownField::create(
+ 'MoveElementIDToPage',
+ 'Move an element to page',
+ SiteTree::class
+ )->setEmptyString('(select an element to move)'));
+ }
+ }
+
+ public static function MoveElementToPage($ID, $moveToID)
+ {
+ $el = BaseElement::get()->byID($ID);
+ $page = SiteTree::get()->byID($moveToID);
+ if (!$page || !$el) {
+ return false;
+ }
+
+ $el->setField('ParentID', $page->ElementalArea()->ID);
+ $el->write();
+
+ return true;
+ }
+
+ public function updateCMSEditLink(&$link): void
+ {
+ if (!$this->owner->inlineEditable()) {
+ $page = $this->owner->getPage();
+
+ if (!$page || $page instanceof SiteTree) {
+ return;
+ }
+
+ // As non-page DataObject's are managed via GridFields, we have to grab their CMS edit URL
+ // and replace the trailing /edit/ with a link to the nested ElementalArea edit form
+ $relationName = $this->owner->getAreaRelationName();
+ $link = preg_replace(
+ '/edit\/?$/',
+ "ItemEditForm/field/{$relationName}/item/{$this->owner->ID}/edit/",
+ $page->CMSEditLink()
+ );
+ }
+ }
+
+
+ public function onBeforeWrite()
+ {
+ parent::onBeforeWrite();
+
+ $moveID = $this->owner->getField('MoveElementIDToPage');
+ if($this->owner->ID && $moveID) {
+ self::MoveElementToPage($this->owner->ID, $moveID);
+ }
+ }
+}
diff --git a/src/Extensions/ElementContentWidget.php b/src/Extensions/ElementContentWidget.php
new file mode 100755
index 0000000..eed6fe6
--- /dev/null
+++ b/src/Extensions/ElementContentWidget.php
@@ -0,0 +1,15 @@
+ 'Small (300px)',
+ '400' => 'Medium (400px)',
+ '600' => 'Big (600px)',
+ ];
+
+ private static $available_widths = [
+ '300' => 'Small (300px)',
+ '400' => 'Medium (400px)',
+ '600' => 'Big (600px)',
+ ];
+
+ private static $available_ratios = [
+ '1:1' => '1:1',
+ '3:2' => '3:2',
+ '2:3' => '2:3',
+ '16:9' => '16:9'
+ ];
+
+ private static $db = [
+ 'Resize' => 'Boolean(1)',
+ 'ManualWidth' => 'Boolean(0)',
+ 'ImageHeight' => 'Float',
+ 'ImageWidth' => 'Float',
+ 'ImageAspectRatio' => 'Text',
+ 'Content' => 'HTMLText',
+ ];
+
+ private static $has_one = [
+ 'ImageLink' => Link::class,
+ ];
+
+ public function updateCMSFields(FieldList $fields)
+ {
+ parent::updateCMSFields($fields);
+
+ $fields->insertBefore(
+ 'Image',
+ LinkField::create('ImageLinkID', 'Link')
+ );
+
+ $this->owner->ImageHeight = $this->getHeight();
+
+ $heights = Config::inst()->get(__CLASS__, 'available_heights');
+ $widths = Config::inst()->get(__CLASS__, 'available_widths');
+ $ratios = Config::inst()->get(__CLASS__, 'available_ratios');
+
+ $fields->replaceField('Resize', CheckboxField::create(
+ 'Resize',
+ 'Would you like to scale image?'
+ ));
+ $fields->removeByName(['ManualWidth','ImageWidth','ImageAspectRatio',]);
+
+ if (count($heights)) {
+ $fields->replaceField(
+ 'ImageHeight',
+ CompositeField::create(
+ CheckboxField::create(
+ 'ManualAspectRatio',
+ 'Set Aspect Ratio',
+ ($this->owner->getField('ImageAspectRatio') ? '1' : '0')
+ ),
+ DropdownField::create(
+ 'ImageAspectRatio',
+ 'Image Aspect Ratio (width:height)',
+ $ratios
+ )
+ ->setEmptyString('(original)')
+ ->displayIf('ManualAspectRatio')->isChecked()
+ ->andIf('ManualWidth')->isNotChecked()
+ ->end(),
+ DropdownField::create(
+ 'ImageHeight',
+ 'Image Height',
+ $heights,
+ $this->getHeight()
+ )
+ ->setEmptyString('(auto)')
+ ->displayIf('Resize')->isChecked()->end(),
+ CheckboxField::create('ManualWidth', 'Set Width Manually')
+ ->displayIf('Resize')->isChecked()
+ ->andIf('ManualAspectRatio')->isNotChecked()
+ ->end(),
+ DropdownField::create(
+ 'ImageWidth',
+ 'Image Width',
+ $widths
+ )
+ ->setEmptyString('(auto)')
+ ->displayIf('ManualWidth')->isChecked()
+ ->end()
+ )
+ );
+ } else {
+ $fields->dataFieldByName('ImageHeight')
+ ->setValue($this->getHeight());
+ }
+ }
+
+ public function ImageResized()
+ {
+ $image = $this->owner->Image();
+
+ if (!$this->owner->Resize) {
+ return $image;
+ }
+
+ $width = $this->getWidth();
+ $height = $this->getHeight();
+ $ratio = $this->owner->getField('ImageAspectRatio');
+
+ if ($ratio) {
+ $v = explode(':', $this->owner->getField('ImageAspectRatio'));
+ $x = $v[0];
+ $y = $v[1];
+
+ if ($width > 0 && $width !== 'auto') {
+ $height = $width*$y/$x;
+ echo 'a1';
+ } elseif ($height && $height > 0) {
+ $width = $height*$x/$y;
+ echo 'a2';
+ }
+ var_dump($width);
+ var_dump($height);
+ }
+
+ if (!$width || $width === 'auto') {
+ return $height > 0
+ ? $image->ScaleHeight($height)
+ : $image;
+ }
+
+ return $height > 0
+ ? $image->FocusFill($width, $height)
+ : $image->ScaleWidth($width);
+ }
+
+ public function getWidth()
+ {
+ $obj = $this->owner;
+ return $obj->getField('ManualWidth') && $obj->getField('ImageWidth')
+ ? $obj->getField('ImageWidth')
+ : $obj->getColumnWidthRecursive();
+ }
+
+ public function getHeight()
+ {
+ $height = $this->owner->getField('ImageHeight');
+ if ($height > 0) {
+ return $height;
+ }
+
+ $sibling = $this->owner->getSibling(false, [
+ 'ImageHeight:GreaterThan' => '0'
+ ]);
+
+ if ($sibling && $sibling->getField('ImageHeight')) {
+ return $sibling->getField('ImageHeight');
+ }
+
+ return 0;
+ }
+}
diff --git a/src/Extensions/ElementRows.php b/src/Extensions/ElementRows.php
new file mode 100755
index 0000000..4fca652
--- /dev/null
+++ b/src/Extensions/ElementRows.php
@@ -0,0 +1,303 @@
+ 'Fixed container',
+ 'container-fluid' => 'Fluid Container',
+ ];
+
+ private static $db = [
+ 'ContainerType' => 'Varchar(254)',
+ //'SidebarOnly' => 'Boolean(0)',
+ 'Size' => 'Enum("1,2,3,4,5,6,7,8,9,10,11,12,auto","auto")',
+ ];
+
+ public function updateCMSFields(FieldList $fields)
+ {
+ $obj = $this->owner;
+ parent::updateCMSFields($fields);
+
+ $fields->removeByName(['AvailableGlobally', 'TopPageLocale', 'TopPageID']);
+ //$fields->removeByName('SidebarOnly');
+
+ // move available globaly to main tab
+ $tab = $fields->findOrMakeTab('Root.Main');
+ $tab->push(CheckboxField::create('AvailableGlobally'));
+ //$tab->push(CheckboxField::create('SidebarOnly', 'Hidden (Sidebar Only)'));
+
+ // container type
+ if ($this->isRoot()) {
+ $tab->push(DropdownField::create(
+ 'ContainerType',
+ _t(__CLASS__.'.CONTAINERTYPE', 'Container Type'),
+ self::$container_styles
+ ));
+ } else {
+ $fields->removeByName('ContainerType');
+ }
+
+ // site-specific
+ $fields->removeByName('ContainerType');
+
+ // column size
+ if ($this->isColumn()) {
+ $sizes = $obj->dbObject('Size');
+
+ $sizeDropdown = DropdownField::create(
+ 'Size',
+ _t(
+ __CLASS__.'.SIZE',
+ 'Column Width (max 12 cols)'
+ ),
+ array_combine(
+ array_values($sizes->enumValues()),
+ [
+ '8.3% (1 of 12)',
+ '16.6% (2 of 12)',
+ '25% (3 of 12)',
+ '33% (4 of 12)',
+ '41.6% (5 of 12)',
+ '50% (6 of 12)',
+ '58.3% (7 of 12)',
+ '66.4% (8 of 12)',
+ '74.7% (9 of 12)',
+ '83% (10 of 12)',
+ '91.3% (11 of 12)',
+ '100% (12 of 12)',
+ 'auto',
+ ]
+ )
+ );
+ $tab->push($sizeDropdown);
+
+ // set default size if size field wasn't set or if it's new object without title set and the size is default
+ $size = $obj->getField('Size');
+ if (!$size || (!$obj->getField('Title') && $size === 'auto')) {
+ $sibling = $this->getSibling();
+
+ $defaultSize = $sizes->getDefaultValue();
+ $defaultSize = $sibling ? $sibling->getField('Size') : $defaultSize;
+
+ $obj->setField('Size', $defaultSize);
+ $sizeDropdown->setValue($defaultSize);
+ }
+ } else {
+ $fields->removeByName('Size');
+ }
+
+ // move parent elements
+ if ($this->isList()) {
+ $currEls = $obj->getField('Elements')->Elements();
+ if ($currEls->count()) {
+ $tab->push(DropdownField::create(
+ 'MoveElementIDToParent',
+ 'Move an element from the current list to parent',
+ $currEls->map('ID', 'Title')
+ )->setEmptyString('(select an element to move)'));
+ }
+
+ $parentEls = $obj->Parent()->Elements()->exclude('ID', $obj->ID);
+ if ($parentEls->count()) {
+ $tab->push(DropdownField::create(
+ 'MoveElementIDFromParent',
+ 'Move an element from the parent to the current list',
+ $parentEls->map('ID', 'Title')
+ )->setEmptyString('(select an element to move)'));
+ }
+ }
+ }
+
+ public function getWidthPercetage()
+ {
+ return $this->isColumn() ? $this->owner->getField('Size') / self::colsNumber() * 100 : false;
+ }
+
+ public function isList()
+ {
+ return is_a($this->owner, ElementList::class) && !is_a($this->owner, AccordionElement::class);
+ }
+
+ public function isRow()
+ {
+ if (!$this->isList()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function isColumn()
+ {
+ if (!$this->isRoot()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public function isRoot()
+ {
+ $parent = $this->owner->Parent()->getOwnerPage();
+ if (is_a($parent, 'Page')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public function getSibling($any = true, $filter = [], $exclude = [])
+ {
+ $class = $any ? $this->owner->baseClass() : $this->owner->ClassName;
+
+ return $class::get()->filter(array_merge(
+ ['ParentID' => $this->owner->Parent()->ID],
+ $filter
+ ))->exclude(array_merge(
+ ['ID' => $this->owner->ID],
+ $exclude
+ ))->last();
+ }
+
+ public function getColumnSizeRecursive($object = null)
+ {
+ $object = $object ? $object : $this->owner;
+
+ if ($object->isColumn() && $object->getField('Size')) {
+ return $object->getField('Size');
+ }
+
+ $parent = $object->Parent()->getOwnerPage();
+
+ if (is_a($parent, 'Page')) {
+ return ($this->owner->getField('ContainerType') === 'container-fluid') ? false : self::colsNumber();
+ }
+
+ return $object->getColumnSizeRecursive($parent);
+ }
+
+ public function getColumnWidthRecursive($object = null, $max = null)
+ {
+ $max = $max ? $max : self::maxWidth();
+
+ $object = $object ? $object : $this->owner;
+
+ if (!$object->isRoot()) {
+ $size = $object->getField('Size');
+ $cols = self::colsNumber();
+
+ if ($size === 'auto') {
+ return $size;
+ }
+
+ $max = $size ? $max / ($cols / $size) : $max;
+ $parent = $object->Parent()->getOwnerPage();
+
+ return $this->getColumnWidthRecursive($parent, $max);
+ }
+
+ return $max;
+ }
+
+ public static function colsNumber()
+ {
+ $db = Config::inst()->get(self::class, 'db');
+ $sizes = $db['Size'];
+ $sizes = preg_replace('!Enum\("([0-9A-z,]+)","([0-9A-z]+)"\)!i', '$1', $sizes);
+ $sizes = explode(',', $sizes);
+
+ // remove auto
+ $k = array_search('auto', $sizes);
+ if ($k !== false) {
+ unset($sizes[$k]);
+ }
+
+ return max($sizes);
+ }
+
+ public static function maxWidth()
+ {
+ return Config::inst()->get(self::class, 'container_max_width');
+ }
+
+ public function ExtraClass()
+ {
+ $object = $this->owner;
+
+ return $object->getField('ExtraClass')
+ .(
+ $this->isColumn()
+ ? ' '.Config::inst()->get(self::class, 'column_class')
+ .($object->getField('Size') === 'auto' ? '' : '-'.$object->getField('Size'))
+ : ''
+ );
+ }
+
+ /*
+ * if it's root element and it doesn't contain any container styles
+ * add the first one
+ */
+ public function ContainerClass()
+ {
+ $type = $this->owner->getField('ContainerType');
+ $container_styles = array_keys(self::$container_styles);
+
+ if (!$type && $this->isRoot()) {
+ $type = \Page::DefaultContainer();
+ }
+
+ return $type;
+ }
+
+ public static function MoveElement($moveID, $targetID)
+ {
+ $el = BaseElement::get_by_id($moveID);
+ if (!$el) {
+ return false;
+ }
+
+ $el->setField('ParentID', $targetID);
+ $el->write();
+
+ return true;
+ }
+
+ public function onBeforeWrite()
+ {
+ parent::onBeforeWrite();
+
+ $moveID = $this->owner->getField('MoveElementIDFromParent');
+ $targetID = $moveID ? $this->owner->Elements()->ID : null;
+
+ if ($moveID && $targetID) {
+ self::MoveElement($moveID, $targetID);
+ }
+
+ $moveID = $this->owner->getField('MoveElementIDToParent');
+ $targetID = $moveID ? $this->owner->Parent()->ID : null;
+
+ if ($moveID && $targetID) {
+ self::MoveElement($moveID, $targetID);
+ }
+ }
+}
diff --git a/src/Extensions/ElementalArea.php b/src/Extensions/ElementalArea.php
new file mode 100755
index 0000000..7f03961
--- /dev/null
+++ b/src/Extensions/ElementalArea.php
@@ -0,0 +1,49 @@
+Elements() instanceof UnsavedRelationList) {
+ return ArrayList::create();
+ }
+
+ $controllers = ArrayList::create();
+ $items = $this->Elements()->filterByCallback(static function (BaseElement $item) {
+ return $item->canView();
+ });
+
+ if ($items !== null) {
+ foreach ($items as $element) {
+ $controller = $element->getController();
+ $controllers->push($controller);
+ }
+ }
+
+ return $controllers;
+ }
+
+ /**
+ * A cache-aware accessor for the elements
+ * @return ArrayList|HasManyList|BaseElement[]
+ */
+ public function Elements()
+ {
+ return $this->owner->Elements();//->exclude('SidebarOnly', true);
+ }
+}
diff --git a/src/Extensions/MapExtension.php b/src/Extensions/MapExtension.php
new file mode 100755
index 0000000..adb2250
--- /dev/null
+++ b/src/Extensions/MapExtension.php
@@ -0,0 +1,73 @@
+ 'Int',
+ ];
+
+ private static $many_many = [
+ 'Locations' => MapPin::class,
+ ];
+
+ private static $owns = [
+ 'Locations',
+ ];
+
+ public function updateCMSFields(FieldList $fields): void
+ {
+ parent::updateCMSFields($fields);
+
+ $fields->removeByName('Locations');
+ $fields->addFieldsToTab('Root.MapPins', [
+ NumericField::create('MapZoom', 'Initial Map Zoom (enter a number from 0 to 24)'),
+ GridField::create(
+ 'Locations',
+ 'Locations',
+ $this->owner->Locations(),
+ $cfg = GridFieldConfig_RelationEditor::create(100)
+ )
+ ]);
+
+ $cfg->getComponentByType(GridFieldDataColumns::class)->setFieldFormatting([
+ 'ShowAtMap' => static function ($v, $obj) {
+ return $v ? 'YES' : 'NO';
+ }
+ ]);
+
+ $fields->findOrMakeTab('Root.MapPins')->setTitle('Locations');
+ }
+
+ public function getGeoJSON(): string
+ {
+ $locs = $this->owner->Locations()->filter('ShowAtMap', true);
+
+ $pins = [];
+ foreach ($locs as $off) {
+ $pins[] = $off->getGeo();
+ }
+
+ return json_encode([
+ 'type' => 'MarkerCollection',
+ 'features' => $pins
+ ]);
+ }
+}
diff --git a/src/Extensions/UserDefinedFormExtension.php b/src/Extensions/UserDefinedFormExtension.php
new file mode 100755
index 0000000..a323ca0
--- /dev/null
+++ b/src/Extensions/UserDefinedFormExtension.php
@@ -0,0 +1,79 @@
+ 'HTMLText',
+ 'RedirectOnComplete' => 'Boolean(0)',
+ 'RedirectOnCompleteURL' => 'Varchar(255)',
+ ];*/
+
+ private static $many_many = [
+ 'SubmissionColumns' => EditableFormField::class,
+ ];
+
+ public function updateCMSFields(FieldList $fields)
+ {
+ parent::updateCMSFields($fields);
+
+ $fields->removeByName('RedirectOnComplete');
+ $fields->removeByName('RedirectOnCompleteURL');
+
+ $fields->removeByName('SubmissionColumns');
+
+ $fields->addFieldToTab(
+ 'Root.Main',
+ ListboxField::create(
+ 'SubmissionColumns',
+ 'Display following columns at submissions list',
+ $this->owner->Fields()->map('ID', 'Title')
+ )
+ );
+
+ $tab = $fields->findOrMakeTab('Root.FormOptions');
+
+ /*$tab->push(CheckboxField::create('RedirectOnComplete'));
+ $tab->push(TextField::create('RedirectOnCompleteURL'));*/
+ //$tab->push(TextareaField::create('CustomThankYouCode'));
+
+ $grid = $fields->dataFieldByName('Submissions');
+
+ $config = $grid->getConfig();
+ $config->getComponentByType(GridFieldPaginator::class)->setItemsPerPage(100);
+
+ $cols = $this->owner->SubmissionColumns();
+ if ($grid && $cols->count()) {
+ $dataCols = $config->getComponentByType(GridFieldDataColumns::class);
+
+ $columns = [
+ 'ID' => 'ID',
+ 'Created' => 'Created',
+ ];
+
+ foreach ($cols as $col) {
+ $title = $col->getField('Title');
+ $name = $col->getField('Name');
+ $columns[$name] = [
+ 'title' => $title,
+ 'callback' => function ($item) use ($name) {
+ return $item->relField($name);
+ }
+ ];
+ }
+
+ $columns['Actions'] = 'Actions';
+
+ $dataCols->setDisplayFields($columns);
+ }
+ }
+}
diff --git a/src/Models/MapPin.php b/src/Models/MapPin.php
new file mode 100755
index 0000000..c1ca9aa
--- /dev/null
+++ b/src/Models/MapPin.php
@@ -0,0 +1,144 @@
+ 'Varchar(255)',
+ 'ShowAtMap' => 'Boolean(1)',
+ ];
+
+ private static $has_one = [
+ 'PhoneNumber' => Link::class,
+ 'Fax' => Link::class,
+ ];
+
+ private static $extensions = [
+ Addressable::class,
+ MarkerExtension::class,
+ Versioned::class,
+ ];
+
+ private static $belongs_many_many = [
+ 'MapElements' => MapElement::class,
+ ];
+
+ private static $default_sort = 'Title ASC, ID DESC';
+
+ private static $summary_fields = [
+ 'Title',
+ 'Address',
+ 'ShowAtMap',
+ ];
+
+ private static $defaults = [
+ 'ShowAtMap' => '1',
+ 'Suburb' => 'Syracuse',
+ 'State' => 'NY',
+ 'Postcode' => '13210',
+ 'Country' => 'US',
+ ];
+
+ public function getCMSFields()
+ {
+ $fields = parent::getCMSFields();
+
+ $fields->removeByName('MapElements');
+
+ $fields->replaceField(
+ 'PhoneNumberID',
+ LinkField::create('PhoneNumberID', 'Phone Number')
+ ->setAllowedTypes(['Phone'])
+ );
+
+ $fields->replaceField(
+ 'FaxID',
+ LinkField::create('FaxID', 'FAX')
+ ->setAllowedTypes(['Phone'])
+ );
+ $fields->removeByName(['Map', 'LatLngOverride', 'Lng','Lat']);
+
+ $fields->addFieldsToTab('Root.Main', [
+ CheckboxField::create('ShowAtMap', 'Show at the map?'),
+ CheckboxField::create('LatLngOverride', 'Override Latitude and Longitude?')
+ ->setDescription('Check this box and save to be able to edit the latitude and longitude manually.'),
+ MapboxField::create('Map', 'Choose a location', 'Lat', 'Lng'),
+ ]);
+
+ return $fields;
+ }
+
+ public function onBeforeWrite()
+ {
+ parent::onBeforeWrite();
+
+ $lng = $this->getField('Lng');
+ $lat = $this->getField('Lat');
+
+
+ if (!$this->getField('Country')) {
+ $this->setField('Country', 'us');
+ }
+
+ // geocode
+ try {
+ // reverse geocoding get address
+ if (!$this->hasAddress() && $lng && $lat) {
+ require_once BASE_PATH . '/app/thirdparty/geocoding-example/php/Mapbox.php';
+ $mapbox = new \Mapbox(MapboxField::getAccessToken());
+
+ // GET Address
+ $res = $mapbox->reverseGeocode($lng, $lat);
+ if ($res->success() && $res->getCount()) {
+ $res = $res->getData();
+ if (count($res) && isset($res[0]['place_name'])) {
+ $details = explode(',', $res[0]['place_name']);
+ $fields = [
+ 'Address',
+ 'City',
+ 'State',
+ //'Country',
+ ];
+
+ $n = count($fields);
+ for ($i = 0; $i < $n; $i++) {
+ if (!isset($details[$i])) {
+ continue;
+ }
+
+ $name = $fields[$i];
+ $val = $details[$i];
+
+ // get postal code
+ if ($name === 'State') {
+ $this->setField('PostalCode', substr($val, strrpos($val, ' ')+1));
+ }
+
+ $this->setField($name, $val);
+ }
+ }
+ }
+ }
+ } catch (\Exception $e) {
+ }
+ }
+}
diff --git a/src/Models/TeamMember.php b/src/Models/TeamMember.php
new file mode 100755
index 0000000..dd151f9
--- /dev/null
+++ b/src/Models/TeamMember.php
@@ -0,0 +1,63 @@
+ 'Varchar(254)',
+ 'LastName' => 'Varchar(254)',
+ 'Company' => 'Varchar(254)',
+ 'Position' => 'Varchar(254)',
+ 'Content' => 'HTMLText',
+ ];
+
+ private static $has_one = [
+ 'Photo' => Image::class,
+ ];
+
+ private static $extensions = [
+ SocialExtension::class,
+ Versioned::class,
+ ];
+
+ private static $owns = [
+ 'Photo',
+ ];
+
+ private static $summary_fields = [
+ 'Company',
+ 'FirstName',
+ 'LastName',
+ 'Position',
+ ];
+
+ private static $searchable_fields = [
+ 'FirstName',
+ 'LastName',
+ ];
+
+ private static $frontend_searchable_fields = [
+ 'FirstName:PartialMatch',
+ 'LastName:PartialMatch',
+ 'Content:PartialMatch',
+ ];
+
+ public function getTitle()
+ {
+ return $this->getField('Company').' | '.$this->getField('FirstName').' '.$this->getField('LastName');
+ }
+}