diff --git a/composer.json b/composer.json index a26edd05c..0f57b3539 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,10 @@ ] }, "config": { - "preferred-install": "dist" + "preferred-install": "dist", + "platform": { + "php": "7.0" + } }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/config/session.php b/config/session.php index 3ee92a981..ef0589eec 100644 --- a/config/session.php +++ b/config/session.php @@ -150,4 +150,23 @@ return [ 'secure' => false, + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | do not enable this as other CSRF protection services are in place. + | + | In the strict mode, the cookie is not sent with any cross-site usage + | even if the user follows a link to another website. Lax cookies are + | only sent with a top-level get request. + | + | Supported: "lax", "strict" + | + */ + + 'same_site' => null, + ]; diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index 195ab1856..ac0e5cd75 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -71,6 +71,7 @@ class ServiceProvider extends ModuleServiceProvider $combiner->registerBundle('~/modules/backend/widgets/mediamanager/assets/js/mediamanager-browser.js'); $combiner->registerBundle('~/modules/backend/widgets/mediamanager/assets/less/mediamanager.less'); $combiner->registerBundle('~/modules/backend/formwidgets/codeeditor/assets/less/codeeditor.less'); + $combiner->registerBundle('~/modules/backend/formwidgets/repeater/assets/less/repeater.less'); $combiner->registerBundle('~/modules/backend/formwidgets/codeeditor/assets/js/build.js'); $combiner->registerBundle('~/modules/backend/formwidgets/fileupload/assets/less/fileupload.less'); diff --git a/modules/backend/behaviors/FormController.php b/modules/backend/behaviors/FormController.php index 3a76b6e98..014a5d6bf 100644 --- a/modules/backend/behaviors/FormController.php +++ b/modules/backend/behaviors/FormController.php @@ -786,6 +786,7 @@ class FormController extends ControllerBehavior /** * Called after the form fields are defined. * @param Backend\Widgets\Form $host The hosting form widget + * @param array $fields Array of all defined form field objects (\Backend\Classes\FormField) * @return void */ public function formExtendFields($host, $fields) diff --git a/modules/backend/formwidgets/ColorPicker.php b/modules/backend/formwidgets/ColorPicker.php index 10ac5820e..6c4f24eff 100644 --- a/modules/backend/formwidgets/ColorPicker.php +++ b/modules/backend/formwidgets/ColorPicker.php @@ -1,6 +1,8 @@ vars['name'] = $this->getFieldName(); $this->vars['value'] = $value = $this->getLoadValue(); - $this->vars['availableColors'] = $this->availableColors; + $this->vars['availableColors'] = $availableColors = $this->getAvailableColors(); $this->vars['allowEmpty'] = $this->allowEmpty; $this->vars['showAlpha'] = $this->showAlpha; - $this->vars['isCustomColor'] = !in_array($value, $this->availableColors); + $this->vars['isCustomColor'] = !in_array($value, $availableColors); + } + + /** + * Gets the appropriate list of colors. + * + * @return array + */ + protected function getAvailableColors() + { + $availableColors = $this->availableColors; + if (is_array($availableColors)) { + return $availableColors; + } + elseif (is_string($availableColors) && !empty($availableColors)) { + if ($this->model->methodExists($availableColors)) { + return $this->availableColors = $this->model->{$availableColors}( + $this->formField->fieldName, + $this->formField->value, + $this->formField->config + ); + } else { + throw new ApplicationException(Lang::get('backend::lang.field.colors_method_not_exists', [ + 'model' => get_class($this->model), + 'method' => $availableColors, + 'field' => $this->formField->fieldName + ])); + } + } } /** diff --git a/modules/backend/formwidgets/FileUpload.php b/modules/backend/formwidgets/FileUpload.php index d2aead310..4d3d3fb60 100644 --- a/modules/backend/formwidgets/FileUpload.php +++ b/modules/backend/formwidgets/FileUpload.php @@ -210,8 +210,8 @@ class FileUpload extends FormWidgetBase ? 'width: '.$this->imageWidth.'px;' : 'width: '.$this->imageHeight.'px;'; - $cssDimensions .= $this->imageHeight - ? 'height: '.$this->imageHeight.'px;' + $cssDimensions .= ($this->imageHeight) + ? 'max-height: '.$this->imageHeight.'px;' : 'height: auto;'; } else { @@ -219,8 +219,8 @@ class FileUpload extends FormWidgetBase ? 'width: '.$this->imageWidth.'px;' : 'width: auto;'; - $cssDimensions .= $this->imageHeight - ? 'height: '.$this->imageHeight.'px;' + $cssDimensions .= ($this->imageHeight) + ? 'max-height: '.$this->imageHeight.'px;' : 'height: auto;'; } diff --git a/modules/backend/formwidgets/repeater/assets/css/repeater.css b/modules/backend/formwidgets/repeater/assets/css/repeater.css index c30e8e94e..4ca4c3cfe 100644 --- a/modules/backend/formwidgets/repeater/assets/css/repeater.css +++ b/modules/backend/formwidgets/repeater/assets/css/repeater.css @@ -1,208 +1,38 @@ -.field-repeater { - padding-top: 5px; -} -.field-repeater ul.field-repeater-items, -.field-repeater li.field-repeater-item { - padding: 0; - margin: 0; - list-style: none; -} -.field-repeater ul.field-repeater-items > li.dragged { - opacity: .7; - position: absolute; - padding-top: 15px; - padding-right: 15px; - z-index: 2000; - background-color: #f9f9f9; - border: 1px dashed #dbdee0; -} -.field-repeater ul.field-repeater-items > li.dragged .repeater-item-remove { - opacity: 0; -} -.field-repeater ul.field-repeater-items > li.dragged .repeater-item-collapsed-title { - top: 5px; -} -.field-repeater ul.field-repeater-items > li.placeholder { - display: block; - position: relative; - height: 25px; - margin-bottom: 5px; -} -.field-repeater ul.field-repeater-items > li.placeholder:before { - display: block; - position: absolute; - font-family: FontAwesome; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - *margin-right: .3em; - content: "\f054"; - color: #d35714; - left: -10px; - top: 8px; - z-index: 2000; -} -.field-repeater li.field-repeater-item { - position: relative; - margin-left: 5px; - padding-left: 15px; - border-left: 1px solid #dbdee0; - min-height: 30px; -} -.field-repeater li.field-repeater-item:before { - color: #bdc3c7; - font-family: FontAwesome; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - *margin-right: .3em; - content: "\f111"; - font-size: 8px; - position: absolute; - left: -4px; - top: -2px; -} -.field-repeater li.field-repeater-item.collapsed .field-repeater-form { - display: none; -} -.field-repeater li.field-repeater-item.collapsed .repeater-item-collapse .repeater-item-collapse-one { - -webkit-transform: scale(1, -1); - -ms-transform: scale(1, -1); - transform: scale(1, -1); -} -.field-repeater li.field-repeater-item.collapsed .repeater-item-collapsed-title { - display: block; -} -.field-repeater li.field-repeater-item .repeater-item-collapsed-title { - display: none; - top: -5px; - position: absolute; - font-size: 13px; -} -.field-repeater li.field-repeater-item .field-repeater-form { - position: relative; - top: -7px; -} -.field-repeater li.field-repeater-item .field-repeater-form:before, -.field-repeater li.field-repeater-item .field-repeater-form:after { - content: " "; - display: table; -} -.field-repeater li.field-repeater-item .field-repeater-form:after { - clear: both; -} -.field-repeater li.field-repeater-item .repeater-item-handle { - position: absolute; - top: -6px; - left: -10px; - color: #bdc3c7; - background: #f9f9f9; - cursor: move; - opacity: 0; - border-radius: 999px; - -webkit-transition: opacity 0.5s; - transition: opacity 0.5s; -} -.field-repeater li.field-repeater-item .repeater-item-handle:hover { - color: #999; -} -.field-repeater li.field-repeater-item .repeater-item-collapse { - position: absolute; - top: -9px; - right: 22px; - z-index: 90; -} -.field-repeater li.field-repeater-item .repeater-item-collapse a, -.field-repeater li.field-repeater-item .repeater-item-collapse button { - -webkit-transition: transform 0.3s; - transition: transform 0.3s; - color: #bdc3c7; - line-height: 20px; - display: block; - font-size: 12px; -} -.field-repeater li.field-repeater-item .repeater-item-collapse a:hover, -.field-repeater li.field-repeater-item .repeater-item-collapse button:hover, -.field-repeater li.field-repeater-item .repeater-item-collapse a:focus, -.field-repeater li.field-repeater-item .repeater-item-collapse button:focus { - color: #999; - text-decoration: none; -} -.field-repeater li.field-repeater-item .repeater-item-remove { - position: absolute; - top: -10px; - right: 0; - z-index: 90; -} -.field-repeater li.field-repeater-item .repeater-item-collapse, -.field-repeater li.field-repeater-item .repeater-item-handle, -.field-repeater li.field-repeater-item .repeater-item-remove { - width: 20px; - height: 20px; - text-align: center; -} -.field-repeater li.field-repeater-item:hover .repeater-item-collapse, -.field-repeater li.field-repeater-item:active .repeater-item-collapse, -.field-repeater li.field-repeater-item:hover .repeater-item-handle, -.field-repeater li.field-repeater-item:active .repeater-item-handle, -.field-repeater li.field-repeater-item:hover .repeater-item-remove, -.field-repeater li.field-repeater-item:active .repeater-item-remove { - opacity: 1; -} -.field-repeater .field-repeater-add-item { - position: relative; - margin-top: 10px; - margin-left: 20px; - border: 2px dotted #e0e0e0; - border-radius: 5px; -} -.field-repeater .field-repeater-add-item:before { - color: #bdc3c7; - font-family: FontAwesome; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - *margin-right: .3em; - content: "\f067"; - font-size: 16px; - position: absolute; - left: -23px; - top: -11px; -} -.field-repeater .field-repeater-add-item > a { - color: #bdc3c7; - text-align: center; - display: block; - text-decoration: none; - padding: 13px 15px; - text-transform: uppercase; - font-weight: 600; - font-size: 12px; -} -.field-repeater .field-repeater-add-item:hover, -.field-repeater .field-repeater-add-item:focus { - background-color: #4ea5e0; - border-color: transparent; -} -.field-repeater .field-repeater-add-item:hover:before, -.field-repeater .field-repeater-add-item:focus:before { - color: #999; -} -.field-repeater .field-repeater-add-item:hover > a, -.field-repeater .field-repeater-add-item:focus > a { - color: #ffffff; -} -.field-repeater .field-repeater-add-item:active { - background: #3498db; - border-color: transparent; -} -.field-repeater .field-repeater-add-item:active > a { - color: #ffffff; -} -.field-repeater .field-repeater-add-item.in-progress { - border-color: #e0e0e0 !important; - background: transparent !important; -} +.field-repeater{padding-top:5px} +.field-repeater .field-repeater-items{counter-reset:repeater-index-counter} +.field-repeater ul.field-repeater-items,.field-repeater li.field-repeater-item{padding:0;margin:0;list-style:none} +.field-repeater ul.field-repeater-items > li.dragged{opacity:.7;position:absolute;padding-top:15px;padding-right:15px;z-index:2000;background-color:#f9f9f9;border:1px dashed #dbdee0} +.field-repeater ul.field-repeater-items > li.dragged .repeater-item-remove{opacity:0} +.field-repeater ul.field-repeater-items > li.dragged .repeater-item-collapsed-title{top:5px} +.field-repeater ul.field-repeater-items > li.placeholder{display:block;position:relative;height:25px;margin-bottom:5px} +.field-repeater ul.field-repeater-items > li.placeholder:before{display:block;position:absolute;font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;content:"\f054";color:#d35714;left:-10px;top:8px;z-index:2000} +.field-repeater li.field-repeater-item{position:relative;margin:0 0 1em 1em !important;padding:1.5em 1.25em 0 1.25em !important;background:#f5f5f5;border:1px solid #d1d6d9;border-radius:3px;box-shadow:inset 0 1px 0 rgba(209,214,217,0.25),0 1px 0 rgba(255,255,255,.5);min-height:30px} +.field-repeater li.field-repeater-item:before{color:#bdc3c7;font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;content:"\f111";font-size:8px;position:absolute;left:-18px;top:-2px} +.field-repeater li.field-repeater-item:after{counter-increment:repeater-index-counter;content:counter(repeater-index-counter);display:block;position:absolute;font-size:12px;left:-27px;color:#bdc3c7;top:10px;width:24px;text-align:center} +.field-repeater li.field-repeater-item.collapsed .field-repeater-form{display:none} +.field-repeater li.field-repeater-item.collapsed .repeater-item-collapse .repeater-item-collapse-one{-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)} +.field-repeater li.field-repeater-item.collapsed .repeater-item-collapsed-title{display:block} +.field-repeater li.field-repeater-item .repeater-item-collapsed-title{display:none;position:absolute;font-size:13px;top:50%;left:10px;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)} +.field-repeater li.field-repeater-item .field-repeater-form{position:relative;top:-7px} +.field-repeater li.field-repeater-item .field-repeater-form:before,.field-repeater li.field-repeater-item .field-repeater-form:after{content:" ";display:table} +.field-repeater li.field-repeater-item .field-repeater-form:after{clear:both} +.field-repeater li.field-repeater-item .repeater-item-handle{position:absolute;top:-6px;left:-25px;color:#bdc3c7;background:#f9f9f9;cursor:move;opacity:0;border-radius:999px;-webkit-transition:opacity 0.5s;transition:opacity 0.5s} +.field-repeater li.field-repeater-item .repeater-item-handle:hover{color:#999} +.field-repeater li.field-repeater-item .repeater-item-collapse{position:absolute;top:5px;right:30px;z-index:90} +.field-repeater li.field-repeater-item .repeater-item-collapse a,.field-repeater li.field-repeater-item .repeater-item-collapse button{-webkit-transition:transform 0.3s;transition:transform 0.3s;color:#bdc3c7;line-height:20px;display:block;font-size:12px} +.field-repeater li.field-repeater-item .repeater-item-collapse a:hover,.field-repeater li.field-repeater-item .repeater-item-collapse button:hover,.field-repeater li.field-repeater-item .repeater-item-collapse a:focus,.field-repeater li.field-repeater-item .repeater-item-collapse button:focus{color:#999;text-decoration:none} +.field-repeater li.field-repeater-item .repeater-item-remove{position:absolute;top:4px;right:5px;z-index:90} +.field-repeater li.field-repeater-item .repeater-item-remove.disabled{display:none} +.field-repeater li.field-repeater-item .repeater-item-remove.disabled + .repeater-item-collapse{right:7px} +.field-repeater li.field-repeater-item .repeater-item-remove .close{float:none;display:inline-block} +.field-repeater li.field-repeater-item .repeater-item-collapse,.field-repeater li.field-repeater-item .repeater-item-handle,.field-repeater li.field-repeater-item .repeater-item-remove{width:20px;height:20px;text-align:center} +.field-repeater li.field-repeater-item:hover .repeater-item-collapse,.field-repeater li.field-repeater-item:active .repeater-item-collapse,.field-repeater li.field-repeater-item:hover .repeater-item-handle,.field-repeater li.field-repeater-item:active .repeater-item-handle,.field-repeater li.field-repeater-item:hover .repeater-item-remove,.field-repeater li.field-repeater-item:active .repeater-item-remove{opacity:1} +.field-repeater .field-repeater-add-item{position:relative;margin-top:10px;margin-left:20px;border:2px dotted #e0e0e0;border-radius:5px} +.field-repeater .field-repeater-add-item:before{color:#bdc3c7;font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;content:"\f067";font-size:16px;position:absolute;left:-23px;top:-11px} +.field-repeater .field-repeater-add-item > a{color:#bdc3c7;text-align:center;display:block;text-decoration:none;padding:13px 15px;text-transform:uppercase;font-weight:600;font-size:12px} +.field-repeater .field-repeater-add-item:hover,.field-repeater .field-repeater-add-item:focus{background-color:#4ea5e0;border-color:transparent} +.field-repeater .field-repeater-add-item:hover:before,.field-repeater .field-repeater-add-item:focus:before{color:#999} +.field-repeater .field-repeater-add-item:hover > a,.field-repeater .field-repeater-add-item:focus > a{color:#ffffff} +.field-repeater .field-repeater-add-item:active{background:#3498db;border-color:transparent} +.field-repeater .field-repeater-add-item:active > a{color:#ffffff} +.field-repeater .field-repeater-add-item.in-progress{border-color:#e0e0e0 !important;background:transparent !important} \ No newline at end of file diff --git a/modules/backend/formwidgets/repeater/assets/js/repeater.js b/modules/backend/formwidgets/repeater/assets/js/repeater.js index 042bba684..2363c8ac9 100644 --- a/modules/backend/formwidgets/repeater/assets/js/repeater.js +++ b/modules/backend/formwidgets/repeater/assets/js/repeater.js @@ -125,9 +125,9 @@ Repeater.prototype.togglePrompt = function () { if (this.options.minItems && this.options.minItems > 0) { var repeatedItems = this.$el.find('> .field-repeater-items > .field-repeater-item').length, - $removeItemBtn = this.$el.find('> .field-repeater-items > .field-repeater-item > .repeater-item-remove') + $removeItemBtn = this.$el.find('> .field-repeater-items > .field-repeater-item > .repeater-item-remove'); - $removeItemBtn.toggle(repeatedItems > this.options.minItems) + $removeItemBtn.toggleClass('disabled', !(repeatedItems > this.options.minItems)) } if (this.options.maxItems && this.options.maxItems > 0) { diff --git a/modules/backend/formwidgets/repeater/assets/less/repeater.less b/modules/backend/formwidgets/repeater/assets/less/repeater.less index 2a86dd6d6..dbacf02d1 100644 --- a/modules/backend/formwidgets/repeater/assets/less/repeater.less +++ b/modules/backend/formwidgets/repeater/assets/less/repeater.less @@ -3,6 +3,10 @@ .field-repeater { padding-top: 5px; + .field-repeater-items { + counter-reset: repeater-index-counter; + } + ul.field-repeater-items, li.field-repeater-item { padding: 0; @@ -48,9 +52,12 @@ li.field-repeater-item { position: relative; - margin-left: 5px; - padding-left: 15px; - border-left: 1px solid #dbdee0; + margin: 0 0 1em 1em !important; + padding: 1.5em 1.25em 0 1.25em !important; + background: #f5f5f5; + border: 1px solid @input-border; + border-radius: @border-radius-base; + box-shadow: @input-box-shadow; min-height: 30px; &:before { @@ -58,10 +65,23 @@ .icon(@circle); font-size: 8px; position: absolute; - left: -4px; + left: -18px; top: -2px; } + &:after { + counter-increment: repeater-index-counter; + content: counter(repeater-index-counter); + display: block; + position: absolute; + font-size: 12px; + left: -27px; + color: #bdc3c7; + top: 10px; + width: 24px; + text-align: center; + } + &.collapsed { .field-repeater-form { display:none; @@ -80,9 +100,11 @@ .repeater-item-collapsed-title { display: none; - top: -5px; position: absolute; font-size: 13px; + top: 50%; + left: 10px; + .transform(translateY(-50%)); } .field-repeater-form { @@ -94,7 +116,7 @@ .repeater-item-handle { position: absolute; top: -6px; - left: -10px; + left: -25px; color: #bdc3c7; background: @body-bg; cursor: move; @@ -108,8 +130,8 @@ .repeater-item-collapse { position: absolute; - top: -9px; - right: 22px; + top: 5px; + right: 30px; z-index: 90; a, button { @@ -129,9 +151,22 @@ .repeater-item-remove { position: absolute; - top: -10px; - right: 0; + top: 4px; + right: 5px; z-index: 90; + + &.disabled { + display: none; + + + .repeater-item-collapse { + right: 7px; + } + } + + .close { + float: none; + display: inline-block; + } } .repeater-item-collapse, diff --git a/modules/backend/lang/ca/lang.php b/modules/backend/lang/ca/lang.php new file mode 100644 index 000000000..7cfbaf269 --- /dev/null +++ b/modules/backend/lang/ca/lang.php @@ -0,0 +1,596 @@ + [ + 'title' => "Àrea d'Administració", + 'invalid_login' => "Els detalls que has introduït no corresponen als nostres registres. Si us plau revisa'ls i torna-ho a intentar." + ], + 'field' => [ + 'invalid_type' => 'El tipus de camp :type utilitzat és invàlid.', + 'options_method_invalid_model' => "L'atribut ':field' no resol a un model vàlid. Intenta especificar explícitament el mètode d'opcions per la classe de model :model.", + 'options_method_not_exists' => "La classe de model :model ha de definir un mètode :method() que retorni opcions pel camp de formulari ':field'." + ], + 'widget' => [ + 'not_registered' => "No s'ha registrat un widget amb el nom de classe ':name'", + 'not_bound' => "No s'ha vinculat al controlador un widget amb el nom de classe ':name'" + ], + 'page' => [ + 'untitled' => 'Sense títol', + 'access_denied' => [ + 'label' => 'Accés denegat', + 'help' => "No tens els permisos necessaris per veure aquesta pàgina.", + 'cms_link' => "Tornar al panell d'administració" + ], + 'no_database' => [ + 'label' => 'No es troba la base de dades', + 'help' => "Es requereix una base de dades per accedir al panell d'administració. Comprova que la base de dades estigui configurada i migrada abans de tornar-ho a intentar.", + 'cms_link' => 'Tornar a la pàgina principal' + ], + 'invalid_token' => [ + 'label' => 'Token de seguretat invàlid' + ] + ], + 'partial' => [ + 'not_found_name' => "El parcial ':name' no s'ha trobat." + ], + 'account' => [ + 'signed_in_as' => 'Connectat com :full_name', + 'sign_out' => 'Sortir', + 'login' => 'Accedir', + 'reset' => 'Reiniciar', + 'restore' => 'Restaurar', + 'login_placeholder' => 'usuari', + 'password_placeholder' => 'contrasenya', + 'remember_me' => 'Recordar-me', + 'forgot_password' => 'Has oblidat la teva contrasenya?', + 'enter_email' => 'Introdueix el teu email', + 'enter_login' => 'Introdueix el teu usuari', + 'email_placeholder' => 'email', + 'enter_new_password' => 'Introdueix una nova contrasenya', + 'password_reset' => 'Reiniciar contrasenya', + 'restore_success' => 'Hem enviat un missatge al teu correu amb instruccions.', + 'restore_error' => "No es pot trobar l'usuari ':login'", + 'reset_success' => "La contrasenya s'ha reiniciat. Ara pots accedir.", + 'reset_error' => 'Dades per restablir contrasenya incorrectes. Si us plau intenta-ho de nou!', + 'reset_fail' => "No s'ha pogut restablir la contrasenya!", + 'apply' => 'Aplicar', + 'cancel' => 'Cancel·lar', + 'delete' => 'Eliminar', + 'ok' => 'OK' + ], + 'dashboard' => [ + 'menu_label' => 'Escriptori', + 'widget_label' => 'Widget', + 'widget_width' => 'Ample', + 'full_width' => 'ample complet', + 'manage_widgets' => 'Gestionar widgets', + 'add_widget' => 'Afegir widget', + 'widget_inspector_title' => 'Configuració de widget', + 'widget_inspector_description' => 'Configurar el widget', + 'widget_columns_label' => 'Ample :columns', + 'widget_columns_description' => "L'ample del widget, un número entre 1 i 10.", + 'widget_columns_error' => "Si us plau introdueix l'ample del widget amb un número entre 1 i 10.", + 'columns' => '{1} columna|[2,Inf] columnes', + 'widget_new_row_label' => 'Forçar nova fila', + 'widget_new_row_description' => 'Posar el widget en una nova fila.', + 'widget_title_label' => 'Títol del widget', + 'widget_title_error' => 'El títol del widget és obligatori.', + 'reset_layout' => 'Reiniciar disposició', + 'reset_layout_confirm' => 'Reiniciar la disposició als valors per defecte?', + 'reset_layout_success' => "La disposició s'ha reiniciat", + 'make_default' => 'Desar per defecte', + 'make_default_confirm' => 'Convertir la disposició actual en els valors per defecte?', + 'make_default_success' => 'La disposició actual és ara la disposició per defecte.', + 'collapse_all' => 'Contraure tots', + 'expand_all' => 'Expandir tots', + 'status' => [ + 'widget_title_default' => 'Estat del sistema', + 'update_available' => '{0} actualitzacions disponibles!|{1} actualització disponible!|[2,Inf] actualitzacions disponibles!', + 'updates_pending' => 'Actualitzacions de software pendents', + 'updates_nil' => 'El software està actualitzat', + 'updates_link' => 'Actualitzar', + 'warnings_pending' => 'Alguns problemes requereixen atenció', + 'warnings_nil' => 'No hi ha avisos per mostrar', + 'warnings_link' => 'Veure', + 'core_build' => 'Versió del sistema', + 'event_log' => "Registre d'esdeveniments", + 'request_log' => 'Registre de peticions', + 'app_birthday' => 'En línia des de', + ], + 'welcome' => [ + 'widget_title_default' => 'Benvingut', + 'welcome_back_name' => 'Benvingut de nou a :app, :name.', + 'welcome_to_name' => 'Benvingut a :app, :name.', + 'first_sign_in' => 'Aquest és el primer cop que accedeixes.', + 'last_sign_in' => "L'últim cop que vas accedir va ser", + 'view_access_logs' => "Veure els registres d'accés", + 'nice_message' => 'Que tinguis un gran dia!', + ] + ], + 'user' => [ + 'name' => 'Administrador', + 'menu_label' => 'Administradors', + 'menu_description' => 'Gestionar els usuaris administradors del panell, grups i permisos.', + 'list_title' => 'Gestionar Administradors', + 'new' => 'Nou Administrador', + 'login' => 'Login', + 'first_name' => 'Nom', + 'last_name' => 'Cognom', + 'full_name' => 'Nom complet', + 'email' => 'Email', + 'role_field' => 'Rol', + 'role_comment' => "Els rols defineixen els permisos d'usuari, que poden ser invalidats a nivell d'usuari, a la pestanya de permisos.", + 'groups' => 'Grups', + 'groups_comment' => 'Especificar a quins grups pertany aquest compte.', + 'avatar' => 'Avatar', + 'password' => 'Contrasenya', + 'password_confirmation' => 'Confirmar contrasenya', + 'permissions' => 'Permisos', + 'account' => 'Compte', + 'superuser' => 'Super Usuari', + 'superuser_comment' => 'Concedeix a aquest compte accés il·limitat a totes les àrees del sistema. Els superusuaris poden afegir i gestionar altres usuaris.', + 'send_invite' => 'Enviar invitació per email', + 'send_invite_comment' => "Envia un missatge de benvinguda que conté la informació d'usuari i contrasenya.", + 'delete_confirm' => 'Eliminar aquest administrador?', + 'return' => "Tornar a la llista d'administradors", + 'allow' => 'Permetre', + 'inherit' => 'Heretar', + 'deny' => 'Denegar', + 'activated' => 'Activat', + 'last_login' => 'Últim accés', + 'created_at' => 'Creat el', + 'updated_at' => 'Actualitzat el', + 'group' => [ + 'name' => 'Grup', + 'name_field' => 'Nom', + 'name_comment' => "El nom es mostra a la llista de grups del formulari d'administrador.", + 'description_field' => 'Descripció', + 'is_new_user_default_field_label' => 'Grup per defecte', + 'is_new_user_default_field_comment' => 'Afegir nous administradors a aquest grup per defecte', + 'code_field' => 'Codi', + 'code_comment' => "Introdueix un codi únic si vols accedir a l'objecte de grup amb l'API.", + 'menu_label' => 'Gestionar grups', + 'list_title' => 'Gestionar grups', + 'new' => 'Nou grup', + 'delete_confirm' => "Eliminar aquest grup d'administració?", + 'return' => 'Tornar a la llista de grups', + 'users_count' => 'Usuaris' + ], + 'role' => [ + 'name' => 'Rol', + 'name_field' => 'Nom', + 'name_comment' => "El nom es mostra a la llista de rols del formulari d'administrador.", + 'description_field' => 'Descripció', + 'code_field' => 'Codi', + 'code_comment' => "Introdueix un codi únic si vols accedir a l'objecte de rol amb l'API.", + 'menu_label' => 'Gestionar rols', + 'list_title' => 'Gestionar rols', + 'new' => 'Nou rol', + 'delete_confirm' => "Eliminar aquest rol d'administració?", + 'return' => 'Tornar a la llista de rols', + 'users_count' => 'Usuaris' + ], + 'preferences' => [ + 'not_authenticated' => 'No hi ha un usuari autenticat per a qui carregar o guardar les preferències.' + ] + ], + 'list' => [ + 'default_title' => 'Llista', + 'search_prompt' => 'Cercar...', + 'no_records' => 'No hi ha registres en aquesta vista.', + 'missing_model' => 'El comportament List utilitzat a :class no té un model definit.', + 'missing_column' => 'No hi ha definicions de columna per :columns.', + 'missing_columns' => 'La llista utilitzada a :class no té columnes de llista definides.', + 'missing_definition' => "El comportament List no té una columna per ':field'.", + 'missing_parent_definition' => "El comportament de llista no conté una definició per ':definition'.", + 'behavior_not_ready' => "El comportament List no s'ha inicialitzat, comprova que has cridat makeLists() al controlador.", + 'invalid_column_datetime' => "El valor de columna ':column' no és un objecte DateTime, et falta una referència a \$dates al model?", + 'pagination' => 'Mostrant registres: :from-:to de :total', + 'first_page' => 'Primera pàgina', + 'last_page' => 'Última pàgina', + 'prev_page' => 'Pàgina anterior', + 'next_page' => 'Pàgina següent', + 'refresh' => 'Refrescar', + 'updating' => 'Actualitzant...', + 'loading' => 'Carregant...', + 'setup_title' => 'Configuració de llista', + 'setup_help' => 'Marca les caselles per seleccionar les columnes que vols veure a la llista. Pots canviar la posiciói de les columnes arrossegant-les amunt o avall.', + 'records_per_page' => 'Registres per pàgina', + 'records_per_page_help' => 'Selecciona el número de registres per pàgina per mostrar. Si us plau tinguis en compte que un gran número de registres en una sola pàgina pot reduir el rendiment.', + 'check' => 'Marcar', + 'delete_selected' => 'Eliminar els seleccionats', + 'delete_selected_empty' => 'No hi ha registres seleccionats per eliminar.', + 'delete_selected_confirm' => 'Eliminar els registres seleccionats?', + 'delete_selected_success' => 'Registres eliminats.', + 'column_switch_true' => 'Sí', + 'column_switch_false' => 'No' + ], + 'fileupload' => [ + 'attachment' => 'Adjunt', + 'help' => 'Afegeix un títol i descripció per aquest adjunt.', + 'title_label' => 'Títol', + 'description_label' => 'Descripció', + 'default_prompt' => 'Clica el %s o arrossega un arxiu aquí per pujar', + 'attachment_url' => "URL de l'adjunt", + 'upload_file' => 'Pujar arxiu', + 'upload_error' => 'Error de pujada', + 'remove_confirm' => 'Segur que ho vols eliminar?', + 'remove_file' => 'Elminar arxiu' + ], + 'repeater' => [ + 'min_items_failed' => ":name requereix un mínim de :min elements, només n'hi ha :items", + 'max_items_failed' => ":name només permet fins a :max elements, n'hi ha :items", + ], + 'form' => [ + 'create_title' => 'Nou :name', + 'update_title' => 'Editar :name', + 'preview_title' => 'Previsualitzar :name', + 'create_success' => ':name creat', + 'update_success' => ':name actualitzat', + 'delete_success' => ':name eliminat', + 'reset_success' => 'Reinici completat', + 'missing_id' => "La ID del registre del formulari no s'ha especificat.", + 'missing_model' => 'El comportament Form utilitzat a :class no té un model definit.', + 'missing_definition' => "El comportament Form no té un camp per ':field'.", + 'not_found' => "El registre de formulari amb la ID :id no s'ha trobat.", + 'action_confirm' => "N'estàs segur?", + 'create' => 'Crear', + 'create_and_close' => 'Crear i tancar', + 'creating' => 'Creant...', + 'creating_name' => 'Creant :name...', + 'save' => 'Guardar', + 'save_and_close' => 'Guardar i tancar', + 'saving' => 'Guardant...', + 'saving_name' => 'Guardant :name...', + 'delete' => 'Eliminar', + 'deleting' => 'Eliminant...', + 'confirm_delete' => 'Eliminar registre?', + 'confirm_delete_multiple' => 'Eliminar els registres seleccionats?', + 'deleting_name' => 'Eliminant :name...', + 'reset_default' => 'Reiniciar als valors per defecte', + 'resetting' => 'Reiniciant', + 'resetting_name' => 'Reiniciant :name', + 'undefined_tab' => 'Misc', + 'field_off' => 'Off', + 'field_on' => 'On', + 'add' => 'Afegir', + 'apply' => 'Aplicar', + 'cancel' => 'Cancel·lar', + 'close' => 'Tancar', + 'confirm' => 'Confirmar', + 'reload' => 'Recarregar', + 'complete' => 'Completat', + 'ok' => 'OK', + 'or' => 'o', + 'confirm_tab_close' => 'Tancar la pestanya? Els canvis no guardats es perdran.', + 'behavior_not_ready' => "El comportament Form no s'ha inicialitzat, comprova que has cridat initForm() al controlador.", + 'preview_no_files_message' => 'No hi ha arxius pujats.', + 'preview_no_media_message' => 'No hi ha medis seleccionats.', + 'preview_no_record_message' => 'No hi ha cap registre seleccionat.', + 'select' => 'Seleccionar', + 'select_all' => 'tots', + 'select_none' => 'cap', + 'select_placeholder' => 'si us plau selecciona', + 'insert_row' => 'Inserir fila', + 'insert_row_below' => 'Inserir fila a sota', + 'delete_row' => 'Eliminar fila', + 'concurrency_file_changed_title' => "L'arxiu ha canviat", + 'concurrency_file_changed_description' => "L'arxiu que estàs editant ha estat canviat al disc per un altre usuari. Pots o bé recarregar l'arxiu i perdre els teus canvis, o bé sobreescriure l'arxiu al disc.", + 'return_to_list' => 'Tornar a la llista' + ], + 'recordfinder' => [ + 'find_record' => 'Trobar registre', + 'cancel' => 'Cancel·lar', + ], + 'pagelist' => [ + 'page_link' => 'Enllaç a pàgina', + 'select_page' => 'Selecciona una pàgina...' + ], + 'relation' => [ + 'missing_config' => "El comportament Relation no té una configuració per ':config'.", + 'missing_definition' => "El comportament Relation no té una definició per ':field'.", + 'missing_model' => 'El comportament Relation utilitzat a :class no té un model definit.', + 'invalid_action_single' => 'Aquesta acció no pot ser realitzada en una relació singular.', + 'invalid_action_multi' => 'Aquesta acció no pot ser realitzada en una relació múltiple.', + 'help' => 'Clica en un element per afegir-lo', + 'related_data' => 'Dada :name relacionada', + 'add' => 'Afegir', + 'add_selected' => 'Afegir seleccionats', + 'add_a_new' => 'Afegir un nou :name', + 'link_selected' => 'Vincular seleccionats', + 'link_a_new' => 'Vincular un nou :name', + 'cancel' => 'Cancel·lar', + 'close' => 'Tancar', + 'add_name' => 'Afegir :name', + 'create' => 'Crear', + 'create_name' => 'Crear :name', + 'update' => 'Actualitzar', + 'update_name' => 'Actualitzar :name', + 'preview' => 'Previsualitzar', + 'preview_name' => 'Previsualitzar :name', + 'remove' => 'Treure', + 'remove_name' => 'Treure :name', + 'delete' => 'Eliminar', + 'delete_name' => 'Eliminar :name', + 'delete_confirm' => "N'estàs segur?", + 'link' => 'Vincular', + 'link_name' => 'Vincular :name', + 'unlink' => 'Desvincular', + 'unlink_name' => 'Desvincular :name', + 'unlink_confirm' => "N'estàs segur?" + ], + 'reorder' => [ + 'default_title' => 'Reordenar registres', + 'no_records' => 'No hi ha registres disponibles per reordenar.' + ], + 'model' => [ + 'name' => 'Model', + 'not_found' => "El model ':class' amb l'ID :id no s'ha trobat.", + 'missing_id' => "No s'ha especificat ID per trobar el registre de model.", + 'missing_relation' => "El model ':class' no té una definició per ':relation'.", + 'missing_method' => "El model ':class' no té un mètode ':method'.", + 'invalid_class' => "El model :model utilitzat a :class no és vàlid, ha d'heretar la classe \Model.", + 'mass_assignment_failed' => "L'assignació massiva ha fallat per l'atribut ':attribute' del model." + ], + 'warnings' => [ + 'tips' => 'Consells de configuració del sistema', + 'tips_description' => "Hi ha problemes que has d'atendre per configurar el sistema correctament.", + 'permissions' => 'El directori :name o un dels seus subdirectoris no pot ser escrit per PHP. Si us plau assigna els permisos corresponents pel servidor web en aquest directori.', + 'extension' => "L'extensió :name no està instal·lada. Si us plau instal·la aquesta llibreria i activa l'extensió.", + 'plugin_missing' => 'El plugin :name és una dependència però no està instal·lat. Si us plau instal·la aquest plugin.', + ], + 'editor' => [ + 'menu_label' => "Opcions de l'editor", + 'menu_description' => "Personalitza les preferències globals de l'editor, com ara el mida de la font i l'esquema de colors.", + 'font_size' => 'Mida de la font', + 'tab_size' => 'Mida de la tabulació', + 'use_hard_tabs' => 'Indentar utilitzant tabulacions', + 'code_folding' => 'Plegat de codi', + 'code_folding_begin' => "Marca d'inici", + 'code_folding_begin_end' => "Marca d'inici i final", + 'autocompletion' => 'Autocompletat', + 'word_wrap' => 'Ajust de línia', + 'highlight_active_line' => 'Destacar la línia activa', + 'auto_closing' => 'Tancar les etiquetes automàticament', + 'show_invisibles' => 'Mostrar els caràcters invisibles', + 'show_gutter' => 'Mostrar números de línia', + 'basic_autocompletion'=> 'Autompletat bàsic (Ctrl + Espai)', + 'live_autocompletion'=> 'Autompletat en viu', + 'enable_snippets'=> 'Activar snippets de codi (Tab)', + 'display_indent_guides'=> "Mostrar guies d'indentació", + 'show_print_margin'=> "Mostrar marge d'impressió", + 'mode_off' => 'Off', + 'mode_fluid' => 'Fluit', + '40_characters' => '40 caràcters', + '80_characters' => '80 caràcters', + 'theme' => 'Esquema de color', + 'markup_styles' => 'Estils de marcat', + 'custom_styles' => "Full d'estils personalitzat", + 'custom styles_comment' => "Estils personalitzats per incloure a l'editor HTML.", + 'markup_classes' => 'Classes de marcat', + 'paragraph' => 'Paràgraf', + 'link' => 'Enllaç', + 'table' => 'Taula', + 'table_cell' => 'Cel·la', + 'image' => 'Imatge', + 'label' => 'Etiqueta', + 'class_name' => 'Nom de classe', + 'markup_tags' => 'Etiquetes de marcat', + 'allowed_empty_tags' => 'Etiquetes buides permeses', + 'allowed_empty_tags_comment' => "La llista d'etiquetes que no s'eliminen quan no tenen contingut.", + 'allowed_tags' => 'Etiquetes permeses', + 'allowed_tags_comment' => "La llista d'etiquetes permeses.", + 'no_wrap' => 'No envoltar etiquetes', + 'no_wrap_comment' => "La llista d'etiquetes que no s'han d'envoltar dins d'etiquetes de bloc.", + 'remove_tags' => 'Eliminar etiquetes', + 'remove_tags_comment' => "La llista d'etiquetes que s'eliminen junt amb el seu contingut.", + 'line_breaker_tags' => 'Etiquetes de canvi de línia', + 'line_breaker_tags_comment' => "La llista d'etiquetes que s'utilitzen per introduir un canvi de línia.", + 'toolbar_buttons' => "Botons de la barra d'eines", + 'toolbar_buttons_comment' => "Els botons de la barra d'eines que es mostraran en l'editor de text enriquit per defecte. [fullscreen, bold, italic, underline, strikeThrough, subscript, superscript, fontFamily, fontSize, |, color, emoticons, inlineStyle, paragraphStyle, |, paragraphFormat, align, formatOL, formatUL, outdent, indent, quote, insertHR, -, insertLink, insertImage, insertVideo, insertAudio, insertFile, insertTable, undo, redo, clearFormatting, selectAll, html]", + ], + 'tooltips' => [ + 'preview_website' => 'Previsualitzar el lloc web' + ], + 'mysettings' => [ + 'menu_label' => 'Les meves opcions', + 'menu_description' => "Les opcions de configuració relacionades amb el teu compte d'administració" + ], + 'myaccount' => [ + 'menu_label' => 'El meu compte', + 'menu_description' => "Actualitza els teus detalls de compte com ara el nom, adreça d'email i contrasenya.", + 'menu_keywords' => 'seguretat acces security login' + ], + 'branding' => [ + 'menu_label' => "Personalitzar el panell d'administració", + 'menu_description' => "Personalitzar l'aspecte de l'àrea d'administració, com ara el nom, els colors o el logo.", + 'brand' => 'Marca', + 'logo' => 'Logo', + 'logo_description' => 'Pujar un logo personalitzat per utilitzar al panell.', + 'app_name' => "Nom de l'aplicació", + 'app_name_description' => "Aquest nom es mostra a l'àrea del títol del panell.", + 'app_tagline' => "Tagline de l'aplicació", + 'app_tagline_description' => "Aquest text es mostra a la finestra d'accés al panell.", + 'colors' => 'Colors', + 'primary_color' => 'Color primari', + 'secondary_color' => 'Color secundari', + 'accent_color' => "Color d'accent", + 'styles' => 'Estils', + 'custom_stylesheet' => "Full d'estils personalitzat", + 'navigation' => 'Navegació', + 'menu_mode' => 'Estil del menú', + 'menu_mode_inline' => 'En línia', + 'menu_mode_tile' => 'Rajoles', + 'menu_mode_collapsed' => 'Comprimit' + ], + 'backend_preferences' => [ + 'menu_label' => 'Preferències del panell', + 'menu_description' => 'Gestiona les preferències del teu compte, com ara el teu idioma preferit.', + 'region' => 'Regió', + 'code_editor' => 'Editor de codi', + 'timezone' => 'Zona horària', + 'timezone_comment' => 'Ajustar les dates mostrades a aquesta zona horària.', + 'locale' => 'Idioma', + 'locale_comment' => 'Selecciona el teu idioma preferit.' + ], + 'access_log' => [ + 'hint' => "Aquest registre mostra una llista dels accessos d'administradors. Els registres es guarden durant :days dies.", + 'menu_label' => "Registre d'accés", + 'menu_description' => "Veure una llista d'accessos correctes al panell.", + 'created_at' => 'Data i Hora', + 'login' => 'Login', + 'ip_address' => 'Adreça IP', + 'first_name' => 'Nom', + 'last_name' => 'Cognom', + 'email' => 'Email' + ], + 'filter' => [ + 'all' => 'tots', + 'options_method_not_exists' => "La classe de model :model ha de definir un mètode :method() que retorni opcions pel filtre ':filter'.", + 'date_all' => 'tots els períodes', + 'number_all' => 'tots els números', + ], + 'import_export' => [ + 'upload_csv_file' => '1. Pujar un arxiu CSV', + 'import_file' => 'Importar arxiu', + 'row' => 'Fila :row', + 'first_row_contains_titles' => 'La primera fila conté els títols de columnes', + 'first_row_contains_titles_desc' => "Deixa això marcat si la primera fila del CSV s'utilitza pels títols de les columnes.", + 'match_columns' => "2. Emparellar les columnes de l'arxiu amb camps de la base de dades", + 'file_columns' => "Columnes de l'arxiu", + 'database_fields' => 'Camps de la base de dades', + 'set_import_options' => "3. Establir les opcions d'importació", + 'export_output_format' => "1. Format de sortida de l'exportació", + 'file_format' => "Format de l'arxiu", + 'standard_format' => 'Estàndard', + 'custom_format' => 'Personalitzat', + 'delimiter_char' => 'Caràcter delimitador', + 'enclosure_char' => 'Caràcter de tancament de cadena', + 'escape_char' => "Caràcter d'escapament", + 'select_columns' => '2. Seleccionar columnes per exportar', + 'column' => 'Columna', + 'columns' => 'Columnes', + 'set_export_options' => "3. Establir les opcions d'exportació", + 'show_ignored_columns' => 'Mostrar columnes ignorades', + 'auto_match_columns' => 'Emparellar columnes automàticament', + 'created' => 'Creat', + 'updated' => 'Actualitzat', + 'skipped' => 'Omesos', + 'warnings' => 'Avisos', + 'errors' => 'Errors', + 'skipped_rows' => 'Files omeses', + 'import_progress' => "Progrés d'importació", + 'processing' => 'Processant', + 'import_error' => "Error en la importació", + 'upload_valid_csv' => 'Si us plau puja un arxiu CSV vàlid.', + 'drop_column_here' => 'Arrossega una columna aquí...', + 'ignore_this_column' => 'Ignora aquesta columna', + 'processing_successful_line1' => "Procès d'exportació d'arxiu completat!", + 'processing_successful_line2' => "El navegador et redigirirà aviat a la descàrrega de l'arxiu.", + 'export_progress' => "Progrès d'exportació", + 'export_error' => "Error en l'exportació", + 'column_preview' => 'Previsualitzar columna', + 'file_not_found_error' => 'Arxiu no trobat', + 'empty_error' => 'No hi ha dades per exportar', + 'empty_import_columns_error' => 'Si us plau indica alguna columna per importar.', + 'match_some_column_error' => 'Si us plau emparella algunes columnes primer.', + 'required_match_column_error' => 'Si us plau indica un emparellament pel camp requerit :label.', + 'empty_export_columns_error' => 'Si us plau indica alguna columna per exportar.', + 'behavior_missing_uselist_error' => "Has d'implementar el comportament de controlador ListController amb l'opció d'exportació useList activada.", + 'missing_model_class_error' => 'Si us plau especifica la propietat modelClass per :type', + 'missing_column_id_error' => "Falta l'identificador de columna", + 'unknown_column_error' => 'Columna desconeguda', + 'encoding_not_supported_error' => "La codificació de l'arxiu font no es reconeix. Si us plau selecciona l'opció de format d'arxiu personalitzat amb la codificació correcta per importar el teu arxiu.", + 'encoding_format' => "Codificació d'arxiu", + 'encodings' => [ + 'utf_8' => 'UTF-8', + 'us_ascii' => 'US-ASCII', + 'iso_8859_1' => 'ISO-8859-1 (Latin-1, Europa Occidental)', + 'iso_8859_2' => 'ISO-8859-2 (Latin-2, Europa Central)', + 'iso_8859_3' => 'ISO-8859-3 (Latin-3, Europa del Sud)', + 'iso_8859_4' => 'ISO-8859-4 (Latin-4, Europa del Nord)', + 'iso_8859_5' => 'ISO-8859-5 (Latin, Ciríl·lic)', + 'iso_8859_6' => 'ISO-8859-6 (Latin, Aràbic)', + 'iso_8859_7' => 'ISO-8859-7 (Latin, Grec)', + 'iso_8859_8' => 'ISO-8859-8 (Latin, Hebreu)', + 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turc)', + 'iso_8859_10' => 'ISO-8859-10 (Latin-6, Nordic)', + 'iso_8859_11' => 'ISO-8859-11 (Latin, Thai)', + 'iso_8859_13' => 'ISO-8859-13 (Latin-7, Regió Bàltica)', + 'iso_8859_14' => 'ISO-8859-14 (Latin-8, Celta)', + 'iso_8859_15' => "ISO-8859-15 (Latin-9, Europa Occidental - revisió amb signe de l'euro)", + 'windows_1251' => 'Windows-1251 (CP1251)', + 'windows_1252' => 'Windows-1252 (CP1252)' + ] + ], + 'permissions' => [ + 'manage_media' => 'Puja i gestiona continguts multimèdia - imatges, vídeos, sons, documents' + ], + 'mediafinder' => [ + 'label' => 'Cercador multimèdia', + 'default_prompt' => 'Clica el botó %s per cercar un element multimèdia' + ], + 'media' => [ + 'menu_label' => 'Mèdia', + 'upload' => 'Pujar', + 'move' => 'Moure', + 'delete' => 'Eliminar', + 'add_folder' => 'Afegir carpeta', + 'search' => 'Cercar', + 'display' => 'Mostrar', + 'filter_everything' => 'Tot', + 'filter_images' => 'Imatges', + 'filter_video' => 'Vídeo', + 'filter_audio' => 'Àudio', + 'filter_documents' => 'Documents', + 'library' => 'Biblioteca', + 'size' => 'Mida', + 'title' => 'Títol', + 'last_modified' => 'Modificat per últim cop', + 'public_url' => 'URL', + 'click_here' => 'Clica aquí', + 'thumbnail_error' => 'Error generant miniatura.', + 'return_to_parent' => 'Tornar a la carpeta pare', + 'return_to_parent_label' => 'Ves amunt ..', + 'nothing_selected' => 'No hi ha res seleccionat.', + 'multiple_selected' => 'Múltiples elements seleccionats.', + 'uploading_file_num' => 'Pujant :number arxiu(s)...', + 'uploading_complete' => 'Pujada completa', + 'uploading_error' => 'Pujada fallida', + 'type_blocked' => "Aquest tipus d'arxiu està bloquejat per raons de seguretat.", + 'order_by' => 'Ordenar per', + 'direction' => 'Direcció', + 'direction_asc' => 'Ascendent', + 'direction_desc' => 'Descendent', + 'folder' => 'Carpeta', + 'no_files_found' => "No s'han trobat arxius amb aquests paràmetres de cerca.", + 'delete_empty' => 'Si us plau selecciona elements per eliminar.', + 'delete_confirm' => 'Eliminar els elements seleccionats?', + 'error_renaming_file' => "Error reanomenant l'element.", + 'new_folder_title' => 'Nova carpeta', + 'folder_name' => 'Nom de carpeta', + 'error_creating_folder' => 'Error creant carpeta', + 'folder_or_file_exist' => 'Ja existeix una carpeta o arxiu amb aquest nom', + 'move_empty' => 'Si us plau selecciona elements per moure.', + 'move_popup_title' => 'Moure arxius o carpetes', + 'move_destination' => 'Carpeta de destí', + 'please_select_move_dest' => 'Si us plau selecciona una carpeta de destí.', + 'move_dest_src_match' => 'Si us plau selecciona una altra carpeta de destí.', + 'empty_library' => 'Això sembla una mica buit. Puja arxius o crea carpetes per començar.', + 'insert' => 'Insertar', + 'crop_and_insert' => 'Retallar i inserir', + 'select_single_image' => 'Si us plau selecciona una única imatge.', + 'selection_not_image' => "L'element seleccionat no és una imatge.", + 'restore' => 'Desfès tots els canvis', + 'resize' => 'Redimensionar...', + 'selection_mode_normal' => 'Normal', + 'selection_mode_fixed_ratio' => 'Ràtio fixa', + 'selection_mode_fixed_size' => 'Mida fixa', + 'height' => 'Alçada', + 'width' => 'Amplada', + 'selection_mode' => 'Selecciona mode', + 'resize_image' => 'Redimensionar imatge', + 'image_size' => "Mida d'imatge:", + 'selected_size' => 'Seleccionat:' + ], +]; diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php index d867786a1..0dd5ae42f 100644 --- a/modules/backend/lang/en/lang.php +++ b/modules/backend/lang/en/lang.php @@ -8,7 +8,8 @@ return [ 'field' => [ 'invalid_type' => 'Invalid field type used :type.', 'options_method_invalid_model' => "The attribute ':field' does not resolve to a valid model. Try specifying the options method for model class :model explicitly.", - 'options_method_not_exists' => "The model class :model must define a method :method() returning options for the ':field' form field." + 'options_method_not_exists' => "The model class :model must define a method :method() returning options for the ':field' form field.", + 'colors_method_not_exists' => "The model class :model must define a method :method() returning html color HEX codes for the ':field' form field." ], 'widget' => [ 'not_registered' => "A widget class name ':name' has not been registered", diff --git a/modules/backend/lang/hu/lang.php b/modules/backend/lang/hu/lang.php index 9bce5a22f..90f9716dd 100644 --- a/modules/backend/lang/hu/lang.php +++ b/modules/backend/lang/hu/lang.php @@ -2,7 +2,8 @@ return [ 'auth' => [ - 'title' => 'Admin felület' + 'title' => 'Admin felület', + 'invalid_login' => 'A megadott adatok nem egyeznek. Kérjük ellenőrizze őket és próbálja újra.' ], 'field' => [ 'invalid_type' => 'A(z) :type mezőtípus érvénytelen.', @@ -26,7 +27,7 @@ return [ 'cms_link' => 'Vissza a weboldalra' ], 'invalid_token' => [ - 'label' => 'Érvénytelen a biztonsági kód.' + 'label' => 'A biztonsági kód érvényessége lejárt. Kérjük töltse be újra az oldalt.' ] ], 'partial' => [ @@ -216,6 +217,10 @@ return [ 'remove_confirm' => 'Biztos benne?', 'remove_file' => 'Fájl eltávolítása' ], + 'repeater' => [ + 'min_items_failed' => 'A(z) :name mező legalább :max elemből állhat. Jelenleg csak :items mező van megadva.', + 'max_items_failed' => 'A(z) :name mező legfeljebb :max elemből állhat. Jelenleg :items mező van megadva.', + ], 'form' => [ 'create_title' => 'Új :name', 'update_title' => ':name szerkesztése', @@ -270,7 +275,7 @@ return [ 'insert_row_below' => 'Sor beszúrása alá', 'delete_row' => 'Sor törlése', 'concurrency_file_changed_title' => 'A fájl megváltozott', - 'concurrency_file_changed_description' => 'Az Ön által szerkesztett fájlt már egy máik felhasználó módosította. Újratöltheti a fájlt és elveszti a változtatásait, vagy felülírja a fájlt.', + 'concurrency_file_changed_description' => 'Az Ön által szerkesztett fájlt már egy másik felhasználó módosította. Újratöltheti a fájlt és elveszti a változtatásait, vagy felülírja a fájlt.', 'return_to_list' => 'Vissza a listához' ], 'recordfinder' => [ @@ -332,7 +337,7 @@ return [ 'tips_description' => 'Olyan problémák vannak, melyekre figyeljen oda a rendszer megfelelő működése érdekében.', 'permissions' => 'A(z) :name könyvtár vagy alkönyvtárai a PHP számára nem írhatóak. Adjon megfelelő engedélyeket a kiszolgálónak erre a könyvtárra.', 'extension' => 'A(z) :name PHP kiterjesztés nincs telepítve. Telepítse ezt a függvénytárat és aktiválja a kiterjesztést.', - 'plugin_missing' => 'A(z) :name bővítményre szükség van, de nincs telepítve. Kérjük, hogy telepítse ezt a bővítményt.' + 'plugin_missing' => 'A(z) :name bővítményre szükség van, de nincs telepítve. Kérjük telepítse ezt a bővítményt.' ], 'editor' => [ 'menu_label' => 'Szövegszerkesztő', @@ -379,6 +384,8 @@ return [ 'no_wrap_comment' => 'Azon HTML elemek, amik tartalma nem tördelhető.', 'remove_tags' => 'Eltávolítható elemek', 'remove_tags_comment' => 'Azon HTML elemek, amik a tartalmukkal együtt törölhetőek.', + 'line_breaker_tags' => 'Sortörő elemek', + 'line_breaker_tags_comment' => 'Azon HTML elemek, amik végén kötelezően egy új sor jelenik meg.', 'toolbar_buttons' => 'Eszköztár', 'toolbar_buttons_comment' => 'Használható értékek: [fullscreen, bold, italic, underline, strikeThrough, subscript, superscript, fontFamily, fontSize, |, color, emoticons, inlineStyle, paragraphStyle, |, paragraphFormat, align, formatOL, formatUL, outdent, indent, quote, insertHR, -, insertLink, insertImage, insertVideo, insertAudio, insertFile, insertTable, undo, redo, clearFormatting, selectAll, html]', ], @@ -446,6 +453,7 @@ return [ 'import_export' => [ 'upload_csv_file' => '1. CSV fájl', 'import_file' => 'Fájl feltöltése', + 'row' => ':row sor', 'first_row_contains_titles' => 'Az első sor tartalmazza az oszlop neveit', 'first_row_contains_titles_desc' => 'Hagyja bejelölve, amennyiben a CSV fájl első sora az oszlop neveket tartalmazza.', 'match_columns' => '2. Oszlopok párosítása', diff --git a/modules/backend/lang/nl/lang.php b/modules/backend/lang/nl/lang.php index df4102a8e..f796692ec 100644 --- a/modules/backend/lang/nl/lang.php +++ b/modules/backend/lang/nl/lang.php @@ -8,6 +8,7 @@ return [ 'invalid_type' => 'Ongeldig type veld: :type.', 'options_method_invalid_model' => "Het attribuut ':field' levert geen geldig model op. Probeer de opties methode expliciet te specifieren voor modelklasse :model.", 'options_method_not_exists' => 'De modelklasse :model moet de methode :method() definiëren met daarin opties voor het veld ":field".', + 'colors_method_not_exists' => 'De modelklasse :model moet de methode :method() definiëren met daarin html HEX kleurcodes voor het veld ":field".', ], 'widget' => [ 'not_registered' => "Een widget met klassenaam ':name' is niet geregistreerd", diff --git a/modules/backend/models/Preference.php b/modules/backend/models/Preference.php index 0715b8017..43bbfc3d7 100644 --- a/modules/backend/models/Preference.php +++ b/modules/backend/models/Preference.php @@ -180,6 +180,7 @@ class Preference extends Model $localeOptions = [ 'ar' => [Lang::get('system::lang.locale.ar'), 'flag-sa'], 'be' => [Lang::get('system::lang.locale.be'), 'flag-by'], + 'ca' => [Lang::get('system::lang.locale.ca'), 'flag-es'], 'cs' => [Lang::get('system::lang.locale.cs'), 'flag-cz'], 'da' => [Lang::get('system::lang.locale.da'), 'flag-dk'], 'en' => [Lang::get('system::lang.locale.en'), 'flag-us'], diff --git a/modules/backend/models/editorsetting/fields.yaml b/modules/backend/models/editorsetting/fields.yaml index 437ded931..7fa5dba2f 100644 --- a/modules/backend/models/editorsetting/fields.yaml +++ b/modules/backend/models/editorsetting/fields.yaml @@ -68,18 +68,18 @@ tabs: class_name: title: backend::lang.editor.class_name - html_allow_empty_tags: - label: backend::lang.editor.allowed_empty_tags - comment: backend::lang.editor.allowed_empty_tags_comment - tab: backend::lang.editor.markup_tags - type: textarea - span: auto - html_allow_tags: label: backend::lang.editor.allowed_tags comment: backend::lang.editor.allowed_tags_comment tab: backend::lang.editor.markup_tags type: textarea + + html_allow_empty_tags: + label: backend::lang.editor.allowed_empty_tags + comment: backend::lang.editor.allowed_empty_tags_comment + tab: backend::lang.editor.markup_tags + type: textarea + size: small span: auto html_no_wrap_tags: @@ -87,6 +87,7 @@ tabs: comment: backend::lang.editor.no_wrap_comment tab: backend::lang.editor.markup_tags type: textarea + size: small span: auto html_remove_tags: @@ -94,6 +95,7 @@ tabs: comment: backend::lang.editor.remove_tags_comment tab: backend::lang.editor.markup_tags type: textarea + size: small span: auto html_line_breaker_tags: @@ -101,6 +103,7 @@ tabs: comment: backend::lang.editor.line_breaker_tags_comment tab: backend::lang.editor.markup_tags type: textarea + size: small span: auto html_toolbar_buttons: diff --git a/modules/backend/widgets/Filter.php b/modules/backend/widgets/Filter.php index 322f26000..4f71f2ff5 100644 --- a/modules/backend/widgets/Filter.php +++ b/modules/backend/widgets/Filter.php @@ -379,8 +379,26 @@ class Filter extends WidgetBase */ $query->limit(500); - /* - * Extensibility + /** + * @event backend.filter.extendQuery + * Provides an opportunity to extend the query of the list of options + * + * Example usage: + * + * Event::listen('backend.filter.extendQuery', function((\Backend\Widgets\Filter) $filterWidget, $query, (\Backend\Classes\FilterScope) $scope) { + * if ($scope->scopeName == 'status') { + * $query->where('status', '<>', 'all'); + * } + * }); + * + * Or + * + * $listWidget->bindEvent('filter.extendQuery', function ($query, (\Backend\Classes\FilterScope) $scope) { + * if ($scope->scopeName == 'status') { + * $query->where('status', '<>', 'all'); + * } + * }); + * */ $this->fireSystemEvent('backend.filter.extendQuery', [$query, $scope]); @@ -489,8 +507,22 @@ class Filter extends WidgetBase return; } - /* - * Extensibility + /** + * @event backend.filter.extendScopesBefore + * Provides an opportunity to interact with the Filter widget before defining the filter scopes + * + * Example usage: + * + * Event::listen('backend.filter.extendScopesBefore', function((\Backend\Widgets\Filter) $filterWidget) { + * // Just in case you really had to do something before scopes are defined + * }); + * + * Or + * + * $listWidget->bindEvent('filter.extendScopesBefore', function () use ((\Backend\Widgets\Filter) $filterWidget) { + * // Just in case you really had to do something before scopes are defined + * }); + * */ $this->fireSystemEvent('backend.filter.extendScopesBefore'); @@ -503,8 +535,26 @@ class Filter extends WidgetBase $this->addScopes($this->scopes); - /* - * Extensibility + /** + * @event backend.filter.extendScopes + * Provides an opportunity to interact with the Filter widget & its scopes after the filter scopes have been initialized + * + * Example usage: + * + * Event::listen('backend.filter.extendScopes', function((\Backend\Widgets\Filter) $filterWidget) { + * $filterWidget->addScopes([ + * 'my_scope' => [ + * 'label' => 'My Filter Scope' + * ] + * ]); + * }); + * + * Or + * + * $listWidget->bindEvent('filter.extendScopes', function () use ((\Backend\Widgets\Filter) $filterWidget) { + * $filterWidget->removeScope('my_scope'); + * }); + * */ $this->fireSystemEvent('backend.filter.extendScopes'); @@ -594,7 +644,7 @@ class Filter extends WidgetBase * Set scope value */ $scope->value = $this->getScopeValue($scope, @$config['default']); - + return $scope; } diff --git a/modules/backend/widgets/Form.php b/modules/backend/widgets/Form.php index 9762ff568..807c54185 100644 --- a/modules/backend/widgets/Form.php +++ b/modules/backend/widgets/Form.php @@ -354,8 +354,22 @@ class Form extends WidgetBase $result = []; $saveData = $this->getSaveData(); - /* - * Extensibility + /** + * @event backend.form.beforeRefresh + * Called before the form is refreshed, modify the $dataHolder->data property in place + * + * Example usage: + * + * Event::listen('backend.form.beforeRefresh', function((\Backend\Widgets\Form) $formWidget, (stdClass) $dataHolder) { + * $dataHolder->data = $arrayOfSaveDataToReplaceExistingDataWith; + * }); + * + * Or + * + * $formWidget->bindEvent('form.beforeRefresh', function ((stdClass) $dataHolder) { + * $dataHolder->data = $arrayOfSaveDataToReplaceExistingDataWith; + * }); + * */ $dataHolder = (object) ['data' => $saveData]; $this->fireSystemEvent('backend.form.beforeRefresh', [$dataHolder]); @@ -367,8 +381,22 @@ class Form extends WidgetBase $this->setFormValues($saveData); $this->prepareVars(); - /* - * Extensibility + /** + * @event backend.form.refreshFields + * Called when the form is refreshed, giving the opportunity to modify the form fields + * + * Example usage: + * + * Event::listen('backend.form.refreshFields', function((\Backend\Widgets\Form) $formWidget, (array) $allFields) { + * $allFields['name']->required = false; + * }); + * + * Or + * + * $formWidget->bindEvent('form.refreshFields', function ((array) $allFields) { + * $allFields['name']->required = false; + * }); + * */ $this->fireSystemEvent('backend.form.refreshFields', [$this->allFields]); @@ -395,8 +423,24 @@ class Form extends WidgetBase $result = ['#'.$this->getId() => $this->makePartial('form')]; } - /* - * Extensibility + /** + * @event backend.form.refresh + * Called after the form is refreshed, should return an array of additional result parameters. + * + * Example usage: + * + * Event::listen('backend.form.refresh', function((\Backend\Widgets\Form) $formWidget, (array) $result) { + * $result['#my-partial-id' => $formWidget->makePartial('$/path/to/custom/backend/partial.htm')]; + * return $result; + * }); + * + * Or + * + * $formWidget->bindEvent('form.refresh', function ((array) $result) use ((\Backend\Widgets\Form $formWidget)) { + * $result['#my-partial-id' => $formWidget->makePartial('$/path/to/custom/backend/partial.htm')]; + * return $result; + * }); + * */ $eventResults = $this->fireSystemEvent('backend.form.refresh', [$result], false); @@ -419,8 +463,46 @@ class Form extends WidgetBase return; } - /* - * Extensibility + /** + * @event backend.form.extendFieldsBefore + * Called before the form fields are defined + * + * Example usage: + * + * Event::listen('backend.form.extendFieldsBefore', function((\Backend\Widgets\Form) $formWidget) { + * // You should always check to see if you're extending correct model/controller + * if (!$widget->model instanceof \Foo\Example\Models\Bar) { + * return; + * } + * + * // Here you can't use addFields() because it will throw you an exception because form is not yet created + * // and it does not have tabs and fields + * // For this example we will pretend that we want to add a new field named example_field + * $widget->fields['example_field'] = [ + * 'label' => 'Example field', + * 'comment' => 'Your example field', + * 'type' => 'text', + * ]; + * }); + * + * Or + * + * $formWidget->bindEvent('form.extendFieldsBefore', function () use ((\Backend\Widgets\Form $formWidget)) { + * // You should always check to see if you're extending correct model/controller + * if (!$widget->model instanceof \Foo\Example\Models\Bar) { + * return; + * } + * + * // Here you can't use addFields() because it will throw you an exception because form is not yet created + * // and it does not have tabs and fields + * // For this example we will pretend that we want to add a new field named example_field + * $widget->fields['example_field'] = [ + * 'label' => 'Example field', + * 'comment' => 'Your example field', + * 'type' => 'text', + * ]; + * }); + * */ $this->fireSystemEvent('backend.form.extendFieldsBefore'); @@ -454,8 +536,62 @@ class Form extends WidgetBase $this->allTabs->secondary = new FormTabs(FormTabs::SECTION_SECONDARY, $this->secondaryTabs); $this->addFields($this->secondaryTabs['fields'], FormTabs::SECTION_SECONDARY); - /* - * Extensibility + /** + * @event backend.form.extendFields + * Called after the form fields are defined + * + * Example usage: + * + * Event::listen('backend.form.extendFields', function((\Backend\Widgets\Form) $formWidget) { + * // Only for the User controller + * if (!$widget->getController() instanceof \RainLab\User\Controllers\Users) { + * return; + * } + * + * // Only for the User model + * if (!$widget->model instanceof \RainLab\User\Models\User) { + * return; + * } + * + * // Add an extra birthday field + * $widget->addFields([ + * 'birthday' => [ + * 'label' => 'Birthday', + * 'comment' => 'Select the users birthday', + * 'type' => 'datepicker' + * ] + * ]); + * + * // Remove a Surname field + * $widget->removeField('surname'); + * }); + * + * Or + * + * $formWidget->bindEvent('form.extendFields', function () use ((\Backend\Widgets\Form $formWidget)) { + * // Only for the User controller + * if (!$widget->getController() instanceof \RainLab\User\Controllers\Users) { + * return; + * } + * + * // Only for the User model + * if (!$widget->model instanceof \RainLab\User\Models\User) { + * return; + * } + * + * // Add an extra birthday field + * $widget->addFields([ + * 'birthday' => [ + * 'label' => 'Birthday', + * 'comment' => 'Select the users birthday', + * 'type' => 'datepicker' + * ] + * ]); + * + * // Remove a Surname field + * $widget->removeField('surname'); + * }); + * */ $this->fireSystemEvent('backend.form.extendFields', [$this->allFields]); @@ -1029,7 +1165,27 @@ class Form extends WidgetBase * Advanced usage */ if (method_exists($this->model, 'fireEvent')) { - $this->model->fireEvent('model.form.filterFields', [$this]); + /** + * @event model.form.filterFields + * Called after the form is initialized + * + * Example usage: + * + * $model->bindEvent('model.form.filterFields', function ((\Backend\Widgets\Form) $formWidget, (stdClass) $fields, (string) $context) use (\October\Rain\Database\Model $model) { + * if ($model->source_type == 'http') { + * $fields->source_url->hidden = false; + * $fields->git_branch->hidden = true; + * } elseif ($model->source_type == 'git') { + * $fields->source_url->hidden = false; + * $fields->git_branch->hidden = false; + * } else { + * $fields->source_url->hidden = true; + * $fields->git_branch->hidden = true; + * } + * }); + * + */ + $this->model->fireEvent('model.form.filterFields', [$this, (object) $this->allFields, $this->getContext()]); } } diff --git a/modules/backend/widgets/MediaManager.php b/modules/backend/widgets/MediaManager.php index 4457d1f4c..6cdee7dbb 100644 --- a/modules/backend/widgets/MediaManager.php +++ b/modules/backend/widgets/MediaManager.php @@ -277,8 +277,22 @@ class MediaManager extends WidgetBase */ $library->deleteFolder($path); - /* - * Extensibility + /** + * @event media.folder.delete + * Called after a folder is deleted + * + * Example usage: + * + * Event::listen('media.folder.delete', function((\Backend\Widgets\MediaManager) $mediaWidget, (string) $path) { + * \Log::info($path . " was deleted"); + * }); + * + * Or + * + * $mediaWidget->bindEvent('folder.delete', function ((string) $path) { + * \Log::info($path . " was deleted"); + * }); + * */ $this->fireSystemEvent('media.folder.delete', [$path]); } @@ -294,6 +308,23 @@ class MediaManager extends WidgetBase * Extensibility */ foreach ($filesToDelete as $path) { + /** + * @event media.file.delete + * Called after a file is deleted + * + * Example usage: + * + * Event::listen('media.file.delete', function((\Backend\Widgets\MediaManager) $mediaWidget, (string) $path) { + * \Log::info($path . " was deleted"); + * }); + * + * Or + * + * $mediaWidget->bindEvent('file.delete', function ((string) $path) { + * \Log::info($path . " was deleted"); + * }); + * + */ $this->fireSystemEvent('media.file.delete', [$path]); } } @@ -352,8 +383,22 @@ class MediaManager extends WidgetBase */ MediaLibrary::instance()->moveFile($originalPath, $newPath); - /* - * Extensibility + /** + * @event media.file.rename + * Called after a file is renamed / moved + * + * Example usage: + * + * Event::listen('media.file.rename', function((\Backend\Widgets\MediaManager) $mediaWidget, (string) $originalPath, (string) $newPath) { + * \Log::info($originalPath . " was moved to " . $path); + * }); + * + * Or + * + * $mediaWidget->bindEvent('file.rename', function ((string) $originalPath, (string) $newPath) { + * \Log::info($originalPath . " was moved to " . $path); + * }); + * */ $this->fireSystemEvent('media.file.rename', [$originalPath, $newPath]); } @@ -363,8 +408,22 @@ class MediaManager extends WidgetBase */ MediaLibrary::instance()->moveFolder($originalPath, $newPath); - /* - * Extensibility + /** + * @event media.folder.rename + * Called after a folder is renamed / moved + * + * Example usage: + * + * Event::listen('media.folder.rename', function((\Backend\Widgets\MediaManager) $mediaWidget, (string) $originalPath, (string) $newPath) { + * \Log::info($originalPath . " was moved to " . $path); + * }); + * + * Or + * + * $mediaWidget->bindEvent('folder.rename', function ((string) $originalPath, (string) $newPath) { + * \Log::info($originalPath . " was moved to " . $path); + * }); + * */ $this->fireSystemEvent('media.folder.rename', [$originalPath, $newPath]); } @@ -403,8 +462,22 @@ class MediaManager extends WidgetBase throw new ApplicationException(Lang::get('backend::lang.media.error_creating_folder')); } - /* - * Extensibility + /** + * @event media.folder.create + * Called after a folder is created + * + * Example usage: + * + * Event::listen('media.folder.create', function((\Backend\Widgets\MediaManager) $mediaWidget, (string) $newFolderPath) { + * \Log::info($newFolderPath . " was created"); + * }); + * + * Or + * + * $mediaWidget->bindEvent('folder.create', function ((string) $newFolderPath) { + * \Log::info($newFolderPath . " was created"); + * }); + * */ $this->fireSystemEvent('media.folder.create', [$newFolderPath]); @@ -481,8 +554,22 @@ class MediaManager extends WidgetBase */ $library->moveFile($path, $dest.'/'.basename($path)); - /* - * Extensibility + /** + * @event media.file.move + * Called after a file is moved + * + * Example usage: + * + * Event::listen('media.file.move', function((\Backend\Widgets\MediaManager) $mediaWidget, (string) $path, (string) $dest) { + * \Log::info($path . " was moved to " . $dest); + * }); + * + * Or + * + * $mediaWidget->bindEvent('file.rename', function ((string) $path, (string) $dest) { + * \Log::info($path . " was moved to " . $dest); + * }); + * */ $this->fireSystemEvent('media.file.move', [$path, $dest]); } @@ -493,8 +580,22 @@ class MediaManager extends WidgetBase */ $library->moveFolder($path, $dest.'/'.basename($path)); - /* - * Extensibility + /** + * @event media.folder.move + * Called after a folder is moved + * + * Example usage: + * + * Event::listen('media.folder.move', function((\Backend\Widgets\MediaManager) $mediaWidget, (string) $path, (string) $dest) { + * \Log::info($path . " was moved to " . $dest); + * }); + * + * Or + * + * $mediaWidget->bindEvent('folder.rename', function ((string) $path, (string) $dest) { + * \Log::info($path . " was moved to " . $dest); + * }); + * */ $this->fireSystemEvent('media.folder.move', [$path, $dest]); } @@ -1177,8 +1278,22 @@ class MediaManager extends WidgetBase File::get($realPath) ); - /* - * Extensibility + /** + * @event media.file.upload + * Called after a file is uploaded + * + * Example usage: + * + * Event::listen('media.file.upload', function((\Backend\Widgets\MediaManager) $mediaWidget, (string) $path, (\Symfony\Component\HttpFoundation\File\UploadedFile) $uploadedFile) { + * \Log::info($path . " was upoaded."); + * }); + * + * Or + * + * $mediaWidget->bindEvent('file.upload', function ((string) $path, (\Symfony\Component\HttpFoundation\File\UploadedFile) $uploadedFile) { + * \Log::info($path . " was uploaded"); + * }); + * */ $this->fireSystemEvent('media.file.upload', [$filePath, $uploadedFile]); diff --git a/modules/cms/classes/ComponentBase.php b/modules/cms/classes/ComponentBase.php index efe067530..337a9b6b7 100644 --- a/modules/cms/classes/ComponentBase.php +++ b/modules/cms/classes/ComponentBase.php @@ -151,8 +151,34 @@ abstract class ComponentBase extends Extendable */ public function runAjaxHandler($handler) { - /* - * Extensibility + /** + * @event cms.component.beforeRunAjaxHandler + * Provides an opportunity to modify an AJAX request to a component before it is processed by the component + * + * The parameter provided is `$handler` (the requested AJAX handler to be run) + * + * Example usage (forwards AJAX handlers to a backend widget): + * + * Event::listen('cms.component.beforeRunAjaxHandler', function((\Cms\Classes\ComponentBase) $component, (string) $handler) { + * if (strpos($handler, '::')) { + * list($componentAlias, $handlerName) = explode('::', $handler); + * if ($componentAlias === $this->getBackendWidgetAlias()) { + * return $this->backendControllerProxy->runAjaxHandler($handler); + * } + * } + * }); + * + * Or + * + * $this->controller->bindEvent('component.beforeRunAjaxHandler', function ((string) $handler) { + * if (strpos($handler, '::')) { + * list($componentAlias, $handlerName) = explode('::', $handler); + * if ($componentAlias === $this->getBackendWidgetAlias()) { + * return $this->backendControllerProxy->runAjaxHandler($handler); + * } + * } + * }); + * */ if ($event = $this->fireSystemEvent('cms.component.beforeRunAjaxHandler', [$handler])) { return $event; @@ -160,8 +186,28 @@ abstract class ComponentBase extends Extendable $result = $this->$handler(); - /* - * Extensibility + /** + * @event cms.component.runAjaxHandler + * Provides an opportunity to modify an AJAX request to a component after it is processed by the component + * + * The parameters provided are `$handler` (the requested AJAX handler to be run) and `$result` (the result of the component processing the request) + * + * Example usage (Logs requests and their response): + * + * Event::listen('cms.component.beforeRunHandler', function((\Cms\Classes\ComponentBase) $component, (string) $handler, (mixed) $result) { + * if (in_array($handler, $interceptHandlers)) { + * return 'request has been intercepted, original response: ' . json_encode($result); + * } + * }); + * + * Or + * + * $this->controller->bindEvent('componenet.beforeRunAjaxHandler', function ((string) $handler, (mixed) $result) { + * if (in_array($handler, $interceptHandlers)) { + * return 'request has been intercepted, original response: ' . json_encode($result); + * } + * }); + * */ if ($event = $this->fireSystemEvent('cms.component.runAjaxHandler', [$handler, $result])) { return $event; diff --git a/modules/cms/classes/Controller.php b/modules/cms/classes/Controller.php index cbf411ee6..291da0eb8 100644 --- a/modules/cms/classes/Controller.php +++ b/modules/cms/classes/Controller.php @@ -683,23 +683,34 @@ class Controller protected function runAjaxHandler($handler) { /** - * @event cms.ajax.beforeRunHandler - * Provides an opportunity to modify an AJAX request - * - * The parameter provided is `$handler` (the requested AJAX handler to be run) - * - * Example usage (forwards AJAX handlers to a backend widget): - * - * $this->controller->bindEvent('ajax.beforeRunHandler', function ($handler) { - * if (strpos($handler, '::')) { - * list($componentAlias, $handlerName) = explode('::', $handler); - * if ($componentAlias === $this->getBackendWidgetAlias()) { - * return $this->backendControllerProxy->runAjaxHandler($handler); - * } - * } - * }); - * - */ + * @event cms.ajax.beforeRunHandler + * Provides an opportunity to modify an AJAX request + * + * The parameter provided is `$handler` (the requested AJAX handler to be run) + * + * Example usage (forwards AJAX handlers to a backend widget): + * + * Event::listen('cms.ajax.beforeRunHandler', function((\Cms\Classes\Controller) $controller, (string) $handler) { + * if (strpos($handler, '::')) { + * list($componentAlias, $handlerName) = explode('::', $handler); + * if ($componentAlias === $this->getBackendWidgetAlias()) { + * return $this->backendControllerProxy->runAjaxHandler($handler); + * } + * } + * }); + * + * Or + * + * $this->controller->bindEvent('ajax.beforeRunHandler', function ((string) $handler) { + * if (strpos($handler, '::')) { + * list($componentAlias, $handlerName) = explode('::', $handler); + * if ($componentAlias === $this->getBackendWidgetAlias()) { + * return $this->backendControllerProxy->runAjaxHandler($handler); + * } + * } + * }); + * + */ if ($event = $this->fireSystemEvent('cms.ajax.beforeRunHandler', [$handler])) { return $event; } diff --git a/modules/cms/classes/Theme.php b/modules/cms/classes/Theme.php index 3cb5cfefc..da5c5d9f4 100644 --- a/modules/cms/classes/Theme.php +++ b/modules/cms/classes/Theme.php @@ -312,7 +312,77 @@ class Theme return $this->configCache = []; } - return $this->configCache = Yaml::parseFile($path); + $config = Yaml::parseFile($path); + + /** + * @event cms.theme.extendConfig + * Extend basic theme configuration supplied by the theme by returning an array. + * + * Note if planning on extending form fields, use the `cms.theme.extendFormConfig` + * event instead. + * + * Example usage: + * + * Event::listen('cms.theme.extendConfig', function ($themeCode, $config) { + * $config['name'] = 'October Theme'; + * $config['description'] = 'Another great theme from October CMS'; + * return $config; + * }); + * + */ + if ($results = Event::fire('cms.theme.extendConfig', [$this->getDirName(), $config])) { + foreach ($results as $result) { + if (!is_array($result)) { + continue; + } + $config = array_merge($config, $result); + } + } + + return $this->configCache = $config; + } + + /** + * Themes have a dedicated `form` option that provide form fields + * for customization, this is an immutable accessor for that and + * also an solid anchor point for extension. + * @return array + */ + public function getFormConfig() + { + $config = $this->getConfigArray('form'); + + /** + * @event cms.theme.extendFormConfig + * Extend form field configuration supplied by the theme by returning an array. + * + * Note if you are planning on using `assetVar` to inject CSS variables from a + * plugin registration file, make sure the plugin has elevated permissions. + * + * Example usage: + * + * Event::listen('cms.theme.extendFormConfig', function ($themeCode, $config) { + * array_set($config, 'tabs.fields.header_color', [ + * 'label' => 'Header Colour', + * 'type' => 'colorpicker', + * 'availableColors' => [#34495e, #708598, #3498db], + * 'assetVar' => 'header-bg', + * 'tab' => 'Global' + * ]); + * return $config; + * }); + * + */ + if ($results = Event::fire('cms.theme.extendFormConfig', [$this->getDirName(), $config])) { + foreach ($results as $result) { + if (!is_array($result)) { + continue; + } + $config = array_merge($config, $result); + } + } + + return $config; } /** diff --git a/modules/cms/controllers/ThemeOptions.php b/modules/cms/controllers/ThemeOptions.php index cde36f052..f8134773c 100644 --- a/modules/cms/controllers/ThemeOptions.php +++ b/modules/cms/controllers/ThemeOptions.php @@ -85,7 +85,7 @@ class ThemeOptions extends Controller { $model = $form->model; $theme = $this->findThemeObject($model->theme); - $config = $theme->getConfigArray('form'); + $config = $theme->getFormConfig(); if ($fields = array_get($config, 'fields')) { $form->addFields($fields); diff --git a/modules/cms/lang/ca/lang.php b/modules/cms/lang/ca/lang.php new file mode 100644 index 000000000..e9b524750 --- /dev/null +++ b/modules/cms/lang/ca/lang.php @@ -0,0 +1,293 @@ + [ + 'invalid_file' => "Nom d'arxiu invàlid: :name. Els noms d'arxiu només poden contenir símbols alfanumèrics, subratllats, guions i punts. Exemples de noms d'arxiu correctes: pagina.htm, pagina, subdirectori/pagina", + 'invalid_property' => "No es pot establir a propietat ':name'", + 'file_already_exists' => "L'arxiu ':name' ja existeix.", + 'error_saving' => "Error guardant l'arxiu ':name'. Si us plau revisa els permisos d'escriptura.", + 'error_creating_directory' => "Error creant el directori ':name'. Si us plau revisa els permisos d'escriptura.", + 'invalid_file_extension' => "Extensió d'arxiu invàlida: :invalid. Extensions permeses: :allowed.", + 'error_deleting' => "Error eliminant l'arxiu de plantilla ':name'. Si us plau revisa els permisos d'escriptura.", + 'delete_success' => 'Plantilles eliminades: :count.', + 'file_name_required' => "El camp 'Nom d'arxiu' és obligatori.", + 'safe_mode_enabled' => 'El mode segur està activat.' + ], + 'dashboard' => [ + 'active_theme' => [ + 'widget_title_default' => 'Lloc web', + 'online' => 'Online', + 'maintenance' => 'En manteniment', + 'manage_themes' => 'Gestionar temes', + 'customize_theme' => 'Personalitzar tema' + ] + ], + 'theme' => [ + 'not_found_name' => "No s'ha trobat el tema ':name'.", + 'by_author' => 'Per :name', + 'active' => [ + 'not_set' => "No s'ha establit un tema actiu.", + 'not_found' => "No s'ha trobat el tema actiu." + ], + 'edit' => [ + 'not_set' => "No s'ha establit un tema d'edició.", + 'not_found' => "No s'ha trobat el tema d'edició.", + 'not_match' => "L'objecte al que estàs intentant accedir no pertany al tema que estàs editant. Si us plau recarrega la pàgina." + ], + 'settings_menu' => 'Tema de front-end', + 'settings_menu_description' => 'Gestionar el tema de front-end i les opcions de personalització.', + 'default_tab' => 'Propietats', + 'name_label' => 'Nom', + 'name_create_placeholder' => 'Nou nom del tema', + 'author_label' => 'Autor', + 'author_placeholder' => 'Persona o companyia', + 'description_label' => 'Descripció', + 'description_placeholder' => 'Descripció del tema', + 'homepage_label' => 'Lloc web', + 'homepage_placeholder' => 'URL del lloc web', + 'code_label' => 'Codi', + 'code_placeholder' => 'Un codi únic per aquest tema utilitzat per a la distribució', + 'preview_image_label' => 'Imatge de previsualització', + 'preview_image_placeholder' => 'La ruta de la imatge de previsualització.', + 'dir_name_label' => 'Nom del directori', + 'dir_name_create_label' => 'El directori de destí del tema', + 'theme_label' => 'Tema', + 'theme_title' => 'Temes', + 'activate_button' => 'Activar', + 'active_button' => 'Activar', + 'customize_theme' => 'Personalitzar tema', + 'customize_button' => 'Personalitzar', + 'duplicate_button' => 'Duplicar', + 'duplicate_title' => 'Duplicar tema', + 'duplicate_theme_success' => 'Tema duplicat!', + 'manage_button' => 'Gestionar', + 'manage_title' => 'Gestionar el tema', + 'edit_properties_title' => 'Tema', + 'edit_properties_button' => 'Editar propietats', + 'save_properties' => 'Guardar propietats', + 'import_button' => 'Importar', + 'import_title' => 'Importar tema', + 'import_theme_success' => 'Tema importat!', + 'import_uploaded_file' => 'Arxiu de tema', + 'import_overwrite_label' => 'Sobreescriure arxius existents', + 'import_overwrite_comment' => 'Desmarca aquesta casella per importar només arxius nous', + 'import_folders_label' => 'Carpetes', + 'import_folders_comment' => 'Si us plau selecciona les carpetes del tema que vols importar', + 'export_button' => 'Exportar', + 'export_title' => 'Exportar tema', + 'export_folders_label' => 'Carpetes', + 'export_folders_comment' => 'Si us plau selecciona les carpetes del tema que vols exportar', + 'delete_button' => 'Eliminar', + 'delete_confirm' => 'Eliminar aquest tema? No es pot desfer!', + 'delete_active_theme_failed' => 'No es pot eliminar el tema actiu, estableix un altre tema com a actiu primer.', + 'delete_theme_success' => 'Tema eliminat!', + 'create_title' => 'Crear tema', + 'create_button' => 'Crear', + 'create_new_blank_theme' => 'Crear un nou tema buit', + 'create_theme_success' => 'Tema creat!', + 'create_theme_required_name' => 'Si us plau indica un nom pel nou tema.', + 'new_directory_name_label' => 'Directori del tema', + 'new_directory_name_comment' => 'Indica un nou nom de directori per al tema duplicat.', + 'dir_name_invalid' => 'El nom només pot contenir números, lletres llatines i els següents símbols: _-', + 'dir_name_taken' => 'El nom de directori desitjat ja existeix.', + 'find_more_themes' => 'Trobar més temes', + 'saving' => 'Guardant tema...', + 'return' => 'Tornar a la llista de temes' + ], + 'maintenance' => [ + 'settings_menu' => 'Mode de manteniment', + 'settings_menu_description' => "Configura el mode de manteniment i canvia l'activació.", + 'is_enabled' => 'Activar el mode de manteniment', + 'is_enabled_comment' => 'Selecciona la pàgina per mostrar quan el mode de manteniment està activat.', + 'hint' => "El mode de manteniment mostrarà la pàgina de manteniment als visitants que no hagin accedit a l'àrea d'administració." + ], + 'page' => [ + 'not_found_name' => "La pàgina ':name' no s'ha trobat", + 'not_found' => [ + 'label' => 'Pàgina no trobada', + 'help' => 'La pàgina demanada no es pot trobar.' + ], + 'custom_error' => [ + 'label' => 'Error de pàgina', + 'help' => "Ens sap greu, però alguna cosa ha anat malament i la pàgina no es pot mostrar." + ], + 'menu_label' => 'Pàgines', + 'unsaved_label' => 'Pàgines no guardades', + 'no_list_records' => "No s'han trobat pàgines", + 'new' => 'Nova pàgina', + 'invalid_url' => 'Format d\'URL invàlid. La URL ha de començar amb el símbol / i pot contenir números, lletres llatines i els següents símbols: ._-[]:?|/+*^$', + 'delete_confirm_multiple' => 'Eliminar pàgines seleccionades?', + 'delete_confirm_single' => 'Eliminar aquesta pàgina?', + 'no_layout' => '-- sense plantilla --', + 'cms_page' => 'Pàgina de CMS', + 'title' => 'Títol de pàgina', + 'url' => 'URL de pàgina', + 'file_name' => "Nom de l'arxiu de pàgina" + ], + 'layout' => [ + 'not_found_name' => "La plantilla ':name' no s'ha trobat", + 'menu_label' => 'Plantilles', + 'unsaved_label' => 'Plantilles no guardades', + 'no_list_records' => "No s'han trobat plantilles", + 'new' => 'Nova plantilla', + 'delete_confirm_multiple' => 'Eliminar plantilles seleccionades?', + 'delete_confirm_single' => 'Eliminar aquesta plantilla?' + ], + 'partial' => [ + 'not_found_name' => "El parcial ':name' no s'ha trobat.", + 'invalid_name' => 'Nom de parcial invàlid: :name.', + 'menu_label' => 'Parcials', + 'unsaved_label' => 'Parcials no guardats', + 'no_list_records' => "No s'han trobat parcials", + 'delete_confirm_multiple' => 'Eliminar parcials seleccionats?', + 'delete_confirm_single' => 'Eliminar aquest parcial?', + 'new' => 'Nou parcial' + ], + 'content' => [ + 'not_found_name' => "L'arxiu de contingut ':name' no s'ha trobat.", + 'menu_label' => 'Contingut', + 'unsaved_label' => 'Contingut no guardat', + 'no_list_records' => "No s'han trobat arxius de contingut", + 'delete_confirm_multiple' => 'Eliminar arxius o directoris de contingut seleccionats?', + 'delete_confirm_single' => 'Eliminar aquest arxiu de contingut?', + 'new' => 'Nou arxiu de contingut' + ], + 'ajax_handler' => [ + 'invalid_name' => "Nom de manipulador d'AJAX invàlid: :name.", + 'not_found' => "El manipulador d'AJAX ':name' no s'ha trobat." + ], + 'cms' => [ + 'menu_label' => 'CMS' + ], + 'sidebar' => [ + 'add' => 'Afegir', + 'search' => 'Cercar...' + ], + 'editor' => [ + 'settings' => 'Opcions', + 'title' => 'Títol', + 'new_title' => 'Nou títol de pàgina', + 'url' => 'URL', + 'filename' => "Nom d'arxiu", + 'layout' => 'Plantilla', + 'description' => 'Descripció', + 'preview' => 'Previsualitzar', + 'meta' => 'Meta', + 'meta_title' => 'Meta títol', + 'meta_description' => 'Meta descripció', + 'markup' => 'Marcat', + 'code' => 'Codi', + 'content' => 'Contingut', + 'hidden' => 'Amagat', + 'hidden_comment' => "Les pàgines amagades només són accessibles pels usuaris connectats a l'àrea d'administració.", + 'enter_fullscreen' => 'Activar mode de pàgina completa', + 'exit_fullscreen' => 'Sortir del mode de pàgina completa', + 'open_searchbox' => 'Obrir caixa de cerca', + 'close_searchbox' => 'Tancar caixa de cerca', + 'open_replacebox' => 'Obrir caixa de substitució', + 'close_replacebox' => 'Tancar caixa de substitució' + ], + 'asset' => [ + 'menu_label' => 'Recursos', + 'unsaved_label' => 'Recursos no guardats', + 'drop_down_add_title' => 'Afegir...', + 'drop_down_operation_title' => 'Acció...', + 'upload_files' => 'Pujar arxius', + 'create_file' => 'Crear arxiu', + 'create_directory' => 'Crear directori', + 'directory_popup_title' => 'Nou directori', + 'directory_name' => 'Nom del directori', + 'rename' => 'Reanomenar', + 'delete' => 'Eliminar', + 'move' => 'Moure', + 'select' => 'Seleccionar', + 'new' => 'Nou arxiu', + 'rename_popup_title' => 'Reanomenar', + 'rename_new_name' => 'Nou nom', + 'invalid_path' => 'La ruta només pot contenir números, lletres llatines, espais i els símbols: ._-/', + 'error_deleting_file' => "Error eliminant l'arxiu :name.", + 'error_deleting_dir_not_empty' => 'Error eliminant el directori :name. El directori no està buit.', + 'error_deleting_dir' => 'Error eliminant el directori :name.', + 'invalid_name' => 'El nom només pot contenir números, lletres llatines, espais i els símbols: ._-', + 'original_not_found' => "No s'ha trobat l'arxiu o directori original", + 'already_exists' => 'Ja existeix un arxiu o directori amb aquest nom', + 'error_renaming' => "Error reanomenant l'arxiu o directori", + 'name_cant_be_empty' => 'El nom no pot estar buit', + 'too_large' => "L'arxiu pujat és massa gran. La mida màxima d'arxiu és :max_size", + 'type_not_allowed' => "Només es permeten els següents tipus d'arxiu: :allowed_types", + 'file_not_valid' => "L'arxiu no és vàlid", + 'error_uploading_file' => "Error pujant l'arxiu ':name': :error", + 'move_please_select' => 'si us plau selecciona', + 'move_destination' => 'Directori de destí', + 'move_popup_title' => 'Moure recursos', + 'move_button' => 'Moure', + 'selected_files_not_found' => "No s'han trobat els arxius seleccionats", + 'select_destination_dir' => 'Si us plau selecciona un directori de destí', + 'destination_not_found' => "No s'ha trobat el directori de destí", + 'error_moving_file' => "Error movent l'arxiu :file", + 'error_moving_directory' => 'Error movent el directori :dir', + 'error_deleting_directory' => 'Error eliminant el directori original :dir', + 'no_list_records' => "No s'han trobat arxius", + 'delete_confirm' => 'Eliminar els arxius o directoris seleccionats?', + 'path' => 'Ruta' + ], + 'component' => [ + 'menu_label' => 'Components', + 'unnamed' => 'Sense nom', + 'no_description' => "No s'ha proveït una descripció", + 'alias' => 'Alies', + 'alias_description' => 'Un nom únic assignat a aquest component per utilitzar-lo al codi de la pàgina o plantilla.', + 'validation_message' => 'Els àlies de components són obligatoris i només poden contenir lletres llatines, números i subratllats. Han de començar amb una lletra.', + 'invalid_request' => 'La plantilla no es pot guardar perquè conté dades de components invàlides.', + 'no_records' => "No s'han trobat components", + 'not_found' => "No s'ha trobat el component ':name'.", + 'method_not_found' => "El component ':name' no conté un mètode ':method'." + ], + 'template' => [ + 'invalid_type' => 'Tipus de plantilla desconegut.', + 'not_found' => "No s'ha trobat la plantilla.", + 'saved' => 'Plantilla guardada.', + 'no_list_records' => "No s'han trobat registres", + 'delete_confirm' => 'Eliminar les plantilles seleccionades?', + 'order_by' => 'Ordenar per' + ], + 'permissions' => [ + 'name' => 'CMS', + 'manage_content' => 'Gestionar els arxius de contingut del lloc web', + 'manage_assets' => 'Gestionar els recursos del lloc web - imatges, arxius JavaScript, arxius CSS', + 'manage_pages' => 'Crear, modificar i eliminar pàgines del lloc web', + 'manage_layouts' => 'Crear, modificar i eliminar plantilles del CMS', + 'manage_partials' => 'Crear, modificar i eliminar parcials del CMS', + 'manage_themes' => 'Activar, desactivar and configurar temes del CMS', + 'manage_theme_options' => 'Configurar opcions de personalització pel tema actiu', + ], + 'theme_log' => [ + 'hint' => "Aquest registre mostra els canvis fets al tema des de l'àrea d'administració.", + 'menu_label' => 'Registre del tema', + 'menu_description' => 'Veure canvis fets al tema actiu.', + 'empty_link' => 'Buidar registre del tema', + 'empty_loading' => 'Buidant registre del tema...', + 'empty_success' => 'Registre del tema buit!', + 'return_link' => 'Tornar al registre del tema', + 'id' => 'ID', + 'id_label' => 'ID del registre', + 'created_at' => 'Data i hora', + 'user' => 'Usuari', + 'type' => 'Tipus', + 'type_create' => 'Crear', + 'type_update' => 'Modificar', + 'type_delete' => 'Eliminar', + 'theme_name' => 'Tema', + 'theme_code' => 'Codi del tema', + 'old_template' => 'Plantilla (antiga)', + 'new_template' => 'Plantilla (nova)', + 'template' => 'Plantilla', + 'diff' => 'Canvis', + 'old_value' => 'Valor antic', + 'new_value' => 'Valor nou', + 'preview_title' => 'Canvis de la plantilla', + 'template_updated' => "La plantilla s'ha modificat", + 'template_created' => "La plantilla s'ha creat", + 'template_deleted' => "La plantilla s'ha eliminat", + ], +]; diff --git a/modules/cms/models/ThemeData.php b/modules/cms/models/ThemeData.php index bb6de6a42..e06f1a859 100644 --- a/modules/cms/models/ThemeData.php +++ b/modules/cms/models/ThemeData.php @@ -52,6 +52,10 @@ class ThemeData extends Model */ protected static $instances = []; + /** + * Before saving the model, strip dynamic attributes applied from config. + * @return void + */ public function beforeSave() { /* @@ -64,6 +68,10 @@ class ThemeData extends Model $this->setRawAttributes(array_only($this->getAttributes(), $staticAttributes)); } + /** + * Clear asset cache after saving to ensure `assetVar` form fields take + * immediate effect. + */ public function afterSave() { try { @@ -95,6 +103,11 @@ class ThemeData extends Model return self::$instances[$dirName] = $themeData; } + /** + * After fetching the model, intiialize model relationships based + * on form field definitions. + * @return void + */ public function afterFetch() { $data = (array) $this->data + $this->getDefaultValues(); @@ -122,6 +135,10 @@ class ThemeData extends Model $this->setRawAttributes((array) $this->getAttributes() + $data, true); } + /** + * Before model is validated, set the default values. + * @return void + */ public function beforeValidate() { if (!$this->exists) { @@ -149,6 +166,7 @@ class ThemeData extends Model /** * Gets default values for this model based on form field definitions. + * @return array */ public function getDefaultValues() { @@ -175,7 +193,7 @@ class ThemeData extends Model throw new Exception(Lang::get('Unable to find theme with name :name', $this->theme)); } - $config = $theme->getConfigArray('form'); + $config = $theme->getFormConfig(); return array_get($config, 'fields', []) + array_get($config, 'tabs.fields', []) + diff --git a/modules/cms/models/ThemeLog.php b/modules/cms/models/ThemeLog.php index 0e7a7c713..343f2fe05 100644 --- a/modules/cms/models/ThemeLog.php +++ b/modules/cms/models/ThemeLog.php @@ -73,7 +73,7 @@ class ThemeLog extends Model $newContent = $template->toCompiled(); $oldContent = $template->getOriginal('content'); - if ($newContent === $oldContent && !$isDelete) { + if ($newContent === $oldContent && $templateName === $oldTemplateName && !$isDelete) { traceLog($newContent, $oldContent); traceLog('Content not dirty for: '. $template->getObjectTypeDirName().'/'.$template->fileName); return; diff --git a/modules/cms/widgets/templatelist/partials/_toolbar.htm b/modules/cms/widgets/templatelist/partials/_toolbar.htm index d447daad0..d5d805c80 100644 --- a/modules/cms/widgets/templatelist/partials/_toolbar.htm +++ b/modules/cms/widgets/templatelist/partials/_toolbar.htm @@ -13,7 +13,7 @@