diff --git a/app/_config.php b/app/_config.php index 78d5a31..04b0f68 100644 --- a/app/_config.php +++ b/app/_config.php @@ -20,5 +20,12 @@ $config->enablePlugins([ $config->addButtonsToLine(2, 'hr'); $config->setOption('block_formats', 'Paragraph=p;Heading 2=h2;Heading 3=h3;Heading 4=h4;Heading 5=h5;Heading 6=h6;Address=address;Pre=pre'); $config->setOption('invalid_elements', 'h1'); +$config->setOption( + 'table_class_list', + [ + ['title' => 'Transparent Table', 'value' => 'table-none'], + ['title' => 'Shaded rows', 'value' => 'table table-striped table-bordered'], + ] +); FulltextSearchable::enable(); diff --git a/app/_config/config.yml b/app/_config/config.yml index 406240d..261fb4a 100644 --- a/app/_config/config.yml +++ b/app/_config/config.yml @@ -19,8 +19,8 @@ SilverStripe\View\SSViewer: SilverStripe\Admin\LeftAndMain: extra_requirements_css: - - 'app/client/dist/css/cms.css' + - 'app/client/dist/css/app_cms.css' SilverStripe\Forms\HTMLEditor\TinyMCEConfig: editor_css: - - 'app/client/dist/css/editor.css' + - 'app/client/dist/css/app_editor.css' diff --git a/app/_config/elements.yml b/app/_config/elements.yml index 99c30da..a8755e7 100644 --- a/app/_config/elements.yml +++ b/app/_config/elements.yml @@ -25,6 +25,7 @@ SilverStripe\CMS\Model\SiteTree: - Dynamic\Elements\Oembed\Elements\ElementOembed - Dynamic\Elements\Elements\ElementTestimonials - Site\Elements\TeamMembersElement + - Site\Elements\SliderElement DNADesign\ElementalList\Model\ElementList: default_global_elements: false @@ -50,4 +51,3 @@ DNADesign\Elemental\Models\ElementContent: Dynamic\Elements\Image\Elements\ElementImage: extensions: - Site\Extensions\ElementImageWidget - diff --git a/app/_config/extensions.yml b/app/_config/extensions.yml index e657287..7b0767c 100644 --- a/app/_config/extensions.yml +++ b/app/_config/extensions.yml @@ -1,6 +1,5 @@ SilverStripe\SiteConfig\SiteConfig: extensions: - - Broarm\OpeningHours\OpeningHours - Site\Extensions\SiteConfigExtension - Site\Extensions\SocialExtension @@ -19,3 +18,18 @@ Dynamic\FlexSlider\Model\SlideImage: SilverStripe\Core\Injector\Injector: SilverStripe\UserForms\Model\UserDefinedForm: class: Site\Extensions\CMSMain_HiddenClass + SilverStripe\Security\MemberAuthenticator\LostPasswordHandler: + class: Site\Extensions\LostPasswordHandlerExtension + +# User Forms +SilverStripe\UserForms\Form\UserForm: + extensions: + - Site\Extensions\PlaceholderFormExtension + +SilverStripe\UserForms\Model\UserDefinedForm: + extensions: + - Site\Extensions\UserDefinedFormExtension + +DNADesign\ElementalUserForms\Model\ElementForm: + extensions: + - Site\Extensions\UserDefinedFormExtension diff --git a/app/_config/files.yml b/app/_config/files.yml new file mode 100644 index 0000000..85b9172 --- /dev/null +++ b/app/_config/files.yml @@ -0,0 +1,79 @@ +--- +Name: webapp-files +--- + +SilverStripe\Assets\Upload_Validator: + allowedExtensions: + - 'stl' + +SilverStripe\Assets\File: + allowed_extensions: + - 'ace' + - 'arc' + - 'arj' + - 'asf' + - 'au' + - 'avi' + - 'bmp' + - 'bz2' + - 'cab' + - 'cda' + - 'csv' + - 'dmg' + - 'doc' + - 'docx' + - 'dotx' + - 'flv' + - 'gif' + - 'gpx' + - 'gz' + - 'hqx' + - 'ico' + - 'jpeg' + - 'jpg' + - 'kml' + - 'm4a' + - 'm4v' + - 'mid' + - 'midi' + - 'mkv' + - 'mov' + - 'mp3' + - 'mp4' + - 'mpa' + - 'mpeg' + - 'mpg' + - 'ogg' + - 'ogv' + - 'pages' + - 'pcx' + - 'pdf' + - 'png' + - 'pps' + - 'ppt' + - 'pptx' + - 'potx' + - 'ra' + - 'ram' + - 'rm' + - 'rtf' + - 'sit' + - 'sitx' + - 'tar' + - 'tgz' + - 'tif' + - 'tiff' + - 'txt' + - 'wav' + - 'webm' + - 'wma' + - 'wmv' + - 'xls' + - 'xlsx' + - 'xltx' + - 'zip' + - 'zipx' + - 'stl' + app_categories: + document: + - 'stl' diff --git a/app/_config/webpack.yml b/app/_config/webpack.yml index 0aac7b8..7341a0c 100644 --- a/app/_config/webpack.yml +++ b/app/_config/webpack.yml @@ -3,9 +3,11 @@ # Cuz WebPack compiling script use it to set configuration Site\Templates\WebpackTemplateProvider: - SRC: app/client/src - DIST: app/client/dist + APPDIR: app + THEMESDIR: themes HOSTNAME: localhost PORT: 3000 - TYPESJS: app/client/src/js/types - TYPESSCSS: app/client/src/scss/types + SRC: client/src + DIST: client/dist + TYPESJS: client/src/js/types + TYPESSCSS: client/src/scss/types \ No newline at end of file diff --git a/app/client/src/js/_components/_ui.carousel.js b/app/client/src/js/_components/_ui.carousel.js index 91c9e6f..623ee1d 100644 --- a/app/client/src/js/_components/_ui.carousel.js +++ b/app/client/src/js/_components/_ui.carousel.js @@ -43,8 +43,7 @@ const CarouselUI = (($) => { // create arrows if ($e.data('arrows')) { - let $prev = $('Previous'); - $e.prepend($prev); + $e.prepend('Previous'); $e.prepend('Next'); } @@ -68,8 +67,8 @@ const CarouselUI = (($) => { $(event.target).carousel('prev'); }); - $e.find('img').hammer().bind('tap', (event) => { - $e.carousel('next'); + $e.hammer().bind('tap', (event) => { + $(event.target).carousel('next'); }); }); } @@ -88,4 +87,4 @@ const CarouselUI = (($) => { return CarouselUI; })($); -export default CarouselUI; +export default CarouselUI; \ No newline at end of file diff --git a/app/client/src/js/_components/_ui.form.basics.js b/app/client/src/js/_components/_ui.form.basics.js index d5e05b1..ae2d41a 100644 --- a/app/client/src/js/_components/_ui.form.basics.js +++ b/app/client/src/js/_components/_ui.form.basics.js @@ -27,10 +27,26 @@ const FormBasics = (($) => { const $el = $(el); $el.selectpicker({ - liveSearch: $el.data('live-search') + iconBase: 'fas', + tickIcon: 'fa-check', + liveSearch: $el.data('live-search'), + noneSelectedText: $el.data('none-selected-text') || 'Nothing selected', + maxOptions: $el.data('max-options') || false, }); + + // FIX: hidden picker + $el.parents('.field.dropdown').find('.dropdown-toggle').click(); + $el.parents('.field.dropdown').find('.dropdown-toggle').click(); + $el.parents('.field.dropdown').find('.dropdown-toggle').blur(); }); + // FIX: hidden picker click + if ($selectFields.length) { + setTimeout(() => { + $(window).scrollTop(0, 0); + }, 600); + } + $fields.each((e, el) => { const $el = $(el); diff --git a/app/client/src/js/_components/_ui.form.croppie.js b/app/client/src/js/_components/_ui.form.croppie.js index 47107dd..d18b5c5 100644 --- a/app/client/src/js/_components/_ui.form.croppie.js +++ b/app/client/src/js/_components/_ui.form.croppie.js @@ -104,6 +104,10 @@ const CroppieUI = (($) => { data.append(name, blob, name + '-image.png'); data.append('ajax', '1'); + if (!$(form).data('jsFormValidate').validate()) { + return false; + } + $.ajax({ url: $(form).attr('action'), data: data, @@ -112,27 +116,29 @@ const CroppieUI = (($) => { type: $(form).attr('method'), success: function(data) { let IS_JSON = false; + let json = {}; try { IS_JSON = true; - const json = $.parseJSON(data); + json = $.parseJSON(data); } catch (e) { IS_JSON = false; } - if (IS_JSON && typeof json === 'object') { - for (let k in json) { + if (IS_JSON) { + /*for (let k in json) { $form.find('select[name="' + k + '"],input[name="' + k + '"],textarea[name="' + k + '"]').setError(true, json[k]); - } + }*/ if (typeof json['status'] !== 'undefined' && json['status'] === 'success') { if (typeof json['link'] !== 'undefined') { G.location = json['link']; } else { - G.location.reload(false); + //G.location.reload(false); } } } else { - G.location.reload(false); + $(form).replaceWith(data); + //G.location.reload(false); } SpinnerUI.hide(); diff --git a/app/client/src/js/_components/_ui.form.validate.field.js b/app/client/src/js/_components/_ui.form.validate.field.js index 05baab2..92ba711 100644 --- a/app/client/src/js/_components/_ui.form.validate.field.js +++ b/app/client/src/js/_components/_ui.form.validate.field.js @@ -65,22 +65,26 @@ const FormValidateField = (($) => { }); } + this.removeError(); if (valid) { - this.removeError(); return true; } - this.setError(scrollTo, msg); + setTimeout(() => { + this.setError(scrollTo, msg); + }, 500); + return false; } valideURL(str) { - const regex = /(http|https):\/\/(\w+:{0,1}\w*)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/; - if (!regex.test(str)) { - return false; - } else { - return true; - } + const pattern = new RegExp('^(https?:\\/\\/){1}' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator + return pattern.test(str); } setError(scrollTo = true, msg = null) { diff --git a/app/client/src/js/_components/_ui.form.validate.js b/app/client/src/js/_components/_ui.form.validate.js index 18896af..6caf750 100644 --- a/app/client/src/js/_components/_ui.form.validate.js +++ b/app/client/src/js/_components/_ui.form.validate.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import Events from "../_events"; import FormValidateField from "./_ui.form.validate.field"; +import SpinnerUI from './_ui.spinner'; const FormValidate = (($) => { // Constants @@ -67,6 +68,7 @@ const FormValidate = (($) => { validate(scrollTo = true, badCallback = false) { console.log('Checking the form ...'); const ui = this; + let valid = true; ui._fields.each(function(i, el) { const $el = $(el); @@ -77,9 +79,14 @@ const FormValidate = (($) => { badCallback(); } + console.log('Invalid form data'); + SpinnerUI.hide(); + valid = false; return false; } }); + + return valid; } static _jQueryInterface() { diff --git a/app/client/src/js/_main.js b/app/client/src/js/_main.js index e4be3d5..43a02e6 100644 --- a/app/client/src/js/_main.js +++ b/app/client/src/js/_main.js @@ -20,7 +20,7 @@ import FormDatetime from './_components/_ui.form.datetime'; import FormStepped from './_components/_ui.form.stepped'; import FormValidate from './_components/_ui.form.validate'; import FormStorage from './_components/_ui.form.storage'; -import FormCroppie from './_components/_ui.form.croppie'; +//import FormCroppie from './_components/_ui.form.croppie'; import AjaxUI from './_components/_ui.ajax'; diff --git a/app/client/src/scss/_components/_ui.carousel.scss b/app/client/src/scss/_components/_ui.carousel.scss index 3d3c1f2..4bec96c 100644 --- a/app/client/src/scss/_components/_ui.carousel.scss +++ b/app/client/src/scss/_components/_ui.carousel.scss @@ -15,7 +15,13 @@ } .carousel-indicators li { - box-shadow: 1px 1px #000; + box-shadow: none; + + // 1px 1px #000; +} + +.carousel-title { + color: #fff; } .carousel-title, diff --git a/app/client/src/scss/_components/_ui.form.basics.scss b/app/client/src/scss/_components/_ui.form.basics.scss new file mode 100644 index 0000000..5d48f38 --- /dev/null +++ b/app/client/src/scss/_components/_ui.form.basics.scss @@ -0,0 +1,39 @@ +// date-time fields +input.date, +input.time { + &[readonly] { + background-color: $white; + } +} + +.bootstrap-timepicker-widget, +.datepicker-dropdown { + border: 1px solid #ced4da; + box-shadow: 0 0 3px #999; +} + +.bootstrap-timepicker-widget { + .glyphicon { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-style: normal; + font-variant: normal; + text-rendering: auto; + line-height: 1; + font-family: Font Awesome\ 5 Free; + font-weight: 900; + } + + .glyphicon-chevron-up:before { + content: "\f077"; + } + + .glyphicon-chevron-down:before { + content: "\f078"; + } + + input { + border: 1px solid #ced4da; + } +} diff --git a/app/client/src/scss/_components/_ui.main.scss b/app/client/src/scss/_components/_ui.main.scss index 3316993..b3d58b9 100644 --- a/app/client/src/scss/_components/_ui.main.scss +++ b/app/client/src/scss/_components/_ui.main.scss @@ -60,6 +60,55 @@ button, input, optgroup, select, textarea, .field { margin: ($grid-gutter-height / 4) 0; + + &.composite { + margin-top: 0; + margin-bottom: 0; + } + + &.required { + &:after { + display: block; + position: absolute; + top: 2rem; + right: .5rem; + content: "*"; + color: $red; + z-index: 2; + } + } + + &.holder-error, + &.error { + input, select, textarea { + border-color: $red; + } + + label { + color: $red; + } + } + + .bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) { + width: 100%; + } +} + +.message { + @extend .alert; + + @extend .alert-info; + + display: block; + margin: .5rem 0; +} + +.message.validation, +.message.required, +.message.error { + @extend .alert; + + @extend .alert-danger; } // stick navbar to top using mobile layout diff --git a/app/client/src/scss/_layout.scss b/app/client/src/scss/_layout.scss index d21024c..788e79e 100644 --- a/app/client/src/scss/_layout.scss +++ b/app/client/src/scss/_layout.scss @@ -53,42 +53,12 @@ body.shrink {} @extend .alert-danger; } -// date-time fields -input.date, -input.time { - &[readonly] { - background-color: $white; - } -} - -.bootstrap-timepicker-widget, -.datepicker-dropdown { - border: 1px solid #ced4da; - box-shadow: 0 0 3px #999; -} - -.bootstrap-timepicker-widget { - .glyphicon { - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - display: inline-block; - font-style: normal; - font-variant: normal; - text-rendering: auto; - line-height: 1; - font-family: Font Awesome\ 5 Free; - font-weight: 900; - } - - .glyphicon-chevron-up:before { - content: "\f077"; - } - - .glyphicon-chevron-down:before { - content: "\f078"; - } - - input { - border: 1px solid #ced4da; +.element { + &.site__elements__sliderelement { + .element-container { + width: 100%; + padding: 0; + max-width: none; + } } } diff --git a/app/client/src/scss/app.scss b/app/client/src/scss/app.scss index 37905fd..2ab06a8 100644 --- a/app/client/src/scss/app.scss +++ b/app/client/src/scss/app.scss @@ -13,6 +13,7 @@ @import "_components/_ui.lightbox"; @import "_components/_ui.main"; +@import "_components/_ui.form.basics"; @import "_components/_ui.elemental"; // Your custom styling diff --git a/app/client/src/scss/types/editor.scss b/app/client/src/scss/types/editor.scss index 22f61ea..487dedd 100644 --- a/app/client/src/scss/types/editor.scss +++ b/app/client/src/scss/types/editor.scss @@ -45,6 +45,15 @@ table { @extend .table; @extend .table-bordered; - - @extend .table-striped; +} + +table { + &.table-none { + border: 0; + + tr, td, th { + border: 0; + background: none !important; + } + } } diff --git a/app/lang/en.yml b/app/lang/en.yml index 2eaea60..300b5d5 100644 --- a/app/lang/en.yml +++ b/app/lang/en.yml @@ -2,6 +2,10 @@ en: SilverStripe\Security\Member: SURNAME: 'Last Name' db_Surname: 'Last Name' + SUBJECTPASSWORDRESET: 'Your password reset link' + ENTEREMAIL: 'Please enter an email address to get a password reset link.' + PASSWORDRESETSENTHEADER: 'Password reset link sent' + PASSWORDRESETSENTTEXT: 'Thank you. A reset link has been sent, provided an account exists for this email address.' Page: LOADINGTEXT: "LOADING .." JAVASCRIPTREQUIRED: "Please, enable javascript!" @@ -13,3 +17,7 @@ en: db_Surname: 'Last Name' SilverShop\Page\CheckoutPage: ProceedToPayment: 'Send Order to Store' + UndefinedOffset\NoCaptcha\Forms\NocaptchaField: + EMPTY: "Please prove you are human - check the Captcha box." + NOSCRIPT: "You must enable JavaScript to submit this form" + VALIDATE_ERROR: "Captcha could not be validated" diff --git a/app/src/Shop/CheckoutMapComponent.php b/app/src/.Shop/CheckoutMapComponent.php similarity index 100% rename from app/src/Shop/CheckoutMapComponent.php rename to app/src/.Shop/CheckoutMapComponent.php diff --git a/app/src/Shop/CheckoutNoDeliveryConfig.php b/app/src/.Shop/CheckoutNoDeliveryConfig.php similarity index 100% rename from app/src/Shop/CheckoutNoDeliveryConfig.php rename to app/src/.Shop/CheckoutNoDeliveryConfig.php diff --git a/app/src/Elements/SliderElement.php b/app/src/Elements/SliderElement.php index 75e3eb1..8dafcb0 100644 --- a/app/src/Elements/SliderElement.php +++ b/app/src/Elements/SliderElement.php @@ -61,17 +61,19 @@ class SliderElement extends ElementSlideshow ]); $grid = $fields->dataFieldByName('Slides'); - $config = $grid->getConfig(); + if ($grid) { + $config = $grid->getConfig(); - $columns = new GridFieldEditableColumns(); - $columns->setDisplayFields([ - 'Hide' => [ - 'title' => 'Hide it?', - 'field' => CheckboxField::class, - ], - ]); + $columns = new GridFieldEditableColumns(); + $columns->setDisplayFields([ + 'Hide' => [ + 'title' => 'Hide it?', + 'field' => CheckboxField::class, + ], + ]); - $config->addComponent($columns); + $config->addComponent($columns); + } return $fields; } diff --git a/app/src/Extensions/LostPasswordHandlerExtension.php b/app/src/Extensions/LostPasswordHandlerExtension.php new file mode 100644 index 0000000..3c9156e --- /dev/null +++ b/app/src/Extensions/LostPasswordHandlerExtension.php @@ -0,0 +1,69 @@ + 'passwordsent', + ]; + + private static $allowed_actions = [ + 'passwordsent', + ]; + + /** + * Show the "password sent" page, after a user has requested + * to reset their password. + * + * @return array + */ + public function passwordsent() + { + $message = _t( + 'SilverStripe\\Security\\Security.PASSWORDRESETSENTTEXT', + "Thank you. A reset link has been sent, provided an account exists for this email address." + ); + + $email = $this->getRequest()->getVar('email'); + $message = $email + ? 'Thank you! A reset link has been sent to \''.$email.'\', provided an account exists for this email address.' + : $message; + + return [ + 'Title' => _t( + 'SilverStripe\\Security\\Security.PASSWORDRESETSENTHEADER', + "Password reset link sent".($email ? ' to \''.$email.'\'' : '') + ), + 'ElementalArea' => DBField::create_field('HTMLFragment', "

$message

"), + ]; + } + + /** + * Avoid information disclosure by displaying the same status, regardless wether the email address actually exists + * + * @param array $data + * @return HTTPResponse + */ + protected function redirectToSuccess(array $data) + { + $link = $this->link('passwordsent').'?email='.$data['Email']; + + return $this->redirect($this->addBackURLParam($link)); + } +} diff --git a/app/src/Extensions/PlaceholderFormExtension.php b/app/src/Extensions/PlaceholderFormExtension.php new file mode 100644 index 0000000..7168ddb --- /dev/null +++ b/app/src/Extensions/PlaceholderFormExtension.php @@ -0,0 +1,37 @@ +setPlaceholder($field); + } + } + + private function setPlaceholder($field) + { + if(is_a($field, TextField::class)) { + $field->setAttribute( + 'placeholder', + $field->Title() + .($field->hasClass('requiredField') ? '*' : '') + ); + $field->setTitle(''); + } + + if(is_a($field, CompositeField::class)) { + $children = $field->getChildren(); + foreach ($children as $child) { + $this->setPlaceholder($child); + } + } + } +} diff --git a/app/src/Extensions/SiteConfigExtension.php b/app/src/Extensions/SiteConfigExtension.php index dcd58fe..620df96 100644 --- a/app/src/Extensions/SiteConfigExtension.php +++ b/app/src/Extensions/SiteConfigExtension.php @@ -11,6 +11,7 @@ use SilverStripe\ORM\DataExtension; use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\TreeMultiselectField; +use SilverStripe\Forms\DropdownField; use BetterBrief\GoogleMapField; class SiteConfigExtension extends DataExtension diff --git a/app/src/Extensions/UserDefinedFormExtension.php b/app/src/Extensions/UserDefinedFormExtension.php new file mode 100644 index 0000000..5167979 --- /dev/null +++ b/app/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/app/src/Pages/PageController.php b/app/src/Pages/PageController.php index e3d7e52..791f1eb 100644 --- a/app/src/Pages/PageController.php +++ b/app/src/Pages/PageController.php @@ -18,6 +18,8 @@ use SilverStripe\Forms\RequiredFields; use SilverStripe\Control\HTTPRequest; use SilverStripe\ORM\ArrayList; use DNADesign\Elemental\Models\ElementContent; +use DNADesign\Elemental\Models\ElementalArea; +use DNADesign\ElementalUserForms\Control\ElementFormController; class PageController extends ContentController { @@ -38,6 +40,26 @@ class PageController extends ContentController return $this->render(); } + public function ElementalArea() + { + if($this->CurrentElement()) { + return false; + } + + return ElementalArea::get()->byID($this->getField('ElementalAreaID')); + } + + public function CurrentElement() + { + $contoller_curr = Controller::curr(); + + if(is_a($contoller_curr, ElementFormController::class)) { + return $contoller_curr; + } + + return false; + } + public function SearchForm() { return Form::create( @@ -98,7 +120,7 @@ class PageController extends ContentController $results->removeDuplicates(); return ArrayData::create([ - 'Title' => 'Search query "'.$term.'"', + 'Title' => 'You searched for: "'.$term.'"', 'Results' => PaginatedList::create($results), ]); } @@ -124,8 +146,10 @@ class PageController extends ContentController public function getSiteWideMessage() { - if (!$this->site_message) { - $session = $this->getRequest()->getSession(); + $request = $this->getRequest(); + + if ($request->isGET() && !$this->site_message) { + $session = $request->getSession(); $this->site_message = $session->get('SiteWideMessage'); $session->clear('SiteWideMessage'); } diff --git a/app/src/Templates/DeferedRequirements.php b/app/src/Templates/DeferedRequirements.php index c3b62b1..793318e 100644 --- a/app/src/Templates/DeferedRequirements.php +++ b/app/src/Templates/DeferedRequirements.php @@ -36,6 +36,7 @@ class DeferedRequirements implements TemplateGlobalProvider public static function Auto($class = false) { $config = Config::inst()->get(self::class); + $themeName = WebpackTemplateProvider::themeName(); // Initialization Requirements::block(THIRDPARTY_DIR.'/jquery/jquery.js'); @@ -50,10 +51,10 @@ class DeferedRequirements implements TemplateGlobalProvider } // App libs if (!$config['nofontawesome']) { - DeferedRequirements::loadCSS('//use.fontawesome.com/releases/v5.3.1/css/all.css'); + DeferedRequirements::loadCSS('//use.fontawesome.com/releases/v5.4.0/css/all.css'); } - DeferedRequirements::loadCSS('app.css'); - DeferedRequirements::loadJS('app.js'); + DeferedRequirements::loadCSS($themeName.'.css'); + DeferedRequirements::loadJS($themeName.'.js'); // Class libs $class = get_class(Controller::curr()); @@ -72,17 +73,17 @@ class DeferedRequirements implements TemplateGlobalProvider $dir = Path::join( Director::publicFolder(), ManifestFileFinder::RESOURCES_DIR, - 'app', + $themeName, 'client', 'dist' ); - if (file_exists(Path::join($dir, 'css', $class . '.css'))) { - DeferedRequirements::loadCSS($class . '.css'); + if (file_exists(Path::join($dir, 'css', $themeName.'_'.$class . '.css'))) { + DeferedRequirements::loadCSS($themeName.'_'.$class . '.css'); } - if (file_exists(Path::join($dir, 'js', $class . '.js'))) { - DeferedRequirements::loadJS($class . '.js'); + if (file_exists(Path::join($dir, 'js', $themeName.'_'.$class . '.js'))) { + DeferedRequirements::loadJS($themeName.'_'.$class . '.js'); } return self::forTemplate(); @@ -90,7 +91,8 @@ class DeferedRequirements implements TemplateGlobalProvider public static function loadCSS($css) { - if (self::$defered && !self::_webpackActive()) { + $external = (mb_substr($css, 0, 2) === '//' || mb_substr($css, 0, 4) === 'http'); + if ($external || (self::$defered && !self::_webpackActive())) { self::$css[] = $css; } else { WebpackTemplateProvider::loadCSS($css); @@ -99,6 +101,9 @@ class DeferedRequirements implements TemplateGlobalProvider public static function loadJS($js) { + /*$external = (mb_substr($js, 0, 2) === '//' || mb_substr($js, 0, 4) === 'http'); + if ($external || (self::$defered && !self::_webpackActive())) {*/ + // webpack supposed to load external JS if (self::$defered && !self::_webpackActive()) { self::$js[] = $js; } else { @@ -118,10 +123,6 @@ class DeferedRequirements implements TemplateGlobalProvider public static function forTemplate() { - if (!self::$defered || self::_webpackActive()) { - return false; - } - $result = ''; foreach (self::$css as $css) { $result .= ''; @@ -137,7 +138,7 @@ class DeferedRequirements implements TemplateGlobalProvider .'function lscd(a){a'; return $result; diff --git a/app/src/Templates/WebpackTemplateProvider.php b/app/src/Templates/WebpackTemplateProvider.php index f4d815e..cf2bc1c 100644 --- a/app/src/Templates/WebpackTemplateProvider.php +++ b/app/src/Templates/WebpackTemplateProvider.php @@ -2,10 +2,11 @@ /** * Directs assets requests to Webpack server or to static files -*/ + */ namespace Site\Templates; +use SilverStripe\Core\Manifest\ModuleManifest; use SilverStripe\View\TemplateGlobalProvider; use SilverStripe\View\Requirements; use SilverStripe\Control\Director; @@ -27,7 +28,7 @@ class WebpackTemplateProvider implements TemplateGlobalProvider /** * @var string assets static files directory */ - private static $dist = 'app/client/dist'; + private static $dist = 'client/dist'; /** * @return array @@ -38,7 +39,8 @@ class WebpackTemplateProvider implements TemplateGlobalProvider 'WebpackDevServer' => 'isActive', 'WebpackCSS' => 'loadCSS', 'WebpackJS' => 'loadJS', - 'ResoursesURL' => 'resoursesURL', + 'ResourcesURL' => 'resourcesURL', + 'ProjectName' => 'themeName', ]; } @@ -48,6 +50,10 @@ class WebpackTemplateProvider implements TemplateGlobalProvider */ public static function loadCSS($path) { + if (self::isActive()) { + return; + } + Requirements::css(self::_getPath($path)); } @@ -60,9 +66,14 @@ class WebpackTemplateProvider implements TemplateGlobalProvider Requirements::javascript(self::_getPath($path)); } - public static function resoursesURL($link = null) + public static function themeName() { - return Controller::join_links(Director::baseURL(), '/resources/app/client/dist/img/', $link); + return Config::inst()->get(ModuleManifest::class, 'project'); + } + + public static function resourcesURL($link = null) + { + return Controller::join_links(Director::baseURL(), '/resources/'.self::themeName().'/client/dist/img/', $link); } @@ -100,6 +111,7 @@ class WebpackTemplateProvider implements TemplateGlobalProvider { return strpos($path, '//') === false ? Controller::join_links( + self::themeName(), Config::inst()->get(__CLASS__, 'DIST'), (strpos($path, '.css') ? 'css' : 'js'), $path diff --git a/app/src/Tests/TestServer.php b/app/src/Tests/TestServer.php new file mode 100644 index 0000000..3c13577 --- /dev/null +++ b/app/src/Tests/TestServer.php @@ -0,0 +1,89 @@ +table{width:100%}table td,table th{border:1px solid #dedede}'; + + echo '

Testing Uploads

'; + $maxUpload = ini_get('upload_max_filesize'); + $maxPost = ini_get('post_max_size'); + + echo self::success('PHP max upload size: '.$maxUpload); + echo self::success('PHP max post size: '.$maxPost); + echo self::success('So calculated max upload size: '.self::formatBytes(min( + Convert::memstring2bytes($maxUpload), + Convert::memstring2bytes($maxPost) + ))); + + $defaultSizes = Config::inst()->get(Upload_Validator::class, 'default_max_file_size'); + if($defaultSizes) { + if(!is_array($defaultSizes)){ + echo self::error('default_max_file_size miss-configuration, plz fix'); + var_dump($defaultSizes); + die(); + } + + echo '

Configured limits:

' + .''; + foreach ($defaultSizes as $k => $size) { + echo ''; + } + echo '
FileSize limit
'.$k.''.$size.'
'; + } + die(); + } + + + public static function formatBytes($size, $precision = 2) + { + $base = log($size, 1024); + $suffixes = array('', 'K', 'M', 'G', 'T'); + + return round(pow(1024, $base - floor($base)), $precision) . $suffixes[(string)floor($base)]; + } + + public static function error($text) + { + return 'ERROR: '.$text.'
'; + } + + public static function success($text) + { + return 'SUCCESS: '.$text.'
'; + } + + public static function warning($text) + { + return 'WARNING: '.$text.'
'; + } + + public static function renderValidation($result) + { + echo '

'; + $msgs = $result->getMessages(); + foreach ($msgs as $msg) { + echo self::error($msg['fieldName'].': '.$msg['message']); + } + echo '

'; + } +} diff --git a/app/templates/Includes/Content.ss b/app/templates/Includes/Content.ss index 309ed0a..59b2378 100644 --- a/app/templates/Includes/Content.ss +++ b/app/templates/Includes/Content.ss @@ -2,7 +2,11 @@

$Title

- $ElementalArea + <% if $CurrentElement %> + $CurrentElement + <% else %> + $ElementalArea + <% end_if %> <% if $Form %>
diff --git a/app/templates/Includes/Footer.ss b/app/templates/Includes/Footer.ss index 84676e7..e004aa8 100644 --- a/app/templates/Includes/Footer.ss +++ b/app/templates/Includes/Footer.ss @@ -27,7 +27,10 @@
<% if $PrivacyPolicy %> - $PrivacyPolicy.Title + $PrivacyPolicy.Title + <% end_if %> + <% if $Sitemap %> + $Sitemap.Title <% end_if %>
diff --git a/app/templates/Includes/Header.ss b/app/templates/Includes/Header.ss index c9ad50c..f619fe4 100644 --- a/app/templates/Includes/Header.ss +++ b/app/templates/Includes/Header.ss @@ -1,6 +1,6 @@
- +
<% with $SiteConfig %> diff --git a/app/templates/Includes/SideBar.ss b/app/templates/Includes/SideBar.ss new file mode 100644 index 0000000..281c686 --- /dev/null +++ b/app/templates/Includes/SideBar.ss @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/app/templates/SilverStripe/UserForms/Control/UserDefinedFormController_ReceivedFormSubmission.ss b/app/templates/SilverStripe/UserForms/Control/UserDefinedFormController_ReceivedFormSubmission.ss new file mode 100644 index 0000000..02cbdb2 --- /dev/null +++ b/app/templates/SilverStripe/UserForms/Control/UserDefinedFormController_ReceivedFormSubmission.ss @@ -0,0 +1,11 @@ +
+
+ $OnCompleteMessage +
+ + <% if $CustomThankYouCode %> +
+ $CustomThankYouCode.RAW +
+ <% end_if %> +
\ No newline at end of file diff --git a/composer.json b/composer.json index 8bb9ead..3dbf60b 100755 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "description": "The SilverStripe Framework Installer", "require": { "php": ">=7.1.0", - "silverstripe/recipe-cms": "1.2.x-dev", + "silverstripe/recipe-cms": "^4", "wilr/silverstripe-googlesitemaps": "*", "silverstripe/userforms": "*", "undefinedoffset/sortablegridfield": "*", @@ -13,37 +13,34 @@ "jonom/silverstripe-betternavigator": "*", "silverstripe/externallinks": "*", "symbiote/silverstripe-gridfieldextensions": "*", - "colymba/gridfield-bulk-editing-tools": "3.0.0-beta4", + "colymba/gridfield-bulk-editing-tools": "^3", "dnadesign/silverstripe-elemental-list": "*", "dnadesign/silverstripe-elemental-virtual": "*", "dnadesign/silverstripe-elemental-userforms": "*", "dynamic/silverstripe-elemental-blocks": "*", - "drmartingonzo/ss-tinymce-charcount": "^1.0", + "drmartingonzo/ss-tinymce-charcount": "*", "axllent/silverstripe-version-truncator": "*", - "firesphere/googlemapsfield": "^1.0@dev", - "gorriecoe/silverstripe-dataobjecthistory": "^1.2", - "mak001/silverstripe-categorization": "^1.0@dev", - "axllent/silverstripe-bootstrap-forms": "^2.0", - "silverstripe/redirectedurls": "dev-master", + "firesphere/googlemapsfield": "*", + "gorriecoe/silverstripe-dataobjecthistory": "*", + "axllent/silverstripe-bootstrap-forms": "*", + "silverstripe/redirectedurls": "*", "undefinedoffset/silverstripe-nocaptcha": "*", "a2nt/silverstripe-font-awesome-field": "dev-master", - "stevie-mayhew/silverstripe-svg": "^2.1", - "betterbrief/silverstripe-googlemapfield": "^2.1", - "innoweb/silverstripe-sitemap": "^2.0", - "silverstripe/multiuser-editing-alert": "^2.0", - "gorriecoe/silverstripe-link": "^1.2", - "gorriecoe/silverstripe-linkfield": "dev-master" + "stevie-mayhew/silverstripe-svg": "*", + "betterbrief/silverstripe-googlemapfield": "*", + "innoweb/silverstripe-sitemap": "*", + "silverstripe/multiuser-editing-alert": "*", + "gorriecoe/silverstripe-link": "*", + "gorriecoe/silverstripe-linkfield": "*" }, "require-dev": { "phpunit/phpunit": "^5.7", "lekoala/silverstripe-debugbar": "dev-master" }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/a2nt/silverstripe-font-awesome" - } - ], + "repositories": [{ + "type": "vcs", + "url": "https://github.com/a2nt/silverstripe-font-awesome" + }], "extra": { "expose": [ "app/client/dist" diff --git a/package.json b/package.json index 7dc9946..2f5a0c8 100755 --- a/package.json +++ b/package.json @@ -29,6 +29,8 @@ "bootstrap-offcanvas": "^1.0.0", "bootstrap-select": "^1.13.2", "bootstrap-timepicker": "^0.5.2", + "bootstrap-confirmation2": "^4.0.4", + "bootstrap-table": "^1.14.2", "core-util-is": "^1.0.2", "font-awesome": "^4.7.0", "foundation-emails": "^2.2.1", diff --git a/themes/sample/client/src/js/_layout.js b/themes/sample/client/src/js/_layout.js new file mode 100644 index 0000000..e69de29 diff --git a/themes/sample/client/src/js/app.js b/themes/sample/client/src/js/app.js new file mode 100644 index 0000000..e1c1dbf --- /dev/null +++ b/themes/sample/client/src/js/app.js @@ -0,0 +1,4 @@ +import '../scss/app.scss'; + +import 'js/app'; +import './_layout'; diff --git a/themes/sample/client/src/scss/_layout.scss b/themes/sample/client/src/scss/_layout.scss new file mode 100644 index 0000000..e69de29 diff --git a/themes/sample/client/src/scss/app.scss b/themes/sample/client/src/scss/app.scss new file mode 100644 index 0000000..0c68847 --- /dev/null +++ b/themes/sample/client/src/scss/app.scss @@ -0,0 +1,2 @@ +@import "~scss/app.scss"; +@import "_layout"; diff --git a/themes/sample/templates/Page.ss b/themes/sample/templates/Page.ss new file mode 100644 index 0000000..fc50fcf --- /dev/null +++ b/themes/sample/templates/Page.ss @@ -0,0 +1,51 @@ + + +<%-- manifest="/cache.appcache" --%> + + <% include MetaHead %> + + + data-default-lng="$Longitude" data-default-lat="$Latitude"<% end_with %>> + <%-- Upgrade your Browser notice --%> + + + <%-- No JS enabled notice --%> + + + <%-- Loading Spinner --%> +

<%t Page.LOADINGTEXT 'LOADING ..' %>
+ + <%-- Site Wide Alert Message --%> + <% include SiteWideMessage %> + +
+ + +
+ $Layout +
+
+ +
+ <% include Footer %> +
+ +
+ $BetterNavigator +
+ + <%-- Require CSS+JS from /public/resourses/[js,css]/[ClassName].[js,css] --%> + $AutoRequirements($ClassName).RAW + + <%-- Mapbox --%> + + + + <%-- place extra requirements after this line --%> +
+ $SiteConfig.ExtraCode +
+ + diff --git a/webpack.config.common.js b/webpack.config.common.js index 8a642bd..ac74373 100755 --- a/webpack.config.common.js +++ b/webpack.config.common.js @@ -6,46 +6,79 @@ const webpack = require('webpack'); const conf = require('./webpack.configuration'); const path = require('path'); +const filesystem = require('fs'); -const includes = { - app: path.join(__dirname, conf.SRC, 'js/app.js'), +const includes = {}; + +const _addAppFiles = (theme) => { + + const dirPath = path.resolve(__dirname, theme); + const themeName = path.basename(theme); + + if (filesystem.existsSync(path.join(dirPath, conf.SRC, 'js', 'app.js'))) { + includes[`${themeName}`] = path.join(dirPath, conf.SRC, 'js', 'app.js'); + } else if (filesystem.existsSync(path.join(dirPath, conf.SRC, 'scss', 'app.scss'))) { + includes[`${themeName}`] = path.join(dirPath, conf.SRC, 'scss', 'app.scss'); + } + + const _getAllFilesFromFolder = function(dir, includeSubFolders = true) { + const dirPath = path.resolve(__dirname, dir); + let results = []; + + filesystem.readdirSync(dirPath).forEach((file) => { + if (file.charAt(0) === '_') { + return; + } + + const filePath = `${dirPath}/${file}`; + const stat = filesystem.statSync(filePath); + + if (stat && stat.isDirectory() && includeSubFolders) { + results = results.concat(_getAllFilesFromFolder(filePath, includeSubFolders)); + } else { + results.push(filePath); + } + }); + + return results; + }; + + // add page specific scripts + const typesJSPath = path.join(theme, conf.TYPESJS); + if (filesystem.existsSync(typesJSPath)) { + const pageScripts = _getAllFilesFromFolder(typesJSPath, true); + pageScripts.forEach((file) => { + includes[`${themeName}_${path.basename(file, '.js')}`] = file; + }); + } + + // add page specific scss + const typesSCSSPath = path.join(theme, conf.TYPESSCSS); + if (filesystem.existsSync(typesSCSSPath)) { + const scssIncludes = _getAllFilesFromFolder(typesSCSSPath, true); + scssIncludes.forEach((file) => { + includes[`${themeName}_${path.basename(file, '.scss')}`] = file; + }); + } }; -const _getAllFilesFromFolder = function(dir) { - dir = path.resolve(__dirname, dir); +_addAppFiles(conf.APPDIR); - const filesystem = require('fs'); - let results = []; +// add themes +if (conf.THEMESDIR) { + const dir = path.resolve(__dirname, conf.THEMESDIR); - filesystem.readdirSync(dir).forEach((file) => { - if (file === '_notes') { - return; - } + if (filesystem.existsSync(dir)) { + filesystem.readdirSync(dir).forEach((file) => { + filePath = `${dir}/${file}`; + const stat = filesystem.statSync(filePath); - file = `${dir}/${file}`; - const stat = filesystem.statSync(file); - - if (stat && stat.isDirectory()) { - results = results.concat(_getAllFilesFromFolder(file)); - } else { - results.push(file); - } - }); - - return results; -}; - -// add page specific scripts -const pageScripts = _getAllFilesFromFolder(conf.TYPESJS); -pageScripts.forEach((file) => { - includes[path.basename(file, '.js')] = file; -}); - -// add page specific scss -const scssIncludes = _getAllFilesFromFolder(conf.TYPESSCSS); -scssIncludes.forEach((file) => { - includes[path.basename(file, '.scss')] = file; -}); + if (stat && stat.isDirectory()) { + _addAppFiles(path.join(conf.THEMESDIR, file)); + } + }); + } +} module.exports = { entry: includes, @@ -79,14 +112,6 @@ module.exports = { }, { test: /\.coffee?$/, use: 'coffee-loader', - }, { - test: /\.(png|jpg|jpeg|gif|svg)$/, - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: 'img/', - publicPath: '../img/', - }, }, { test: /\.worker\.js$/, use: { @@ -97,7 +122,12 @@ module.exports = { resolve: { modules: [ path.resolve(__dirname, 'public'), - 'node_modules' + path.resolve(__dirname, conf.APPDIR, 'client', 'src'), + path.resolve(__dirname, conf.APPDIR, 'client', 'src', 'js'), + path.resolve(__dirname, conf.APPDIR, 'client', 'src', 'scss'), + path.resolve(__dirname, conf.APPDIR, 'client', 'src', 'img'), + path.resolve(__dirname, conf.APPDIR, 'client', 'src', 'thirdparty'), + path.resolve(__dirname, 'node_modules') ], alias: { 'jquery': require.resolve('jquery'), diff --git a/webpack.config.dev.js b/webpack.config.dev.js index 3ead09b..8ee472a 100755 --- a/webpack.config.dev.js +++ b/webpack.config.dev.js @@ -25,7 +25,7 @@ const config = merge.strategy({ }, output: { - path: path.join(__dirname, conf.DIST), + path: path.join(__dirname), filename: '[name].js', // necessary for HMR to know where to load the hot update chunks publicPath: 'https://' + conf.HOSTNAME + ':' + conf.PORT + '/' @@ -80,7 +80,12 @@ const config = merge.strategy({ }, { test: /\.(gif|png|jpg|jpeg|ttf|otf|eot|svg|woff(2)?)$/, use: [{ - loader: 'url-loader' + loader: 'file-loader', + options: { + name(file) { + return 'public/[path][name].[ext]'; + }, + }, }] }] }, @@ -98,7 +103,9 @@ const config = merge.strategy({ clientLogLevel: 'info', contentBase: [ path.resolve(__dirname, 'public'), - 'node_modules' + path.resolve(__dirname, 'public', 'resources'), + path.resolve(__dirname, 'public', 'resources', conf.APPDIR, conf.DIST), + 'node_modules', ], //watchContentBase: true, overlay: { @@ -106,9 +113,9 @@ const config = merge.strategy({ errors: true }, headers: { - 'Access-Control-Allow-Origin': '*' + 'Access-Control-Allow-Origin': '*', } }, }); -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/webpack.config.prod.js b/webpack.config.prod.js index d0cd4c8..893d7a3 100755 --- a/webpack.config.prod.js +++ b/webpack.config.prod.js @@ -17,9 +17,9 @@ module.exports = merge(common, { devtool: '', output: { - path: path.join(__dirname, conf.DIST), - filename: 'js/[name].js', - publicPath: conf.DIST + '/', + path: path.join(__dirname, conf.APPDIR, conf.DIST), + filename: path.join('js', '[name].js'), + publicPath: path.join(conf.APPDIR, conf.DIST), }, module: { @@ -82,7 +82,20 @@ module.exports = merge(common, { publicPath: '../fonts/' } }] - }] + }, { + test: /\.(png|jpg|jpeg|gif|svg)$/, + loader: 'file-loader', + options: { + name: '[name].[ext]', + outputPath: 'img/', + publicPath: '../img/' + /*, + name(file) { + //return 'public/[path][name].[ext]'; + return '[hash].[ext]'; + },*/ + }, + }, ] }, plugins: [ @@ -106,9 +119,9 @@ module.exports = merge(common, { }), new FaviconsWebpackPlugin({ - logo: path.join(__dirname, conf.SRC) + '/favicon.png', + logo: path.join(__dirname, conf.APPDIR, conf.SRC, 'favicon.png'), prefix: '/icons/', - statsFilename: conf.DIST + '/icons/iconstats.json', + statsFilename: path.join(conf.APPDIR, conf.DIST, 'icons', 'iconstats.json'), icons: { android: true, appleIcon: true,