diff --git a/app/_config/config.yml b/app/_config/config.yml index d82a5e1..406240d 100644 --- a/app/_config/config.yml +++ b/app/_config/config.yml @@ -17,6 +17,10 @@ SilverStripe\View\SSViewer: # extra_requirements_css: # - 'colymba/gridfield-bulk-editing-tools:client/dist/styles/main.css' +SilverStripe\Admin\LeftAndMain: + extra_requirements_css: + - 'app/client/dist/css/cms.css' + SilverStripe\Forms\HTMLEditor\TinyMCEConfig: editor_css: - 'app/client/dist/css/editor.css' diff --git a/app/_config/extensions.yml b/app/_config/extensions.yml index 3933ef9..e657287 100644 --- a/app/_config/extensions.yml +++ b/app/_config/extensions.yml @@ -1,5 +1,6 @@ SilverStripe\SiteConfig\SiteConfig: extensions: + - Broarm\OpeningHours\OpeningHours - Site\Extensions\SiteConfigExtension - Site\Extensions\SocialExtension diff --git a/app/client/src/js/_components/_ui.carousel.js b/app/client/src/js/_components/_ui.carousel.js index 623ee1d..91c9e6f 100644 --- a/app/client/src/js/_components/_ui.carousel.js +++ b/app/client/src/js/_components/_ui.carousel.js @@ -43,7 +43,8 @@ const CarouselUI = (($) => { // create arrows if ($e.data('arrows')) { - $e.prepend('<i class="carousel-control-prev" data-target="#' + id + '" role="button" data-slide="prev"><i class="fas fa-chevron-left" aria-hidden="true"></i><i class="sr-only">Previous</i></i>'); + let $prev = $('<i class="carousel-control-prev" data-target="#' + id + '" role="button" data-slide="prev"><i class="fas fa-chevron-left" aria-hidden="true"></i><i class="sr-only">Previous</i></i>'); + $e.prepend($prev); $e.prepend('<i class="carousel-control-next" data-target="#' + id + '" role="button" data-slide="next"><i class="fas fa-chevron-right" aria-hidden="true"></i><i class="sr-only">Next</i></i>'); } @@ -67,8 +68,8 @@ const CarouselUI = (($) => { $(event.target).carousel('prev'); }); - $e.hammer().bind('tap', (event) => { - $(event.target).carousel('next'); + $e.find('img').hammer().bind('tap', (event) => { + $e.carousel('next'); }); }); } @@ -87,4 +88,4 @@ const CarouselUI = (($) => { return CarouselUI; })($); -export default CarouselUI; \ No newline at end of file +export default CarouselUI; diff --git a/app/client/src/js/_components/_ui.opening-hours.js b/app/client/src/js/_components/_ui.opening-hours.js new file mode 100644 index 0000000..70fbf12 --- /dev/null +++ b/app/client/src/js/_components/_ui.opening-hours.js @@ -0,0 +1,108 @@ +import $ from 'jquery'; + +import Events from '../_events'; + +const OpeningHoursUI = (($) => { + // Constants + const NAME = 'OpeningHoursUI'; + + class OpeningHoursUI { + // Static methods + + static each(callback) { + $('.js-opening-hours').each(function(i, e) { + callback(i, $(e)); + }); + } + + static init() { + this.dispose(); + const hours = $.parseJSON($('.oppening-hours-json').html()); + const date = new Date(); + const dateYMD = this.Date_toYMD(date); + const weekday = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + ]; + const today = weekday[date.getDay()]; + let html = '<b class="opening-hours-status opening-hours-status-closed">Closed today</b>'; + + if ( + typeof hours['days'] !== 'undefined' && + typeof hours['days'][today] !== 'undefined' && + hours['days'][today].length + ) { + html = 'Open today '; + $.each(hours['days'][today], (i, v) => { + if (v['DisplayStart'] || v['DisplayEnd']) { + if ( + ( + v['DisplayStart'] && v['DisplayStart'] <= dateYMD && + v['DisplayEnd'] && v['DisplayEnd'] >= dateYMD + ) || + (v['DisplayStart'] && v['DisplayStart'] <= dateYMD && !v['DisplayEnd']) || + (v['DisplayEnd'] && v['DisplayEnd'] >= dateYMD && !v['DisplayStart']) + ) { + html = 'Open today from ' + v['From'] + ' to ' + v['Till']; + return false; + } + } else { + if (i > 0) { + html += ', <br/>'; + } + html += 'from ' + v['From'] + ' to ' + v['Till']; + } + }); + + html += ' <b class="opening-hours-status"></b>'; + } + + if ( + typeof hours['holidays'] !== 'undefined' && + typeof hours['holidays'][dateYMD] !== 'undefined' + ) { + html = '<b class="opening-hours-status opening-hours-status-closed">Closed today' + + (hours['holidays'][dateYMD] ? ' for ' + hours['holidays'][dateYMD] : '') + + '</b>'; + } + + this.each((i, e) => { + const $e = $(e); + $e.html(html); + }); + } + + static Date_toYMD(date) { + var year, month, day; + year = String(date.getFullYear()); + month = String(date.getMonth() + 1); + if (month.length == 1) { + month = '0' + month; + } + day = String(date.getDate()); + if (day.length == 1) { + day = '0' + day; + } + return year + '-' + month + '-' + day; + } + + static dispose() { + this.each((i, e) => { + $(e).html(''); + }); + } + } + + $(window).on(`${Events.AJAX} ${Events.LOADED}`, () => { + OpeningHoursUI.init(); + }); + + return OpeningHoursUI; +})($); + +export default OpeningHoursUI; diff --git a/app/client/src/scss/types/cms.scss b/app/client/src/scss/types/cms.scss new file mode 100644 index 0000000..0a749ad --- /dev/null +++ b/app/client/src/scss/types/cms.scss @@ -0,0 +1,3 @@ +#Menu-SilverStripe-CampaignAdmin-CampaignAdmin { + display: none; +} diff --git a/app/client/src/scss/types/editor.scss b/app/client/src/scss/types/editor.scss index 08303af..22f61ea 100644 --- a/app/client/src/scss/types/editor.scss +++ b/app/client/src/scss/types/editor.scss @@ -6,16 +6,18 @@ &.left { float: left; clear: left; + margin: 0 1rem 1rem 0; } &.center { display: block; - margin: 0 auto; + margin: 1rem auto; } &.right { float: right; clear: right; + margin: 1rem 0 0 1rem; } } diff --git a/app/src/Extensions/NotificationsExtension.php b/app/src/Extensions/NotificationsExtension.php new file mode 100644 index 0000000..54ead14 --- /dev/null +++ b/app/src/Extensions/NotificationsExtension.php @@ -0,0 +1,94 @@ +<?php + +namespace Site\Extensions; + +use Dynamic\Elements\Blog\Elements\ElementBlogPosts; +use Innoweb\Sitemap\Pages\SitemapPage; +use Sheadawson\Linkable\Forms\LinkField; +use Sheadawson\Linkable\Models\Link; +use SilverStripe\AssetAdmin\Forms\UploadField; +use SilverStripe\Assets\Image; +use SilverStripe\Forms\CheckboxField; +use SilverStripe\Forms\DropdownField; +use SilverStripe\Forms\GridField\GridField; +use SilverStripe\Forms\GridField\GridFieldConfig; +use SilverStripe\Forms\GridField\GridFieldDeleteAction; +use SilverStripe\Forms\GridField\GridFieldToolbarHeader; +use SilverStripe\Forms\HeaderField; +use SilverStripe\Forms\LiteralField; +use SilverStripe\Forms\TextareaField; +use SilverStripe\Forms\TextField; +use SilverStripe\ORM\DataExtension; +use SilverStripe\CMS\Model\SiteTree; +use SilverStripe\Forms\FieldList; +use SilverStripe\Forms\TreeMultiselectField; +use BetterBrief\GoogleMapField; +use Site\Models\Holiday; +use Site\Models\Notification; +use Site\Models\OpeningHour; +use Symbiote\GridFieldExtensions\GridFieldAddNewInlineButton; +use Symbiote\GridFieldExtensions\GridFieldEditableColumns; +use Symbiote\GridFieldExtensions\GridFieldTitleHeader; + +class NotificationsExtension extends DataExtension +{ + private static $db = [ + 'ShowNotifications' => 'Boolean(1)', + ]; + + private static $has_many = [ + 'Notifications' => Notification::class, + ]; + + public function updateCMSFields(FieldList $fields) + { + $tab = $fields->findOrMakeTab('Root.Notifications'); + + if(!$this->owner->exists()) { + $tab->push(LiteralField::create( + 'NotificationsNotice', + '<p class="message notice">The object must be saved before notifications can be added</p>' + )); + + return null; + } + + $items = $this->owner->Notifications(); + + $config = GridFieldConfig::create(); + $config->addComponents([ + new GridFieldToolbarHeader(), + new GridFieldTitleHeader(), + new GridFieldEditableColumns(), + new GridFieldAddNewInlineButton('toolbar-header-right'), + new GridFieldDeleteAction(), + ]); + + $tab->setChildren(FieldList::create( + HeaderField::create('NotificationsHeader','Notifications'), + LiteralField::create( + 'CurrentNotifications', + '<b>Current:</b>' + .$this->owner->renderWith('Site\\Objects\\NotificationsList') + ), + CheckboxField::create('ShowNotifications'), + GridField::create( + 'Notifications', + '', + $items, + $config + ) + )); + } + + public function NotificationsToday() + { + $items = $this->owner->Notifications(); + $time = time(); + + return $items->filter([ + 'DateOn:LessThanOrEqual' => date('Y-m-d', $time), + 'DateOff:GreaterThanOrEqual' => date('Y-m-d', $time), + ]); + } +} diff --git a/app/src/Extensions/OpenningHoursExtension.php b/app/src/Extensions/OpenningHoursExtension.php new file mode 100644 index 0000000..c5e9237 --- /dev/null +++ b/app/src/Extensions/OpenningHoursExtension.php @@ -0,0 +1,189 @@ +<?php + +namespace Site\Extensions; + +use Dynamic\Elements\Blog\Elements\ElementBlogPosts; +use Innoweb\Sitemap\Pages\SitemapPage; +use Sheadawson\Linkable\Forms\LinkField; +use Sheadawson\Linkable\Models\Link; +use SilverStripe\AssetAdmin\Forms\UploadField; +use SilverStripe\Assets\Image; +use SilverStripe\Forms\CheckboxField; +use SilverStripe\Forms\DropdownField; +use SilverStripe\Forms\GridField\GridField; +use SilverStripe\Forms\GridField\GridFieldConfig; +use SilverStripe\Forms\GridField\GridFieldDeleteAction; +use SilverStripe\Forms\GridField\GridFieldToolbarHeader; +use SilverStripe\Forms\HeaderField; +use SilverStripe\Forms\LiteralField; +use SilverStripe\Forms\TextareaField; +use SilverStripe\Forms\TextField; +use SilverStripe\ORM\DataExtension; +use SilverStripe\CMS\Model\SiteTree; +use SilverStripe\Forms\FieldList; +use SilverStripe\Forms\TreeMultiselectField; +use BetterBrief\GoogleMapField; +use Site\Models\Holiday; +use Site\Models\OpeningHour; +use Symbiote\GridFieldExtensions\GridFieldAddNewInlineButton; +use Symbiote\GridFieldExtensions\GridFieldEditableColumns; +use Symbiote\GridFieldExtensions\GridFieldTitleHeader; + +class OpenningHoursExtension extends DataExtension +{ + private static $db = [ + 'ShowOpeningHours' => 'Boolean(1)', + 'OpenningHoursNote' => 'Varchar(255)', + ]; + + private static $has_one = [ + 'OpeningHoursPage' => SiteTree::class, + ]; + + private static $has_many = [ + 'OpeningHours' => OpeningHour::class, + 'Holidays' => Holiday::class, + ]; + + public function HoursLink() + { + return $this->owner->OpeningHoursPage()->Link(); + } + + public function updateCMSFields(FieldList $fields) + { + $tab = $fields->findOrMakeTab('Root.OpeningHours'); + + if(!$this->owner->exists()) { + $tab->push(LiteralField::create( + 'OpeningHoursNotice', + '<p class="message notice">The object must be saved before opening hours can be added</p>' + )); + + return null; + } + + $hours = $this->owner->OpeningHours(); + + $config = GridFieldConfig::create(); + $config->addComponents([ + new GridFieldToolbarHeader(), + new GridFieldTitleHeader(), + new GridFieldEditableColumns(), + new GridFieldAddNewInlineButton('toolbar-header-right'), + new GridFieldDeleteAction(), + ]); + + $tab->setChildren(FieldList::create( + HeaderField::create('OpeningHours','Opening Hours'), + LiteralField::create( + 'CurrentOpeningHour', + '<b>Today:</b>' + .'<p class="message notice">' + .$this->owner->renderWith('Site\\Objects\\OpeningHoursList') + .'</p>' + ), + CheckboxField::create('ShowOpeningHours'), + DropdownField::create( + 'OpeningHoursPageID', + 'Opening Hours Page', + SiteTree::get()->map()->toArray() + ), + /*TextareaField::create('OpenningHoursNote'), + LiteralField::create( + 'OpeningHoursNote', + '<p><b>Please, specify time ranges. For example:</b><br/>' + .'Monday 10:00 AM - 2:00 PM<br/>' + .'Monday 3:00 PM - 6:00 PM<br/>' + .'Tuesday 12:00 AM - 2:00 PM<br/>' + .'Tuesday 3:00 PM - 4:00 PM<br/>' + .'...<br/>' + .'<b>Short day example durring holidays:</b><br/>' + .'Monday 12:00 AM - 2:00 PM 12/31/2018 - 01/06/2019' + .'</p>' + ),*/ + GridField::create( + 'OpeningHours', + 'Opening Hours', + $hours, + $config + ) + )); + + $tab = $fields->findOrMakeTab('Root.Holidays'); + $tab->push(GridField::create( + 'Holidays', + 'Holidays', + $this->owner->Holidays(), + $config + )); + } + + /** + * Get the opening hours + * + * @return OpeningHour|DataObject|null + */ + public function OpeningHoursToday() + { + $hours = $this->owner->OpeningHours(); + $time = time(); + + $today = $hours->filter([ + 'Day' => date('l', $time), + 'DisplayStart:LessThanOrEqual' => date('Y-m-d', $time), + 'DisplayEnd:GreaterThanOrEqual' => date('Y-m-d', $time), + ]); + + return $today->exists() ? $today : $hours->filter([ + 'Day' => date('l', $time), + 'DisplayStart' => null, + 'DisplayEnd' => null, + ]); + } + + public function OpeningHoursJSON() + { + $hours = $this->owner->OpeningHours(); + $result = []; + foreach ($hours as $hour) { + $from = str_replace(':00', '', date('g:i a', strtotime($hour->getField('From')))); + $till = str_replace(':00', '', date('g:i a', strtotime($hour->getField('Till')))); + + $result['days'][$hour->getField('Day')][] = [ + 'From' => $from, + 'Till' => $till, + 'DisplayStart' => $hour->getField('DisplayStart'), + 'DisplayEnd' => $hour->getField('DisplayEnd'), + ]; + } + + $holidays = $this->owner->Holidays(); + foreach ($holidays as $holiday) { + $result['holidays'][$holiday->getField('Date')] = $holiday->getField('Title'); + } + + return json_encode($result); + } + + public function onBeforeWrite() + { + parent::onBeforeWrite(); + if ($this->owner->exists() && !$this->owner->OpeningHours()->exists()) { + $this->createOpeningHours(); + } + } + + /** + * Set up the opening hours for each day of the week + */ + private function createOpeningHours() + { + $days = OpeningHour::singleton()->dbObject('Day')->enumValues(); + foreach ($days as $day) { + $openingHour = OpeningHour::create(); + $openingHour->Day = $day; + $this->owner->OpeningHours()->add($openingHour); + } + } +} diff --git a/app/src/Models/Holiday.php b/app/src/Models/Holiday.php new file mode 100644 index 0000000..01b1b9e --- /dev/null +++ b/app/src/Models/Holiday.php @@ -0,0 +1,57 @@ +<?php +/** + * Created by PhpStorm. + * User: tony + * Date: 9/12/18 + * Time: 2:55 AM + */ + +namespace Site\Models; + +use Dynamic\FlexSlider\Model\SlideImage; +use Sheadawson\Linkable\Forms\LinkField; +use Sheadawson\Linkable\Models\Link; +use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\ValidationResult; +use SilverStripe\SiteConfig\SiteConfig; + +class Holiday extends DataObject +{ + private static $table_name = 'Holiday'; + + private static $db = [ + 'Title' => 'Varchar(255)', + 'Date' => 'Date', + ]; + + private static $has_one = [ + 'Parent' => SiteConfig::class, + ]; + + + private static $summary_fields = [ + 'Title' => 'Title', + 'Date' => 'Date', + ]; + + private static $default_sort = 'Date ASC, Title ASC'; + + public function validate() + { + $result = parent::validate(); + + $exists = self::get()->filter([ + 'ID:not' => $this->ID, + 'Date' => $this->getField('Date'), + ])->exists(); + + if($exists) { + return $result->addError( + 'Holiday was defined already.', + ValidationResult::TYPE_ERROR + ); + } + + return $result; + } +} diff --git a/app/src/Models/Notification.php b/app/src/Models/Notification.php new file mode 100644 index 0000000..47ff7bd --- /dev/null +++ b/app/src/Models/Notification.php @@ -0,0 +1,63 @@ +<?php +/** + * Created by PhpStorm. + * User: tony + * Date: 9/12/18 + * Time: 2:55 AM + */ + +namespace Site\Models; + +use Dynamic\FlexSlider\Model\SlideImage; +use Sheadawson\Linkable\Forms\LinkField; +use Sheadawson\Linkable\Models\Link; +use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\ValidationResult; +use SilverStripe\SiteConfig\SiteConfig; + +class Notification extends DataObject +{ + private static $table_name = 'Notification'; + + private static $db = [ + 'Title' => 'Varchar(255)', + 'Content' => 'Text', + 'DateOn' => 'Date', + 'DateOff' => 'Date', + ]; + + private static $has_one = [ + 'Parent' => SiteConfig::class, + ]; + + + private static $summary_fields = [ + 'Title' => 'Title', + 'Content' => 'Text', + 'DateOn' => 'Turn on date', + 'DateOff' => 'Turn off date', + ]; + + private static $default_sort = 'DateOn DESC, DateOff DESC, Title ASC'; + + public function validate() + { + $result = parent::validate(); + + if(!$this->getField('DateOn') || !$this->getField('DateOff')) { + return $result->addError( + 'Turn on and turn off dates are required.', + ValidationResult::TYPE_ERROR + ); + } + + if(!$this->getField('Content')) { + return $result->addError( + 'Text field required.', + ValidationResult::TYPE_ERROR + ); + } + + return $result; + } +} diff --git a/app/src/Models/OpeningHour.php b/app/src/Models/OpeningHour.php new file mode 100644 index 0000000..8cb8b80 --- /dev/null +++ b/app/src/Models/OpeningHour.php @@ -0,0 +1,90 @@ +<?php +/** + * Created by PhpStorm. + * User: tony + * Date: 9/12/18 + * Time: 2:55 AM + */ + +namespace Site\Models; + +use Dynamic\FlexSlider\Model\SlideImage; +use Sheadawson\Linkable\Forms\LinkField; +use Sheadawson\Linkable\Models\Link; +use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\ValidationResult; +use SilverStripe\SiteConfig\SiteConfig; + +class OpeningHour extends DataObject +{ + private static $table_name = 'OpeningHour'; + + private static $db = [ + 'Day' => 'Enum("Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday", "Monday")', + 'From' => 'Time', + 'Till' => 'Time', + 'Comment' => 'Varchar(255)', + 'DisplayStart' => 'Date', + 'DisplayEnd' => 'Date', + ]; + + private static $has_one = [ + 'Parent' => SiteConfig::class, + ]; + + private static $defaults = [ + 'From' => '09:00:00', + 'Till' => '22:00:00', + ]; + + private static $summary_fields = [ + 'Day' => 'Day *', + 'From' => 'From *', + 'Till' => 'Till *', + //'Comment' => 'Comment', + 'DisplayStart' => 'Display Start', + 'DisplayEnd' => 'Display End', + ]; + + private static $default_sort = 'Day ASC, From ASC'; + + public function validate() + { + $result = parent::validate(); + + + if( + !$this->getField('Day') + || !$this->getField('From') + || !$this->getField('Till') + || $this->getField('From') > $this->getField('Till') + ) { + return $result->addError( + 'Day, From and Till fields are required or wrong time range was specified.', + ValidationResult::TYPE_ERROR + ); + } + + $exists = self::get()->filter([ + 'ID:not' => $this->ID, + 'ParentID' => $this->getField('ParentID'), + 'Day' => $this->getField('Day'), + 'From:GreaterThanOrEqual' => $this->getField('From'), + 'Till:LessThanOrEqual' => $this->getField('Till'), + ])->exists(); + + if($exists) { + return $result->addError( + 'Hours were defined already for specified range.', + ValidationResult::TYPE_ERROR + ); + } + + return $result; + } + + public function forTemplate() + { + return $this->renderWith(__CLASS__); + } +} diff --git a/app/templates/Includes/SiteWideMessage.ss b/app/templates/Includes/SiteWideMessage.ss index 9ddf8be..cb54ee1 100644 --- a/app/templates/Includes/SiteWideMessage.ss +++ b/app/templates/Includes/SiteWideMessage.ss @@ -1,10 +1,9 @@ <% if $SiteWideMessage %> - <% with $SiteWideMessage %> <div class="alert alert-fixed-top alert-{$Type}"> {$Message} - <button type="button" class="close" data-dismiss="alert" aria-label="Close"> - <i class="fas fa-times"></i> - </button> </div> - <% end_with %> <% end_if %> + +<% with $SiteConfig %> + <% include Site\Objects\NotificationsList %> +<% end_with %> \ No newline at end of file diff --git a/app/templates/Site/Models/OpeningHour.ss b/app/templates/Site/Models/OpeningHour.ss new file mode 100644 index 0000000..60ddc7c --- /dev/null +++ b/app/templates/Site/Models/OpeningHour.ss @@ -0,0 +1 @@ +<b>$Day:</b> $From.Nice - $Till.Nice \ No newline at end of file diff --git a/app/templates/Site/Objects/NotificationsList.ss b/app/templates/Site/Objects/NotificationsList.ss new file mode 100644 index 0000000..32654e6 --- /dev/null +++ b/app/templates/Site/Objects/NotificationsList.ss @@ -0,0 +1,10 @@ +<% if $ShowNotifications && $NotificationsToday %> + <% loop $NotificationsToday %> + <div class="alert alert-warning"> + <% if $Title %> + <b>$Title:</b> + <% end_if %> + $Content + </div> + <% end_loop %> +<% end_if %> \ No newline at end of file diff --git a/app/templates/Site/Objects/OpeningHoursList.ss b/app/templates/Site/Objects/OpeningHoursList.ss new file mode 100644 index 0000000..5634a64 --- /dev/null +++ b/app/templates/Site/Objects/OpeningHoursList.ss @@ -0,0 +1,7 @@ +<% if $OpeningHoursToday %> + <% loop $OpeningHoursToday %> + $forTemplate<br/> + <% end_loop %> +<% else %> + <b class="hours hous-closed">Closed</b> +<% end_if %> \ No newline at end of file diff --git a/composer.json b/composer.json index 26f740f..c99ec3b 100755 --- a/composer.json +++ b/composer.json @@ -31,7 +31,8 @@ "stevie-mayhew/silverstripe-svg": "^2.1", "betterbrief/silverstripe-googlemapfield": "^2.1", "innoweb/silverstripe-sitemap": "^2.0", - "silverstripe/multiuser-editing-alert": "^2.0" + "silverstripe/multiuser-editing-alert": "^2.0", + "bramdeleeuw/silverstripe-openinghours": "^1.0" }, "require-dev": { "phpunit/phpunit": "^5.7",