diff --git a/.github/ISSUE_TEMPLATE/1_BUG_REPORT.yaml b/.github/ISSUE_TEMPLATE/1_BUG_REPORT.yaml
index 38486aae1..6b1130299 100644
--- a/.github/ISSUE_TEMPLATE/1_BUG_REPORT.yaml
+++ b/.github/ISSUE_TEMPLATE/1_BUG_REPORT.yaml
@@ -26,6 +26,7 @@ body:
label: PHP Version
description: Please provide us the branch of PHP version. For example, for PHP version 7.4.9, select `7.4`, or for PHP version 8.0.1, select `8.0`.
options:
+ - 8.1
- 8.0
- 7.4
- 7.3
@@ -71,8 +72,3 @@ body:
attributes:
label: Workaround
description: If you have a workaround, please detail it here, for the benefit of other users who encounter the issue.
- - type: textarea
- id: other_details
- attributes:
- label: Any further details?
- description: If some other information is needed to describe this issue, please specify it here.
diff --git a/.github/workflows/code-quality.yaml b/.github/workflows/code-quality.yaml
index 834d662f7..58bfdf2a2 100644
--- a/.github/workflows/code-quality.yaml
+++ b/.github/workflows/code-quality.yaml
@@ -37,3 +37,24 @@ jobs:
- name: Run code quality checks (on pull request)
if: github.event_name == 'pull_request'
run: ./.github/workflows/utilities/phpcs-pr ${{ github.base_ref }}
+ codeQualityJS:
+ runs-on: ubuntu-latest
+ name: JavaScript
+ steps:
+ - name: Checkout changes
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Install Node
+ uses: actions/setup-node@v1
+ with:
+ node-version: 12
+
+ - name: Install Node dependencies
+ working-directory: ./modules/system/assets/js/snowboard
+ run: npm install
+
+ - name: Run code quality checks
+ working-directory: ./modules/system/assets/js/snowboard
+ run: npx eslint .
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 984e4171b..ad0c7b0ad 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -24,9 +24,11 @@ jobs:
node-version: 12
- name: Install Node dependencies
+ working-directory: ./tests/js
run: npm install
- name: Run tests
+ working-directory: ./tests/js
run: npm run test
phpUnitTests:
strategy:
diff --git a/.gitignore b/.gitignore
index 63a1b61d7..0a2a3393b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,6 @@ _ide_helper.php
.DS_Store
package-lock.json
/node_modules
+
+# Ignore generated public directory from `winter:mirror public`
+public
\ No newline at end of file
diff --git a/.gitpod.yml b/.gitpod.yml
index d9d1ca589..8bd98780c 100644
--- a/.gitpod.yml
+++ b/.gitpod.yml
@@ -27,10 +27,10 @@ vscode:
github:
prebuilds:
- master: true
+ master: false
branches: false
- pullRequests: true
+ pullRequests: false
pullRequestsFromForks: false
- addCheck: true
+ addCheck: false
addComment: false
addBadge: true
diff --git a/.jshintrc b/.jshintrc
deleted file mode 100644
index bb55890a9..000000000
--- a/.jshintrc
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "esversion": 6,
- "curly": true,
- "asi": true
-}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 000000000..ab85385f6
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,5 @@
+{
+ "recommendations": [
+ "wintercms.winter-cms"
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..8c3d85f11
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,29 @@
+{
+ "files.associations": {
+ "**/modules/*/behaviors/*/partials/*.htm": "php",
+ "**/modules/*/controllers/*.htm": "php",
+ "**/modules/*/formwidgets/*/partials/*.htm": "php",
+ "**/modules/*/layouts/*.htm": "php",
+ "**/modules/*/models/*/*.htm": "php",
+ "**/modules/*/partials/*.htm": "php",
+ "**/modules/*/reportwidgets/*/partials/*.htm": "php",
+ "**/modules/*/views/mail/*.htm": "wintercms",
+ "**/modules/*/widgets/*/partials/*.htm": "php",
+
+ "**/plugins/*/*/behaviors/*/partials/*.htm": "php",
+ "**/plugins/*/*/components/*/*.htm": "twig",
+ "**/plugins/*/*/controllers/*/*.htm": "php",
+ "**/plugins/*/*/formwidgets/*/partials/*.htm": "php",
+ "**/plugins/*/*/layouts/*.htm": "php",
+ "**/plugins/*/*/models/*/*.htm": "php",
+ "**/plugins/*/*/partials/*.htm": "php",
+ "**/plugins/*/*/reportwidgets/*/partials/*.htm": "php",
+ "**/plugins/*/*/views/mail/*.htm": "wintercms",
+ "**/plugins/*/*/widgets/*/partials/*.htm": "php",
+
+ "**/themes/*/content/**/*.htm": "wintercms",
+ "**/themes/*/layouts/*.htm": "wintercms",
+ "**/themes/*/pages/**/*.htm": "wintercms",
+ "**/themes/*/partials/**/*.htm": "wintercms"
+ }
+}
diff --git a/composer.json b/composer.json
index 71a01c5ee..e7f98a59e 100644
--- a/composer.json
+++ b/composer.json
@@ -29,7 +29,7 @@
"source": "https://github.com/wintercms/winter"
},
"require": {
- "php": ">=7.2.9",
+ "php": "^7.2.9|^8.0",
"winter/storm": "~1.1.2",
"winter/wn-system-module": "~1.1.2",
"winter/wn-backend-module": "~1.1.2",
@@ -83,5 +83,11 @@
"replace": false,
"merge-dev": false
}
+ },
+ "config": {
+ "allow-plugins": {
+ "composer/installers": true,
+ "wikimedia/composer-merge-plugin": true
+ }
}
}
diff --git a/config/app.php b/config/app.php
index a05ae4f3b..66d305064 100644
--- a/config/app.php
+++ b/config/app.php
@@ -43,6 +43,22 @@
'url' => 'http://localhost',
+ /*
+ |--------------------------------------------------------------------------
+ | Asset URL
+ |--------------------------------------------------------------------------
+ |
+ | This URL is used to properly generate URLs for assets, including
+ | URLs generated by the `| theme` & `| asset` filters in Twig, or the
+ | `Url::asset()` & `asset()` helpers. If set to null, the URL used will
+ | be the current hostname or `app.url` config.
+ |
+ | 'asset_url' => 'https://cdn.example.com/',
+ |
+ */
+
+ 'asset_url' => env('ASSET_URL', null),
+
/*
|--------------------------------------------------------------------------
| Trusted hosts
@@ -110,13 +126,13 @@
| from your proxy when rewriting the request. This is an integer map value
| so you may specify more than one value.
|
- | Possible values (prepended with `Illuminate\Http\Request::`)
- | - HEADER_X_FORWARDED_ALL - trust all forwarded headers
- | - HEADER_X_FORWARDED_FOR - trust only the proxy IP
- | - HEADER_X_FORWARDED_HOST - trust only the proxy hostname
- | - HEADER_X_FORWARDED_PORT - trust only the proxy port
- | - HEADER_X_FORWARDED_PROTO - trust only the proxy protocol
- | - HEADER_X_FORWARDED_AWS_ELB - trust Amazon Elastic Load Balancing header
+ | Possible values:
+ | - 'HEADER_X_FORWARDED_ALL' - trust all forwarded headers
+ | - Illuminate\Http\Request::HEADER_X_FORWARDED_FOR - trust only the proxy IP
+ | - Illuminate\Http\Request::HEADER_X_FORWARDED_HOST - trust only the proxy hostname
+ | - Illuminate\Http\Request::HEADER_X_FORWARDED_PORT - trust only the proxy port
+ | - Illuminate\Http\Request::HEADER_X_FORWARDED_PROTO - trust only the proxy protocol
+ | - Illuminate\Http\Request::HEADER_X_FORWARDED_AWS_ELB - trust Amazon Elastic Load Balancing headers
|
| Examples:
| - To trust only the hostname, use the following:
@@ -132,7 +148,7 @@
| - Amazon ELB users should always use the "HEADER_X_FORWARDED_AWS_ELB" option.
*/
- 'trustedProxyHeaders' => Illuminate\Http\Request::HEADER_X_FORWARDED_ALL,
+ 'trustedProxyHeaders' => 'HEADER_X_FORWARDED_ALL',
/*
|--------------------------------------------------------------------------
@@ -168,7 +184,7 @@
| backend yet, as this can cause issues in the backend.
|
| Currently supported backend locales are listed in
- | Backend\Models\Preference->getLocaleOptions())
+ | Backend\Models\Preference->getLocaleOptions()
|
*/
diff --git a/config/cache.php b/config/cache.php
index f31e784c3..17e250763 100644
--- a/config/cache.php
+++ b/config/cache.php
@@ -56,19 +56,19 @@
],
'memcached' => [
- 'driver' => 'memcached',
- 'persistent_id' => null,
+ 'driver' => 'memcached',
+ 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
- // env('MEMCACHED_USERNAME'),
- // env('MEMCACHED_PASSWORD'),
+ env('MEMCACHED_USERNAME'),
+ env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
- 'host' => '127.0.0.1',
- 'port' => 11211,
+ 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
+ 'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
@@ -81,11 +81,11 @@
'dynamodb' => [
'driver' => 'dynamodb',
- 'key' => '',
- 'secret' => '',
- 'region' => 'us-east-1',
- 'table' => 'cache',
- 'endpoint' => '',
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
+ 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
+ 'endpoint' => env('DYNAMODB_ENDPOINT'),
],
],
diff --git a/config/database.php b/config/database.php
index c866db25e..ab04e6c28 100644
--- a/config/database.php
+++ b/config/database.php
@@ -143,21 +143,4 @@
],
- /*
- |--------------------------------------------------------------------------
- | Use DB configuration for testing
- |--------------------------------------------------------------------------
- |
- | When running plugin tests Winter CMS by default uses SQLite in memory.
- | You can override this behavior by setting `useConfigForTesting` to true.
- |
- | After that Winter CMS will take DB parameters from the config.
- | If file `/config/testing/database.php` exists, config will be read from it,
- | but remember that when not specified it will use parameters specified in
- | `/config/database.php`.
- |
- */
-
- 'useConfigForTesting' => false,
-
];
diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php
index 0db8fc8af..d8c19f9fe 100644
--- a/modules/backend/ServiceProvider.php
+++ b/modules/backend/ServiceProvider.php
@@ -143,48 +143,52 @@ protected function registerBackendPermissions()
'backend.access_dashboard' => [
'label' => 'system::lang.permissions.view_the_dashboard',
'tab' => 'system::lang.permissions.name',
+ 'roles' => [UserRole::CODE_DEVELOPER, UserRole::CODE_PUBLISHER],
],
'backend.manage_default_dashboard' => [
'label' => 'system::lang.permissions.manage_default_dashboard',
'tab' => 'system::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
],
'backend.manage_users' => [
'label' => 'system::lang.permissions.manage_other_administrators',
'tab' => 'system::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
],
'backend.impersonate_users' => [
'label' => 'system::lang.permissions.impersonate_users',
'tab' => 'system::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
],
'backend.manage_preferences' => [
'label' => 'system::lang.permissions.manage_preferences',
'tab' => 'system::lang.permissions.name',
+ 'roles' => [UserRole::CODE_DEVELOPER, UserRole::CODE_PUBLISHER],
],
'backend.manage_editor' => [
'label' => 'system::lang.permissions.manage_editor',
'tab' => 'system::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
],
'backend.manage_own_editor' => [
'label' => 'system::lang.permissions.manage_own_editor',
'tab' => 'system::lang.permissions.name',
+ 'roles' => [UserRole::CODE_DEVELOPER, UserRole::CODE_PUBLISHER],
],
'backend.manage_branding' => [
'label' => 'system::lang.permissions.manage_branding',
'tab' => 'system::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
],
'media.manage_media' => [
'label' => 'backend::lang.permissions.manage_media',
'tab' => 'system::lang.permissions.name',
+ 'roles' => [UserRole::CODE_DEVELOPER, UserRole::CODE_PUBLISHER],
],
'backend.allow_unsafe_markdown' => [
'label' => 'backend::lang.permissions.allow_unsafe_markdown',
'tab' => 'system::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
],
]);
$manager->registerPermissionOwnerAlias('Winter.Backend', 'October.Backend');
diff --git a/modules/backend/behaviors/FormController.php b/modules/backend/behaviors/FormController.php
index b08657aaa..28d58c415 100644
--- a/modules/backend/behaviors/FormController.php
+++ b/modules/backend/behaviors/FormController.php
@@ -24,7 +24,7 @@
* This behavior is implemented in the controller like so:
*
* public $implement = [
- * 'Backend.Behaviors.FormController',
+ * \Backend\Behaviors\FormController::class,
* ];
*
* public $formConfig = 'config_form.yaml';
@@ -66,11 +66,6 @@ class FormController extends ControllerBehavior
*/
protected $formWidget;
- /**
- * @inheritDoc
- */
- protected $requiredProperties = ['formConfig'];
-
/**
* @var array Configuration values that must exist when applying the primary config file.
* - modelClass: Class name for the model
@@ -93,6 +88,11 @@ class FormController extends ControllerBehavior
*/
protected $model;
+ /**
+ * @var mixed Configuration for this behaviour
+ */
+ public $formConfig = 'config_form.yaml';
+
/**
* Behavior constructor
* @param \Backend\Classes\Controller $controller
@@ -104,7 +104,7 @@ public function __construct($controller)
/*
* Build configuration
*/
- $this->config = $this->makeConfig($controller->formConfig, $this->requiredConfig);
+ $this->config = $this->makeConfig($controller->formConfig ?: $this->formConfig, $this->requiredConfig);
$this->config->modelClass = Str::normalizeClassName($this->config->modelClass);
}
@@ -176,7 +176,7 @@ public function initForm($model, $context = null)
/*
* Detected Relation controller behavior
*/
- if ($this->controller->isClassExtendedWith('Backend.Behaviors.RelationController')) {
+ if ($this->controller->isClassExtendedWith(\Backend\Behaviors\RelationController::class)) {
$this->controller->initRelation($model);
}
diff --git a/modules/backend/behaviors/ImportExportController.php b/modules/backend/behaviors/ImportExportController.php
index 7419d7a85..d93683dea 100644
--- a/modules/backend/behaviors/ImportExportController.php
+++ b/modules/backend/behaviors/ImportExportController.php
@@ -22,7 +22,7 @@
* This behavior is implemented in the controller like so:
*
* public $implement = [
- * 'Backend.Behaviors.ImportExportController',
+ * \Backend\Behaviors\ImportExportController::class,
* ];
*
* public $importExportConfig = 'config_import_export.yaml';
@@ -36,11 +36,6 @@
*/
class ImportExportController extends ControllerBehavior
{
- /**
- * @inheritDoc
- */
- protected $requiredProperties = ['importExportConfig'];
-
/**
* @var array Configuration values that must exist when applying the primary config file.
*/
@@ -96,6 +91,11 @@ class ImportExportController extends ControllerBehavior
*/
protected $exportOptionsFormWidget;
+ /**
+ * @var mixed Configuration for this behaviour
+ */
+ public $importExportConfig = 'config_import_export.yaml';
+
/**
* Behavior constructor
* @param Backend\Classes\Controller $controller
@@ -107,7 +107,7 @@ public function __construct($controller)
/*
* Build configuration
*/
- $this->config = $this->makeConfig($controller->importExportConfig, $this->requiredConfig);
+ $this->config = $this->makeConfig($controller->importExportConfig ?: $this->importExportConfig, $this->requiredConfig);
/*
* Process config
@@ -576,7 +576,7 @@ protected function checkUseListExportMode()
return false;
}
- if (!$this->controller->isClassExtendedWith('Backend.Behaviors.ListController')) {
+ if (!$this->controller->isClassExtendedWith(\Backend\Behaviors\ListController::class)) {
throw new ApplicationException(Lang::get('backend::lang.import_export.behavior_missing_uselist_error'));
}
diff --git a/modules/backend/behaviors/ListController.php b/modules/backend/behaviors/ListController.php
index 936bd0045..e69326e8e 100644
--- a/modules/backend/behaviors/ListController.php
+++ b/modules/backend/behaviors/ListController.php
@@ -12,7 +12,7 @@
* This behavior is implemented in the controller like so:
*
* public $implement = [
- * 'Backend.Behaviors.ListController',
+ * \Backend\Behaviors\ListController::class,
* ];
*
* public $listConfig = 'config_list.yaml';
@@ -36,11 +36,6 @@ class ListController extends ControllerBehavior
*/
protected $primaryDefinition;
- /**
- * @var array List configuration, keys for alias and value for config objects.
- */
- protected $listConfig = [];
-
/**
* @var \Backend\Classes\WidgetBase[] Reference to the list widget object.
*/
@@ -56,11 +51,6 @@ class ListController extends ControllerBehavior
*/
protected $filterWidgets = [];
- /**
- * @inheritDoc
- */
- protected $requiredProperties = ['listConfig'];
-
/**
* @var array Configuration values that must exist when applying the primary config file.
* - modelClass: Class name for the model
@@ -73,6 +63,11 @@ class ListController extends ControllerBehavior
*/
protected $actions = ['index'];
+ /**
+ * @var mixed Configuration for this behaviour
+ */
+ public $listConfig = 'config_list.yaml';
+
/**
* Behavior constructor
* @param \Backend\Classes\Controller $controller
@@ -84,12 +79,13 @@ public function __construct($controller)
/*
* Extract list definitions
*/
- if (is_array($controller->listConfig)) {
- $this->listDefinitions = $controller->listConfig;
+ $config = $controller->listConfig ?: $this->listConfig;
+ if (is_array($config)) {
+ $this->listDefinitions = $config;
$this->primaryDefinition = key($this->listDefinitions);
}
else {
- $this->listDefinitions = ['list' => $controller->listConfig];
+ $this->listDefinitions = ['list' => $config];
$this->primaryDefinition = 'list';
}
@@ -382,8 +378,6 @@ public function listRender($definition = null)
$definition = $this->primaryDefinition;
}
- $listConfig = $this->controller->listGetConfig($definition);
-
$vars = [
'toolbar' => null,
'filter' => null,
@@ -421,10 +415,10 @@ public function listMakePartial($partial, $params = [])
/**
* Refreshes the list container only, useful for returning in custom AJAX requests.
- * @param string $definition Optional list definition.
+ *
* @return array The list element selector as the key, and the list contents are the value.
*/
- public function listRefresh($definition = null)
+ public function listRefresh(string $definition = null)
{
if (!count($this->listWidgets)) {
$this->makeLists();
@@ -441,7 +435,7 @@ public function listRefresh($definition = null)
* Returns the widget used by this behavior.
* @return \Backend\Classes\WidgetBase
*/
- public function listGetWidget($definition = null)
+ public function listGetWidget(string $definition = null)
{
if (!$definition) {
$definition = $this->primaryDefinition;
@@ -452,16 +446,19 @@ public function listGetWidget($definition = null)
/**
* Returns the configuration used by this behavior.
- * @return \Backend\Classes\WidgetBase
+ * @return stdClass
*/
- public function listGetConfig($definition = null)
+ public function listGetConfig(string $definition = null)
{
if (!$definition) {
$definition = $this->primaryDefinition;
}
- if (!$config = array_get($this->listConfig, $definition)) {
- $config = $this->listConfig[$definition] = $this->makeConfig($this->listDefinitions[$definition], $this->requiredConfig);
+ if (
+ !($config = array_get($this->listDefinitions, $definition))
+ || !is_object($config)
+ ) {
+ $config = $this->listDefinitions[$definition] = $this->makeConfig($this->listDefinitions[$definition], $this->requiredConfig);
}
return $config;
diff --git a/modules/backend/behaviors/RelationController.php b/modules/backend/behaviors/RelationController.php
index c5972adaf..8f81627a9 100644
--- a/modules/backend/behaviors/RelationController.php
+++ b/modules/backend/behaviors/RelationController.php
@@ -14,7 +14,7 @@
* This behavior is implemented in the controller like so:
*
* public $implement = [
- * 'Backend.Behaviors.RelationController',
+ * \Backend\Behaviors\RelationController::class,
* ];
*
* public $relationConfig = 'config_relation.yaml';
@@ -80,11 +80,6 @@ class RelationController extends ControllerBehavior
*/
protected $pivotWidget;
- /**
- * @inheritDoc
- */
- protected $requiredProperties = ['relationConfig'];
-
/**
* @var array Properties that must exist for each relationship definition.
*/
@@ -200,6 +195,11 @@ class RelationController extends ControllerBehavior
*/
protected $foreignId;
+ /**
+ * @var mixed Configuration for this behaviour
+ */
+ public $relationConfig = 'config_relation.yaml';
+
/**
* @var string Active session key, used for deferred bindings.
*/
@@ -229,7 +229,7 @@ public function __construct($controller)
/*
* Build configuration
*/
- $this->config = $this->originalConfig = $this->makeConfig($controller->relationConfig, $this->requiredConfig);
+ $this->config = $this->originalConfig = $this->makeConfig($controller->relationConfig ?: $this->relationConfig, $this->requiredConfig);
}
/**
@@ -411,7 +411,7 @@ public function initRelation($model, $field = null)
/*
* Pivot widget
*/
- if ($this->manageMode == 'pivot' && $this->pivotWidget = $this->makePivotWidget()) {
+ if ($this->manageMode === 'pivot' && $this->pivotWidget = $this->makePivotWidget()) {
$this->controller->relationExtendPivotWidget($this->pivotWidget, $this->field, $this->model);
$this->pivotWidget->bindToController();
}
@@ -610,7 +610,7 @@ protected function makeToolbarWidget()
/*
* Add search to toolbar
*/
- $useSearch = $this->viewMode == 'multi' && $this->getConfig('view[showSearch]');
+ $useSearch = $this->viewMode === 'multi' && $this->getConfig('view[showSearch]');
if ($useSearch) {
$toolbarConfig->search = [
@@ -661,7 +661,7 @@ protected function makeViewWidget()
/*
* Multiple (has many, belongs to many)
*/
- if ($this->viewMode == 'multi') {
+ if ($this->viewMode === 'multi') {
$config = $this->makeConfigForMode('view', 'list');
$config->model = $this->relationModel;
$config->alias = $this->alias . 'ViewList';
@@ -738,9 +738,9 @@ protected function makeViewWidget()
/*
* Allows pivot data to enter the fray
*/
- if ($this->relationType == 'belongsToMany'
- || $this->relationType == 'morphToMany'
- || $this->relationType == 'morphedByMany'
+ if ($this->relationType === 'belongsToMany'
+ || $this->relationType === 'morphToMany'
+ || $this->relationType === 'morphedByMany'
) {
$this->relationObject->setQuery($query->getQuery());
return $this->relationObject;
@@ -784,7 +784,7 @@ protected function makeViewWidget()
/*
* Single (belongs to, has one)
*/
- elseif ($this->viewMode == 'single') {
+ elseif ($this->viewMode === 'single') {
$this->viewModel = $this->relationObject->getResults()
?: $this->relationModel;
@@ -808,8 +808,8 @@ protected function makeManageWidget()
/*
* List / Pivot
*/
- if ($this->manageMode == 'list' || $this->manageMode == 'pivot') {
- $isPivot = $this->manageMode == 'pivot';
+ if ($this->manageMode === 'list' || $this->manageMode === 'pivot') {
+ $isPivot = $this->manageMode === 'pivot';
$config = $this->makeConfigForMode('manage', 'list');
$config->model = $this->relationModel;
@@ -821,7 +821,7 @@ protected function makeManageWidget()
$config->recordsPerPage = $this->getConfig('manage[recordsPerPage]');
$config->noRecordsMessage = $this->getConfig('manage[noRecordsMessage]');
- if ($this->viewMode == 'single') {
+ if ($this->viewMode === 'single') {
$config->showCheckboxes = false;
$config->recordOnClick = sprintf(
"$.wn.relationBehavior.clickManageListRecord(':%s', '%s', '%s')",
@@ -898,7 +898,7 @@ protected function makeManageWidget()
/*
* Form
*/
- elseif ($this->manageMode == 'form') {
+ elseif ($this->manageMode === 'form') {
if (!$config = $this->makeConfigForMode('manage', 'form', false)) {
return null;
}
@@ -933,7 +933,7 @@ protected function makeManageWidget()
/*
* Exclude existing relationships
*/
- if ($this->manageMode == 'pivot' || $this->manageMode == 'list') {
+ if ($this->manageMode === 'pivot' || $this->manageMode === 'list') {
$widget->bindEvent('list.extendQuery', function ($query) {
/*
* Where not in the current list of related records
@@ -1070,7 +1070,7 @@ public function onRelationManageForm()
{
$this->beforeAjax();
- if ($this->manageMode == 'pivot' && $this->manageId) {
+ if ($this->manageMode === 'pivot' && $this->manageId) {
return $this->onRelationManagePivotForm();
}
@@ -1092,7 +1092,7 @@ public function onRelationManageCreate()
$saveData = $this->manageWidget->getSaveData();
$sessionKey = $this->deferredBinding ? $this->relationGetSessionKey(true) : null;
- if ($this->viewMode == 'multi') {
+ if ($this->viewMode === 'multi') {
$newModel = $this->relationModel;
/*
@@ -1107,14 +1107,34 @@ public function onRelationManageCreate()
);
}
+ // Handle Pivot Data when creating related records
+ $isPivot = false;
+ if (
+ in_array($this->relationType, ['belongsToMany', 'morphToMany', 'morphedByMany'])
+ && !empty($saveData['pivot'])
+ ) {
+ $isPivot = true;
+ $pivotModel = $this->relationObject->newPivot();
+ $newModel->setRelation('pivot', $pivotModel);
+ }
+
$modelsToSave = $this->prepareModelsToSave($newModel, $saveData);
+
foreach ($modelsToSave as $modelToSave) {
+ if ($modelToSave instanceof \Winter\Storm\Database\Pivot) {
+ $pivotData = $modelToSave->getAttributes();
+ continue;
+ }
+
$modelToSave->save(null, $this->manageWidget->getSessionKey());
}
- $this->relationObject->add($newModel, $sessionKey);
- }
- elseif ($this->viewMode == 'single') {
+ if ($isPivot && !empty($pivotData)) {
+ $this->relationObject->add($newModel, $sessionKey, $pivotData);
+ } else {
+ $this->relationObject->add($newModel, $sessionKey);
+ }
+ } elseif ($this->viewMode === 'single') {
$newModel = $this->viewModel = $this->viewWidget->model = $this->manageWidget->model;
$this->viewWidget->setFormValues($saveData);
@@ -1140,7 +1160,7 @@ public function onRelationManageCreate()
* Belongs to relations won't save when using add() so
* it should occur if the conditions are right.
*/
- if (!$this->deferredBinding && $this->relationType == 'belongsTo') {
+ if (!$this->deferredBinding && $this->relationType === 'belongsTo') {
$parentModel = $this->relationObject->getParent();
if ($parentModel->exists) {
$parentModel->save();
@@ -1160,14 +1180,14 @@ public function onRelationManageUpdate()
$this->beforeAjax();
$saveData = $this->manageWidget->getSaveData();
- if ($this->viewMode == 'multi') {
+ if ($this->viewMode === 'multi') {
$model = $this->manageWidget->model;
$modelsToSave = $this->prepareModelsToSave($model, $saveData);
foreach ($modelsToSave as $modelToSave) {
$modelToSave->save(null, $this->manageWidget->getSessionKey());
}
}
- elseif ($this->viewMode == 'single') {
+ elseif ($this->viewMode === 'single') {
// Ensure that the view widget model is the same instance as the manage widget model
// since they will technically be different object instances in this context as
// $viewWidet->model is populated by $this->relationObject->getResults() and
@@ -1191,7 +1211,7 @@ public function onRelationManageDelete()
/*
* Multiple (has many, belongs to many)
*/
- if ($this->viewMode == 'multi') {
+ if ($this->viewMode === 'multi') {
if (($checkedIds = post('checked')) && is_array($checkedIds)) {
foreach ($checkedIds as $relationId) {
if (!$obj = $this->relationModel->find($relationId)) {
@@ -1205,7 +1225,7 @@ public function onRelationManageDelete()
/*
* Single (belongs to, has one)
*/
- elseif ($this->viewMode == 'single') {
+ elseif ($this->viewMode === 'single') {
$relatedModel = $this->viewModel;
if ($relatedModel->exists) {
$relatedModel->delete();
@@ -1234,7 +1254,7 @@ public function onRelationManageAdd()
/*
* Add
*/
- if ($this->viewMode == 'multi') {
+ if ($this->viewMode === 'multi') {
$checkedIds = $recordId ? [$recordId] : post('checked');
if (is_array($checkedIds)) {
@@ -1254,7 +1274,7 @@ public function onRelationManageAdd()
/*
* Link
*/
- elseif ($this->viewMode == 'single') {
+ elseif ($this->viewMode === 'single') {
if ($recordId && ($model = $this->relationModel->find($recordId))) {
if ($this->relationType === 'hasOne') {
// Unassign previous relation if one is already assigned
@@ -1272,7 +1292,7 @@ public function onRelationManageAdd()
* Belongs to relations won't save when using add() so
* it should occur if the conditions are right.
*/
- if (!$this->deferredBinding && $this->relationType == 'belongsTo') {
+ if (!$this->deferredBinding && $this->relationType === 'belongsTo') {
$parentModel = $this->relationObject->getParent();
if ($parentModel->exists) {
$parentModel->save();
@@ -1298,7 +1318,7 @@ public function onRelationManageRemove()
/*
* Remove
*/
- if ($this->viewMode == 'multi') {
+ if ($this->viewMode === 'multi') {
$checkedIds = $recordId ? [$recordId] : post('checked');
if (is_array($checkedIds)) {
@@ -1313,8 +1333,8 @@ public function onRelationManageRemove()
/*
* Unlink
*/
- elseif ($this->viewMode == 'single') {
- if ($this->relationType == 'belongsTo') {
+ elseif ($this->viewMode === 'single') {
+ if ($this->relationType === 'belongsTo') {
$this->relationObject->dissociate();
$this->relationObject->getParent()->save();
@@ -1324,7 +1344,7 @@ public function onRelationManageRemove()
$this->initRelation($this->model);
}
}
- elseif ($this->relationType == 'hasOne' || $this->relationType == 'morphOne') {
+ elseif ($this->relationType === 'hasOne' || $this->relationType === 'morphOne') {
if ($obj = $relatedModel->find($recordId)) {
$this->relationObject->remove($obj, $sessionKey);
}
@@ -1689,7 +1709,7 @@ protected function evalManageMode()
if (isset($this->config->pivot)) {
return 'pivot';
}
- elseif ($this->eventTarget == 'list') {
+ elseif ($this->eventTarget === 'list') {
return 'form';
}
else {
@@ -1700,7 +1720,7 @@ protected function evalManageMode()
case 'morphOne':
case 'hasMany':
case 'morphMany':
- if ($this->eventTarget == 'button-add') {
+ if ($this->eventTarget === 'button-add') {
return 'list';
}
@@ -1787,7 +1807,7 @@ protected function makeConfigForMode($mode = 'view', $type = 'list', $throwExcep
* - view.list => manage.list
*/
if (!$config) {
- if ($mode == 'manage' && $type == 'list') {
+ if ($mode === 'manage' && $type === 'list') {
return $this->makeConfigForMode('view', $type);
}
diff --git a/modules/backend/behaviors/ReorderController.php b/modules/backend/behaviors/ReorderController.php
index aedb46677..20cd3460f 100644
--- a/modules/backend/behaviors/ReorderController.php
+++ b/modules/backend/behaviors/ReorderController.php
@@ -11,7 +11,7 @@
* This behavior is implemented in the controller like so:
*
* public $implement = [
- * 'Backend.Behaviors.ReorderController',
+ * \Backend\Behaviors\ReorderController::class,
* ];
*
* public $reorderConfig = 'config_reorder.yaml';
@@ -25,11 +25,6 @@
*/
class ReorderController extends ControllerBehavior
{
- /**
- * @inheritDoc
- */
- protected $requiredProperties = ['reorderConfig'];
-
/**
* @var array Configuration values that must exist when applying the primary config file.
*/
@@ -67,6 +62,11 @@ class ReorderController extends ControllerBehavior
*/
protected $toolbarWidget;
+ /**
+ * @var mixed Configuration for this behaviour
+ */
+ public $reorderConfig = 'config_reorder.yaml';
+
/**
* Behavior constructor
* @param Backend\Classes\Controller $controller
@@ -78,7 +78,7 @@ public function __construct($controller)
/*
* Build configuration
*/
- $this->config = $this->makeConfig($controller->reorderConfig, $this->requiredConfig);
+ $this->config = $this->makeConfig($controller->reorderConfig ?: $this->reorderConfig, $this->requiredConfig);
/*
* Widgets
diff --git a/modules/backend/classes/AuthManager.php b/modules/backend/classes/AuthManager.php
index 982a71e54..deb56cc9a 100644
--- a/modules/backend/classes/AuthManager.php
+++ b/modules/backend/classes/AuthManager.php
@@ -253,7 +253,7 @@ protected function validateUserModel($user)
/**
* Returns an array of registered permissions belonging to a given role code
* @param string $role
- * @param bool $includeOrphans
+ * @param bool $includeOrphans Include any permissons that do not have a default role specified
* @return array
*/
public function listPermissionsForRole($role, $includeOrphans = true)
diff --git a/modules/backend/classes/BackendController.php b/modules/backend/classes/BackendController.php
index 88d6ff3f5..b809e0feb 100644
--- a/modules/backend/classes/BackendController.php
+++ b/modules/backend/classes/BackendController.php
@@ -148,8 +148,6 @@ class_exists('\Cms\Classes\Controller')
*/
public function run($url = null)
{
- $params = RouterHelper::segmentizeUrl($url);
-
// Handle NotFoundHttpExceptions in the backend (usually triggered by abort(404))
Event::listen('exception.beforeRender', function ($exception, $httpCode, $request) {
if (!$this->cmsHandling && $exception instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException) {
diff --git a/modules/backend/controllers/AccessLogs.php b/modules/backend/controllers/AccessLogs.php
index 8e437e8db..05391cb7b 100644
--- a/modules/backend/controllers/AccessLogs.php
+++ b/modules/backend/controllers/AccessLogs.php
@@ -17,14 +17,9 @@ class AccessLogs extends Controller
* @var array Extensions implemented by this controller.
*/
public $implement = [
- \Backend\Behaviors\ListController::class
+ \Backend\Behaviors\ListController::class,
];
- /**
- * @var array `ListController` configuration.
- */
- public $listConfig = 'config_list.yaml';
-
/**
* @var array Permissions required to view this page.
*/
diff --git a/modules/backend/controllers/Index.php b/modules/backend/controllers/Index.php
index 008ec1634..0827d61aa 100644
--- a/modules/backend/controllers/Index.php
+++ b/modules/backend/controllers/Index.php
@@ -77,6 +77,7 @@ protected function checkPermissionRedirect()
if ($first = array_first(BackendMenu::listMainMenuItems(), $true)) {
return Redirect::intended($first->url);
}
+ return Backend::redirect('backend/users/myaccount');
}
}
}
diff --git a/modules/backend/controllers/Preferences.php b/modules/backend/controllers/Preferences.php
index cce20becb..262c1059c 100644
--- a/modules/backend/controllers/Preferences.php
+++ b/modules/backend/controllers/Preferences.php
@@ -18,14 +18,9 @@
class Preferences extends Controller
{
public $implement = [
- \Backend\Behaviors\FormController::class
+ \Backend\Behaviors\FormController::class,
];
- /**
- * @var array `FormController` configuration.
- */
- public $formConfig = 'config_form.yaml';
-
/**
* @var array Permissions required to view this page.
*/
diff --git a/modules/backend/controllers/UserGroups.php b/modules/backend/controllers/UserGroups.php
index e73c9a56e..10bc25725 100644
--- a/modules/backend/controllers/UserGroups.php
+++ b/modules/backend/controllers/UserGroups.php
@@ -18,19 +18,9 @@ class UserGroups extends Controller
*/
public $implement = [
\Backend\Behaviors\FormController::class,
- \Backend\Behaviors\ListController::class
+ \Backend\Behaviors\ListController::class,
];
- /**
- * @var array `FormController` configuration.
- */
- public $formConfig = 'config_form.yaml';
-
- /**
- * @var array `ListController` configuration.
- */
- public $listConfig = 'config_list.yaml';
-
/**
* @var array Permissions required to view this page.
*/
diff --git a/modules/backend/controllers/UserRoles.php b/modules/backend/controllers/UserRoles.php
index cddc0b67e..7aa23d045 100644
--- a/modules/backend/controllers/UserRoles.php
+++ b/modules/backend/controllers/UserRoles.php
@@ -20,19 +20,10 @@ class UserRoles extends Controller
*/
public $implement = [
\Backend\Behaviors\FormController::class,
- \Backend\Behaviors\ListController::class
+ \Backend\Behaviors\ListController::class,
+ \Backend\Behaviors\RelationController::class,
];
- /**
- * @var array `FormController` configuration.
- */
- public $formConfig = 'config_form.yaml';
-
- /**
- * @var array `ListController` configuration.
- */
- public $listConfig = 'config_list.yaml';
-
/**
* @var array Permissions required to view this page.
*/
@@ -57,30 +48,4 @@ public function __construct()
}
});
}
-
- /**
- * Add available permission fields to the Role form.
- */
- public function formExtendFields($form)
- {
- /*
- * Add permissions tab
- */
- $form->addTabFields($this->generatePermissionsField());
- }
-
- /**
- * Adds the permissions editor widget to the form.
- * @return array
- */
- protected function generatePermissionsField()
- {
- return [
- 'permissions' => [
- 'tab' => 'backend::lang.user.permissions',
- 'type' => 'Backend\FormWidgets\PermissionEditor',
- 'mode' => 'checkbox'
- ]
- ];
- }
}
diff --git a/modules/backend/controllers/Users.php b/modules/backend/controllers/Users.php
index 15bd175c6..1dc885f4c 100644
--- a/modules/backend/controllers/Users.php
+++ b/modules/backend/controllers/Users.php
@@ -25,19 +25,9 @@ class Users extends Controller
*/
public $implement = [
\Backend\Behaviors\FormController::class,
- \Backend\Behaviors\ListController::class
+ \Backend\Behaviors\ListController::class,
];
- /**
- * @var array `FormController` configuration.
- */
- public $formConfig = 'config_form.yaml';
-
- /**
- * @var array `ListController` configuration.
- */
- public $listConfig = 'config_list.yaml';
-
/**
* @var array Permissions required to view this page.
*/
diff --git a/modules/backend/controllers/preferences/_field_editor_preview.htm b/modules/backend/controllers/preferences/_field_editor_preview.htm
index f052ea72e..d8256a024 100644
--- a/modules/backend/controllers/preferences/_field_editor_preview.htm
+++ b/modules/backend/controllers/preferences/_field_editor_preview.htm
@@ -17,6 +17,6 @@
data-show-gutter="= $model->editor_show_gutter ? 'true' : 'false' ?>"
data-language="css"
data-margin="0"
- data-vendor-path="= Url::to('/modules/backend/formwidgets/codeeditor/assets/vendor/ace') ?>/">
+ data-vendor-path="= Url::asset('/modules/backend/formwidgets/codeeditor/assets/vendor/ace') ?>/">
diff --git a/modules/backend/controllers/userroles/__users.htm b/modules/backend/controllers/userroles/__users.htm
new file mode 100644
index 000000000..85372e143
--- /dev/null
+++ b/modules/backend/controllers/userroles/__users.htm
@@ -0,0 +1 @@
+= $this->relationRender('users'); ?>
\ No newline at end of file
diff --git a/modules/backend/controllers/userroles/config_relation.yaml b/modules/backend/controllers/userroles/config_relation.yaml
new file mode 100644
index 000000000..aa9b85507
--- /dev/null
+++ b/modules/backend/controllers/userroles/config_relation.yaml
@@ -0,0 +1,10 @@
+# ===================================
+# Relation Behavior Config
+# ===================================
+
+users:
+ label: backend::lang.user.name
+ view:
+ list: ~/modules/backend/models/user/columns.yaml
+ toolbarButtons: add|remove
+ recordUrl: backend/users/update/:id
diff --git a/modules/backend/formwidgets/DatePicker.php b/modules/backend/formwidgets/DatePicker.php
index a76897241..c6ad5f3c9 100644
--- a/modules/backend/formwidgets/DatePicker.php
+++ b/modules/backend/formwidgets/DatePicker.php
@@ -128,6 +128,11 @@ public function prepareVars()
$value = $value->toDateTimeString();
}
+ // Disable the datepicker visually when readOnly is enabled
+ if ($this->formField->readOnly) {
+ $this->formField->disabled = true;
+ }
+
$this->vars['name'] = $this->getFieldName();
$this->vars['value'] = $value ?: '';
$this->vars['field'] = $this->formField;
diff --git a/modules/backend/formwidgets/colorpicker/assets/css/colorpicker.css b/modules/backend/formwidgets/colorpicker/assets/css/colorpicker.css
index 53498005b..8178f7c0c 100644
--- a/modules/backend/formwidgets/colorpicker/assets/css/colorpicker.css
+++ b/modules/backend/formwidgets/colorpicker/assets/css/colorpicker.css
@@ -9,4 +9,5 @@
.field-colorpicker .pcr-app[data-theme="nano"] .pcr-selection .pcr-color-preview .pcr-last-color{display:block;flex-grow:0;flex-shrink:0;width:20%;border-radius:0;background:var(--pcr-color);border-bottom:1px solid rgba(0,0,0,0.5)}
.field-colorpicker .pcr-app[data-theme="nano"] .pcr-selection .pcr-color-palette{grid-row-start:2;grid-column-start:1;grid-row-end:2;grid-column-end:3}
.field-colorpicker .pcr-app[data-theme="nano"] .pcr-selection .pcr-color-chooser{margin-top:0.8rem;grid-row-start:3;grid-column-start:1;grid-row-end:3;grid-column-end:3}
-.field-colorpicker .pcr-app[data-theme="nano"] .pcr-selection .pcr-color-opacity{margin-top:0.8em;grid-row-start:4;grid-column-start:1;grid-row-end:4;grid-column-end:3}
\ No newline at end of file
+.field-colorpicker .pcr-app[data-theme="nano"] .pcr-selection .pcr-color-opacity{margin-top:0.8em;grid-row-start:4;grid-column-start:1;grid-row-end:4;grid-column-end:3}
+.field-colorpicker[data-disabled="true"] [data-color-preview]{cursor:default}
\ No newline at end of file
diff --git a/modules/backend/formwidgets/colorpicker/assets/js/colorpicker.js b/modules/backend/formwidgets/colorpicker/assets/js/colorpicker.js
index 8f30d5d6e..311c5db9f 100644
--- a/modules/backend/formwidgets/colorpicker/assets/js/colorpicker.js
+++ b/modules/backend/formwidgets/colorpicker/assets/js/colorpicker.js
@@ -53,6 +53,7 @@
this.keyboardEntry = false
this.originalColor = null
this.originalFormat = null
+ this.formatSet = false
// Create a Pickr instance
this.pickr = Pickr.create({
@@ -103,6 +104,7 @@
this.pickr.on('swatchselect', (hsva) => this.onPickerSwatch(hsva))
this.pickr.on('cancel', () => this.onPickerStopChange())
this.pickr.on('hide', () => this.onPickerHide())
+ this.pickr.on('clear', () => this.onPickerClear())
}
/**
@@ -190,7 +192,10 @@
return
}
- if (this.pickr.setColor(this.$colorValue.val())) {
+ if (
+ (this.$colorValue.val() === '' && this.options.allowEmpty)
+ || this.pickr.setColor(this.$colorValue.val())
+ ) {
this.keyboardEntry = true
} else {
this.keyboardEntry = false
@@ -233,14 +238,19 @@
* Fires when the picker is first initialized for a widget.
*/
ColorPicker.prototype.onPickerInit = function () {
- this.pickr.setColor(this.$dataLocker.val())
+ if (this.$dataLocker.val()) {
+ this.pickr.setColor(this.$dataLocker.val())
+ }
+
this.hidePicker()
- if (this.options.formats.length === 1) {
- this.setColorFormat(this.options.formats[0])
- }
+ if (this.$dataLocker.val()) {
+ if (this.options.formats.length === 1) {
+ this.setColorFormat(this.options.formats[0])
+ }
- this.setColor(this.pickr.getColor())
+ this.setColor(this.pickr.getColor())
+ }
}
/**
@@ -260,6 +270,11 @@
*/
ColorPicker.prototype.onPickerChange = function (hsva) {
this.keyboardEntry = false
+
+ if (!this.formatSet && this.options.formats.length === 1) {
+ this.setColorFormat(this.options.formats[0])
+ }
+
$(this.pickr.getRoot().preview.currentColor).text(this.valueFromHSVA(hsva))
// If the format changes, change the value
@@ -283,6 +298,10 @@
ColorPicker.prototype.onPickerSwatch = function (hsva) {
this.keyboardEntry = false
this.setColor(hsva)
+
+ if (this.options.formats.length === 1) {
+ this.setColorFormat(this.options.formats[0])
+ }
}
/**
@@ -290,15 +309,36 @@
*/
ColorPicker.prototype.onPickerHide = function () {
if (this.keyboardEntry) {
- this.setColor(this.pickr.getColor())
+ if (this.$colorValue.val() === '' && this.options.allowEmpty) {
+ this.setColor()
+ } else {
+ this.setColor(this.pickr.getColor())
- if (this.options.formats.length === 1) {
- this.setColorFormat(this.options.formats[0])
+ if (this.options.formats.length === 1) {
+ this.setColorFormat(this.options.formats[0])
+ }
}
}
- this.pickr.setColor(this.$dataLocker.val())
+ if (this.$dataLocker.val() === '') {
+ this.pickr.setColor(null)
+ } else {
+ this.pickr.setColor(this.$dataLocker.val())
+ }
this.$colorValue.val(this.$dataLocker.val())
+
+ if (
+ this.originalColor !== null
+ && this.valueFromHSVA(this.pickr.getColor()) !== this.valueFromHSVA(this.originalColor)
+ ) {
+ this.$el.trigger('change')
+ }
+ }
+
+ ColorPicker.prototype.onPickerClear = function () {
+ this.setColor()
+ this.hidePicker()
+ this.$colorValue.blur()
}
/**
@@ -349,12 +389,22 @@
/**
* Sets the color value for the widget and updates the color preview.
*
- * @param {HSVaColor} hsva
+ * @param {HSVaColor?} hsva
*/
ColorPicker.prototype.setColor = function(hsva) {
- this.$colorPreview.css('background', this.valueFromHSVA(hsva, 'hex'))
- this.$colorValue.val(this.valueFromHSVA(hsva))
- this.$dataLocker.val(this.valueFromHSVA(hsva))
+ if (hsva === undefined && !this.options.allowEmpty) {
+ this.$colorPreview.css('background', this.valueFromHSVA(this.originalColor, 'hex'))
+ this.$colorValue.val(this.valueFromHSVA(this.originalColor))
+ this.$dataLocker.val(this.valueFromHSVA(this.originalColor))
+ } else if (hsva === undefined) {
+ this.$colorPreview.css('background', '#fff')
+ this.$colorValue.val('')
+ this.$dataLocker.val('')
+ } else {
+ this.$colorPreview.css('background', this.valueFromHSVA(hsva, 'hex'))
+ this.$colorValue.val(this.valueFromHSVA(hsva))
+ this.$dataLocker.val(this.valueFromHSVA(hsva))
+ }
}
/**
@@ -378,6 +428,8 @@
this.pickr.setColorRepresentation('HEX')
break
}
+
+ this.formatSet = true
}
/**
diff --git a/modules/backend/formwidgets/colorpicker/assets/less/colorpicker.less b/modules/backend/formwidgets/colorpicker/assets/less/colorpicker.less
index 1b4f7b67a..766f553e3 100644
--- a/modules/backend/formwidgets/colorpicker/assets/less/colorpicker.less
+++ b/modules/backend/formwidgets/colorpicker/assets/less/colorpicker.less
@@ -103,4 +103,7 @@
}
}
+ &[data-disabled="true"] [data-color-preview] {
+ cursor: default;
+ }
}
diff --git a/modules/backend/formwidgets/colorpicker/partials/_colorpicker.htm b/modules/backend/formwidgets/colorpicker/partials/_colorpicker.htm
index 93ac09a15..dc4397d13 100644
--- a/modules/backend/formwidgets/colorpicker/partials/_colorpicker.htm
+++ b/modules/backend/formwidgets/colorpicker/partials/_colorpicker.htm
@@ -13,14 +13,22 @@
>
-
disabled
- previewMode): ?> readonly
- >
+ previewMode): ?>
+
+ = $value ?>
+
+
+
disabled
+ >
+
diff --git a/modules/backend/formwidgets/markdowneditor/assets/js/markdowneditor.js b/modules/backend/formwidgets/markdowneditor/assets/js/markdowneditor.js
index 108a9e46b..3e8cc9b1a 100644
--- a/modules/backend/formwidgets/markdowneditor/assets/js/markdowneditor.js
+++ b/modules/backend/formwidgets/markdowneditor/assets/js/markdowneditor.js
@@ -826,13 +826,13 @@
label: 'markdowneditor.link',
icon: 'link',
action: 'formatInline',
- template: '[$1](http://)'
+ template: '[$1](https://)'
},
image: {
label: 'markdowneditor.image',
icon: 'image',
action: 'formatInline',
- template: '![$1](http://)'
+ template: '![$1](https://)'
},
horizontalrule: {
label: 'markdowneditor.horizontalrule',
diff --git a/modules/backend/lang/ca/lang.php b/modules/backend/lang/ca/lang.php
index bb07d1ec9..108adbad0 100644
--- a/modules/backend/lang/ca/lang.php
+++ b/modules/backend/lang/ca/lang.php
@@ -512,7 +512,7 @@
'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_9' => '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)',
diff --git a/modules/backend/lang/da/lang.php b/modules/backend/lang/da/lang.php
index ee9a4bb3d..af6ae4a5c 100644
--- a/modules/backend/lang/da/lang.php
+++ b/modules/backend/lang/da/lang.php
@@ -464,7 +464,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabisk)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Græsk)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebraisk)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Tyrkisk)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Tyrkisk)',
'iso_8859_10' => 'ISO-8859-10 (Latin-6, Nordisk)',
'iso_8859_11' => 'ISO-8859-11 (Latin, Thai)',
'iso_8859_13' => 'ISO-8859-13 (Latin-7, Baltic Rim)',
diff --git a/modules/backend/lang/de/lang.php b/modules/backend/lang/de/lang.php
index ef052f8fb..8b13bbbaa 100644
--- a/modules/backend/lang/de/lang.php
+++ b/modules/backend/lang/de/lang.php
@@ -537,7 +537,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/el/lang.php b/modules/backend/lang/el/lang.php
index e55d33d84..acebfe19d 100644
--- a/modules/backend/lang/el/lang.php
+++ b/modules/backend/lang/el/lang.php
@@ -470,7 +470,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php
index feb55ccc4..7b29eea02 100644
--- a/modules/backend/lang/en/lang.php
+++ b/modules/backend/lang/en/lang.php
@@ -369,6 +369,7 @@
'plugin_missing' => 'The plugin :name is a dependency but is not installed. Please install this plugin.',
'debug' => 'Debug mode is enabled. This is not recommended for production installations.',
'decompileBackendAssets' => 'Assets in the Backend are currently decompiled. This is not recommended for production installations.',
+ 'default_backend_user' => 'A user with the default login details (admin / admin@domain.tld) was found. Change their username and / or email address to help protect the system.',
],
'editor' => [
'menu_label' => 'Editor settings',
@@ -567,7 +568,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/es/lang.php b/modules/backend/lang/es/lang.php
index df4653c45..f79c9411f 100644
--- a/modules/backend/lang/es/lang.php
+++ b/modules/backend/lang/es/lang.php
@@ -460,7 +460,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/et/lang.php b/modules/backend/lang/et/lang.php
index 6189eb022..f60c4cbf5 100644
--- a/modules/backend/lang/et/lang.php
+++ b/modules/backend/lang/et/lang.php
@@ -480,7 +480,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/fa/lang.php b/modules/backend/lang/fa/lang.php
index b25ea3f4e..463cc4925 100644
--- a/modules/backend/lang/fa/lang.php
+++ b/modules/backend/lang/fa/lang.php
@@ -1,5 +1,4 @@
[
'title' => 'بخش مدیریت',
@@ -67,8 +66,8 @@
'email_placeholder' => "پست الکترونیکی",
'enter_new_password' => "کلمه عبور جدید را وارد نمایید",
'password_reset' => "بازنشاندن کلمه عبور",
- 'restore_success' => "یک نامه به پست الکترونیکی شما جهت شروع عملیات بارگرداندن کلمه عبور ارسال شد.",
- 'reset_success' => "کلمه عبور شما بارگردانی شد و شما هم اکنون میتوانید وارد سیستم شوید.",
+ 'restore_success' => "یک نامه به پست الکترونیکی شما جهت شروع فرایند بازنشانی کلمه عبور ارسال شد.",
+ 'reset_success' => "کلمه عبور شما بازنشانی شد و شما هم اکنون میتوانید وارد سیستم شوید.",
'reset_error' => "اطلاعات رمز عبور نا معتبر است , لطفا مجددا تلاش نمایید!",
'reset_fail' => "عدم توانایی در بازنشاندن کلمه عبور شما!",
'apply' => 'اعمال کردن',
@@ -98,7 +97,7 @@
'reset_layout_success' => 'تنطیم مجدد طرح بندی انجام شد.',
'make_default' => 'استفاده به عنوان پیشفرض',
'make_default_confirm' => 'آیا از استفاده طرح بندی کنونی به عنوان پیشفرض اطمینان دارید؟',
- 'make_default_success' => 'طرح بندی کنونی به عنوان پیشفرض تایین شد.',
+ 'make_default_success' => 'طرح بندی کنونی به عنوان پیشفرض تعیین شد.',
'collapse_all' => 'بستن همه',
'expand_all' => 'باز کردن همه',
'status' => [
@@ -202,7 +201,7 @@
'no_records' => 'چیزی یافت نشد.',
'missing_model' => 'هیچ مدلی برای لیست استفاده شده در کلاس :class تعریف نشده است.',
'missing_column' => 'ستونی برای :columns تعریف نشده است.',
- 'missing_columns' => 'ستونی برای لیست عریف شده در :class موجود نیست.',
+ 'missing_columns' => 'ستونی برای لیست تعریف شده در :class موجود نیست.',
'missing_definition' => "در لیست تعریف شده ستونی برای ':field' موجود نیست.",
'missing_parent_definition' => "کنترل کننده لیست شامل تعریف ':definition' نمی باشد.",
'behavior_not_ready' => 'لسیت مقدار دهی اولیه شده است ، لطفا بررسی نمایید که متد makeLists() در کنترلر خود فراخوانی کرده باشید.',
@@ -214,22 +213,22 @@
'next_page' => 'صفحه بعد',
'refresh' => 'بازنشانی',
'updating' => 'درحال به روز رسانی...',
- 'loading' => 'در حال بارگذاری...',
+ 'loading' => 'در حال بارگزاری...',
'setup_title' => 'راه اندازی لیست',
'setup_help' => 'ستون هایی را که میخواهید مشاهده نمایید را انتخاب نمایید. میتوانید محل قرار گیری ستونها را با جابجا نمودن آنها به .',
'records_per_page' => 'مورد در هر صفحه',
- 'records_per_page_help' => 'تعداد موارد نمایش داده شده در هر صفحه را انتخاب نمایید. لطفا توجه نمایید نمایش تعداد زیادی از موارد در هر صفحه از کارایی سیستم میکاهد.',
+ 'records_per_page_help' => 'تعداد موارد نمایش داده شده در هر صفحه را انتخاب نمایید. لطفا توجه نمایید نمایش تعداد زیادی از موارد در هر صفحه از کارایی سیستم می کاهد.',
'check' => 'بررسی',
'delete_selected' => 'حذف انتخاب شده ها',
'delete_selected_empty' => 'مورد جهت حذف انتخاب نشده است.',
'delete_selected_confirm' => 'آیا میخواهید موارد انتخابی را حذف کنید؟',
- 'delete_selected_success' => 'حدف موارد انخابی انجام شد.',
+ 'delete_selected_success' => 'حدف موارد انتخابی انجام شد.',
'column_switch_true' => 'بله',
'column_switch_false' => 'خیر'
],
'fileupload' => [
'attachment' => 'فایل ضمیمه',
- 'help' => 'برای فایل ضمیمه عنوان و توضیح اختصاص بهری وارد نمایید.',
+ 'help' => 'برای فایل ضمیمه عنوان و توضیح مختصری وارد نمایید.',
'title_label' => 'عنوان',
'description_label' => 'توضیحات',
'default_prompt' => 'فایل را جهت ارسال به این نقطه بکشید و یا %s را کلیک کنید',
@@ -290,7 +289,7 @@
'ok' => 'تایید',
'or' => 'یا',
'confirm_tab_close' => 'در صورت بستن این پنجره موارد ذخیره نشده از بین خواهند رفت. آیا از حذف شدن این پنجره اطمینان دارید؟',
- 'behavior_not_ready' => 'فرم مور نظر مقدار دهی اولیه نشده است ، بررسی کنید که متد initForm() در کنترلر فرتخوانی شده باشد.',
+ 'behavior_not_ready' => 'فرم مور نظر مقدار دهی اولیه نشده است ، بررسی کنید که متد initForm() در کنترلر فراخوانی شده باشد.',
'preview_no_files_message' => 'فایلی جهت ارسال وجود ندارد',
'preview_no_media_message' => 'رسانه ای انتخاب نشده است.',
'preview_no_record_message' => 'موردی انتخاب نشده است.',
@@ -320,6 +319,7 @@
'missing_model' => "مدلی برای ارتباط موجود در :class وجود ندارد.",
'invalid_action_single' => "این عمل در ارتباط یک تعرفه نمبتواند اعمال شود.",
'invalid_action_multi' => "این عمل در ارتباط چند طرفه نمیتواند اعمال شود.",
+ 'relationwidget_unsupported_type' => 'نوع رابطه ":type" توسط ارتباط ابزارک پشتیبانی نمی شود.',
'help' => "بر روی یک گزینه کلیک کنید تا افزوده شود",
'related_data' => "اطلاعات :name مرتبط",
'add' => "افزودن",
@@ -345,7 +345,7 @@
'link_name' => "لینک :name",
'unlink' => "حذف لینک",
'unlink_name' => "حذف لینک :name",
- 'unlink_confirm' => "آبا اطمینان دارید؟",
+ 'unlink_confirm' => "آیا اطمینان دارید؟",
],
'reorder' => [
'default_title' => 'مرتب سازی موارد',
@@ -354,7 +354,7 @@
'model' => [
'name' => "مدل",
'not_found' => "مدل ':class' با مشخصه ی :id یافت نشد",
- 'missing_id' => "مشخصه ای برای مودل مورد نظر یافت نشد.",
+ 'missing_id' => "مشخصه ای برای مدل مورد نظر یافت نشد.",
'missing_relation' => "مدل ':class' شامل تعریفی از ':relation'.",
'missing_method' => "مدل ':class' متدی با نام ':method' ندارد.",
'invalid_class' => "مدل :model استفاده شده در :class معتبر نمی باشد، این مدل باید از کلاس \Model ارث برده باشد.",
@@ -368,6 +368,7 @@
'plugin_missing' => 'افزونه :name مورد نیاز است ولی نصب نشده. لطفا این افزونه را نصب کنید.',
'debug' => 'حالت عیب یابی فعال است. این مورد در حالت ارائه نهایی نرم افزار توصیه نمی شود.',
'decompileBackendAssets' => 'فایل های assets موجود در Backend در حال حاضر کامپایل نشده اند. این مورد هنگام ارائه نهایی نرم افزار توصیه نمی شود.',
+ 'default_backend_user' => 'کاربر مدیر با مشخصات پیش فرض (admin / admin@domain.tld)یافت شد. جهت افزایش امنیت سیستم نام کاربری و پست الکترونیک را تغییر دهید.',
],
'editor' => [
'menu_label' => 'تنظیمات ویرایشگر کد',
@@ -457,6 +458,11 @@
'app_tagline' => 'شعار برنامه',
'app_tagline_description' => 'این شعار در قسمت ورود به بخش مدیریت نمایش داده می شود.',
'colors' => 'رنگ ها',
+ 'branding_colors' => 'رنگ های برندسازی',
+ 'branding_colors_comment' => 'این رنگ ها جهت یکسان سازی رنگ برند شما در بخش مدیریت استفاده می شوند',
+ 'default_colors' => 'رنگ های پیش فرض',
+ 'default_colors_comment' => 'این رنگها بهعنوان نمونه در همه انتخابکننده های رنگ در دسترس خواهند بود، مگر اینکه لغو شوند.',
+ 'add_default_color' => 'افزودن یک رنگ پیش فرض',
'primary_color' => 'اصلی color',
'secondary_color' => 'ثانویه color',
'accent_color' => 'رنگ مهم',
@@ -561,7 +567,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
@@ -608,7 +614,7 @@
'uploading_file_num' => 'ارسال :number فایل(ها)...',
'uploading_complete' => 'ارسال انجام شد',
'uploading_error' => 'خطا در ارسال',
- 'type_blocked' => 'نوع فایل استفاده شده به دلیل مسایل امنیتی مجاز نمی باشد..',
+ 'type_blocked' => 'نوع فایل استفاده شده به دلیل مسائل امنیتی مجاز نمی باشد..',
'order_by' => 'مرتب سازی با',
'direction' => 'جهت',
'direction_asc' => 'صعودی',
diff --git a/modules/backend/lang/fi/lang.php b/modules/backend/lang/fi/lang.php
index 0b1d8ad0a..7caf7fb6d 100644
--- a/modules/backend/lang/fi/lang.php
+++ b/modules/backend/lang/fi/lang.php
@@ -500,7 +500,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, arabialainen)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Kreikkalainen)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Heprealainen)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkkilainen)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkkilainen)',
'iso_8859_10' => 'ISO-8859-10 (Latin-6, Pohjoismainen)',
'iso_8859_11' => 'ISO-8859-11 (Latin, Thaimaalainen)',
'iso_8859_13' => 'ISO-8859-13 (Latin-7, Balttilainen)',
diff --git a/modules/backend/lang/fr/lang.php b/modules/backend/lang/fr/lang.php
index bc3ded4ea..0eb5bb982 100644
--- a/modules/backend/lang/fr/lang.php
+++ b/modules/backend/lang/fr/lang.php
@@ -2,30 +2,30 @@
return [
'auth' => [
- 'title' => 'Zone d’administration',
+ 'title' => 'Zone d\'administration',
'invalid_login' => 'L\'utilisateur saisi ne correspond à aucun utilisateur enregistré. Merci de vérifier votre saisie et de réessayer.'
],
'field' => [
'invalid_type' => 'Type de champ invalide :type.',
- 'options_method_invalid_model' => "L’attribut ':field' ne correspond à aucun modèle valide. Essayez de spécifier explicitement la méthode d’options pour la classe du modèle ':model'.",
+ 'options_method_invalid_model' => "L\'attribut ':field' ne correspond à aucun modèle valide. Essayez de spécifier explicitement la méthode d\'options pour la classe du modèle ':model'.",
'options_method_not_exists' => 'La classe du modèle :model doit définir une méthode :method() renvoyant des options pour le champ ":field" du formulaire.',
'colors_method_not_exists' => "La classe du modèle :model doit définir une méthode :method() renvoyant le code html en hexadécimal de la couleur du champ ':field' du formulaire."
],
'widget' => [
- 'not_registered' => 'Aucun widget au nom de classe ":name" n’a été enregistré',
- 'not_bound' => 'Un widget au nom de classe ":name" n’a pas été lié au contrôleur'
+ 'not_registered' => 'Aucun widget au nom de classe ":name" n\'a été enregistré',
+ 'not_bound' => 'Un widget au nom de classe ":name" n\'a pas été lié au contrôleur'
],
'page' => [
'untitled' => 'Sans titre',
'access_denied' => [
'label' => 'Accès refusé',
- 'help' => 'Vous n’êtes pas autorisé à consulter cette page.',
- 'cms_link' => 'Retour à l’interface d’administration'
+ 'help' => 'Vous n\'êtes pas autorisé à consulter cette page.',
+ 'cms_link' => 'Retour à l\'interface d\'administration'
],
'no_database' => [
'label' => 'Base de données introuvable',
- 'help' => 'Une base de données est requise pour l’accès à l’interface d’administration. Veuillez vérifier que la base de données existe et que les migrations ont été effectuées avant de réessayer.',
- 'cms_link' => 'Retour à l’accueil'
+ 'help' => 'Une base de données est requise pour l\'accès à l\'interface d\'administration. Veuillez vérifier que la base de données existe et que les migrations ont été effectuées avant de réessayer.',
+ 'cms_link' => 'Retour à l\'accueil'
],
],
'partial' => [
@@ -46,7 +46,7 @@
'email_placeholder' => 'adresse e-mail',
'enter_new_password' => 'Saisir votre nouveau mot de passe',
'password_reset' => 'Réinitialiser le mot de passe',
- 'restore_success' => 'Un e-mail contenant les instructions a été envoyé à l’adresse e-mail de votre compte.',
+ 'restore_success' => 'Un e-mail contenant les instructions a été envoyé à l\'adresse e-mail de votre compte.',
'reset_success' => 'Mot de passe réinitialisé avec succès. Vous pouvez maintenant vous connecter.',
'reset_error' => 'Données de réinitialisation du mot de passe invalides. Veuillez réessayer !',
'reset_fail' => 'Réinitialisation du mot de passe impossible !',
@@ -68,7 +68,7 @@
'widget_columns_description' => 'La largeur du Widget doit être comprise entre 1 et 10.',
'widget_columns_error' => 'Veuillez définir la largeur du Widget avec un nombre compris entre 1 et 10.',
'columns' => '{1} colonne|[2,Inf] colonnes',
- 'widget_new_row_label' => 'Forcer l’affichage sur une nouvelle ligne',
+ 'widget_new_row_label' => 'Forcer l\'affichage sur une nouvelle ligne',
'widget_new_row_description' => 'Placer le Widget sur une nouvelle ligne.',
'widget_title_label' => 'Titre du Widget',
'widget_title_error' => 'Le titre du Widget est obligatoire.',
@@ -98,7 +98,7 @@
'widget_title_default' => 'Bienvenue',
'welcome_back_name' => 'Bienvenue sur :app, :name.',
'welcome_to_name' => 'Bienvenue sur :app, :name.',
- 'first_sign_in' => 'C’est la première fois que vous vous connectez.',
+ 'first_sign_in' => 'C\'est la première fois que vous vous connectez.',
'last_sign_in' => 'Votre dernière connexion remonte au',
'view_access_logs' => 'Consulter le journal des accès',
'nice_message' => 'Passez une agréable journée !',
@@ -107,7 +107,7 @@
'user' => [
'name' => 'Administrateur',
'menu_label' => 'Administrateurs',
- 'menu_description' => 'Gérer les utilisateurs, les groupes et les permissions de l’interface d’administration.',
+ 'menu_description' => 'Gérer les utilisateurs, les groupes et les permissions de l\'interface d\'administration.',
'list_title' => 'Gérer les administrateurs',
'new' => 'Créer un nouvel administrateur',
'login' => 'Identifiant',
@@ -125,9 +125,9 @@
'permissions' => 'Permissions',
'account' => 'Compte',
'superuser' => 'Super utilisateur',
- 'superuser_comment' => 'Donner à ce compte un niveau d’accès illimité à toutes les sections du système. Les super-utilisateurs peuvent ajouter et gérer les autres utilisateurs. ',
+ 'superuser_comment' => 'Donner à ce compte un niveau d\'accès illimité à toutes les sections du système. Les super-utilisateurs peuvent ajouter et gérer les autres utilisateurs. ',
'send_invite' => 'Envoyer une invitation par e-mail',
- 'send_invite_comment' => 'Envoyer une invitation aux utilisateurs par e-mail contenant l’identifiant et le mot de passe.',
+ 'send_invite_comment' => 'Envoyer une invitation aux utilisateurs par e-mail contenant l\'identifiant et le mot de passe.',
'delete_confirm' => 'Supprimer cet administrateur ?',
'return' => 'Retour à la liste des administrateurs',
'allow' => 'Autoriser',
@@ -147,11 +147,11 @@
'is_new_user_default_field_label' => 'Groupe par défaut',
'is_new_user_default_field_comment' => 'Ajouter les nouveaux administrateurs dans ce groupe par défaut.',
'code_field' => 'Code',
- 'code_comment' => 'Saisir un code d’accès unique si vous souhaitez accéder à ce groupe via l’API.',
+ 'code_comment' => 'Saisir un code d\'accès unique si vous souhaitez accéder à ce groupe via l\'API.',
'menu_label' => 'Groupes',
'list_title' => 'Gérer les groupes',
- 'new' => 'Ajouter un groupe d’administrateurs',
- 'delete_confirm' => 'Supprimer ce groupe d’administrateurs ?',
+ 'new' => 'Ajouter un groupe d\'administrateurs',
+ 'delete_confirm' => 'Supprimer ce groupe d\'administrateurs ?',
'return' => 'Retour à la liste des groupes',
'users_count' => 'Utilisateurs'
],
@@ -161,7 +161,7 @@
'name_comment' => 'Le nom est affiché dans la liste des rôles du formulaire de gestion des Administrateurs.',
'description_field' => 'Description',
'code_field' => 'Code',
- 'code_comment' => 'Saisir un code d’accès unique si vous souhaitez accéder à ce rôle via l’API.',
+ 'code_comment' => 'Saisir un code d\'accès unique si vous souhaitez accéder à ce rôle via l\'API.',
'menu_label' => 'Gérer les rôles',
'list_title' => 'Gérer les rôles',
'new' => 'Nouveau rôle',
@@ -170,7 +170,7 @@
'users_count' => 'Utilisateurs'
],
'preferences' => [
- 'not_authenticated' => 'Il n’y a aucun utilisateur authentifié pour lequel il est possible de charger ou modifier les préférences.'
+ 'not_authenticated' => 'Il n\'y a aucun utilisateur authentifié pour lequel il est possible de charger ou modifier les préférences.'
],
'trashed_hint_title' => 'Ce compte a été supprimé',
'trashed_hint_desc' => 'Ce compte a été supprimé et il sera impossible de se connecter avec. Pour le récupérer, cliquer sur l\'icône "Utilisateur" en bas à droite.',
@@ -178,14 +178,14 @@
'list' => [
'default_title' => 'Liste',
'search_prompt' => 'Rechercher…',
- 'no_records' => 'Il n’y a aucun résultat dans cette vue.',
- 'missing_model' => 'La liste utilisée dans la classe :class n’a pas de modèle défini.',
- 'missing_column' => 'Il n’y a pas de définition pour la colonne :columns.',
- 'missing_columns' => 'La liste utilisée dans la classe :class n’a pas de colonne de liste définie.',
+ 'no_records' => 'Il n\'y a aucun résultat dans cette vue.',
+ 'missing_model' => 'La liste utilisée dans la classe :class n\'a pas de modèle défini.',
+ 'missing_column' => 'Il n\'y a pas de définition pour la colonne :columns.',
+ 'missing_columns' => 'La liste utilisée dans la classe :class n\'a pas de colonne de liste définie.',
'missing_definition' => 'La liste utilisée ne contient de pas de colonne pour le champ ":field".',
'missing_parent_definition' => "Le behavior List ne contient pas de définition pour ':definition'.",
- 'behavior_not_ready' => 'La liste utilisée n’a pas été initialisée, vérifier que la méthode d’appel de makeLists() a été soumise au contrôleur.',
- 'invalid_column_datetime' => 'La valeur de la colonne ":column" n’est pas un objet DateTime, manque-t-il une référence dans la propriété \$dates du modèle ?',
+ 'behavior_not_ready' => 'La liste utilisée n\'a pas été initialisée, vérifier que la méthode d\'appel de makeLists() a été soumise au contrôleur.',
+ 'invalid_column_datetime' => 'La valeur de la colonne ":column" n\'est pas un objet DateTime, manque-t-il une référence dans la propriété \$dates du modèle ?',
'pagination' => 'Enregistrements affichés : :from-:to sur :total',
'first_page' => 'Première page',
'last_page' => 'Dernière page',
@@ -195,12 +195,12 @@
'updating' => 'Mise à jour…',
'loading' => 'Chargement…',
'setup_title' => 'Configuration de la liste',
- 'setup_help' => 'Cocher les colonnes qui doivent être affichées dans la liste. Il est possible de modifier l’ordre des colonnes en les glissant vers le haut ou le bas.',
- 'records_per_page' => 'Nombre d’enregistrements par page',
- 'records_per_page_help' => 'Choisir le nombre d’enregistrements à afficher. Note : un nombre d’enregistrements trop élevé sur une seule page peut réduire les performances.',
+ 'setup_help' => 'Cocher les colonnes qui doivent être affichées dans la liste. Il est possible de modifier l\'ordre des colonnes en les glissant vers le haut ou le bas.',
+ 'records_per_page' => 'Nombre d\'enregistrements par page',
+ 'records_per_page_help' => 'Choisir le nombre d\'enregistrements à afficher. Note : un nombre d\'enregistrements trop élevé sur une seule page peut réduire les performances.',
'check' => 'Sélectionner',
'delete_selected' => 'Supprimer la sélection',
- 'delete_selected_empty' => 'Il n’y a aucun enregistrement à supprimer',
+ 'delete_selected_empty' => 'Il n\'y a aucun enregistrement à supprimer',
'delete_selected_confirm' => 'Confirmer la suppression des enregistrements sélectionnés ?',
'delete_selected_success' => 'Les enregistrements ont été supprimés.',
'column_switch_true' => 'Oui',
@@ -215,7 +215,7 @@
'attachment_url' => 'Adresse URL du fichier joint',
'upload_file' => 'Télécharger le fichier',
'upload_error' => 'Erreur lors du téléchargement',
- 'remove_confirm' => 'Confirmer l’action ?',
+ 'remove_confirm' => 'Confirmer l\'action ?',
'remove_file' => 'Supprimer le fichier',
],
'repeater' => [
@@ -231,11 +231,11 @@
'restore_success' => ':name récuperé avec succès',
'delete_success' => ':name supprimé(e) avec succès',
'reset_success' => 'Réinitialisation terminée',
- 'missing_id' => 'L’ID de l’enregistrement du formulaire n’est pas précisé.',
- 'missing_model' => 'Le behavior formulaire utilisé dans la classe :class n’a pas de modèle défini.',
- 'missing_definition' => 'Le behavior formulaire utilisé n’a pas de champ pour ":field".',
- 'not_found' => 'Aucun enregistrement de formulaire ne correspond à l’ID :id.',
- 'action_confirm' => 'Confirmer l’action ?',
+ 'missing_id' => 'L\'ID de l\'enregistrement du formulaire n\'est pas précisé.',
+ 'missing_model' => 'Le behavior formulaire utilisé dans la classe :class n\'a pas de modèle défini.',
+ 'missing_definition' => 'Le behavior formulaire utilisé n\'a pas de champ pour ":field".',
+ 'not_found' => 'Aucun enregistrement de formulaire ne correspond à l\'ID :id.',
+ 'action_confirm' => 'Confirmer l\'action ?',
'create' => 'Créer',
'create_and_close' => 'Créer et fermer',
'creating' => 'Création en cours…',
@@ -268,10 +268,10 @@
'ok' => 'OK',
'or' => 'ou',
'confirm_tab_close' => 'Fermer cet onglet ? Les modifications réalisées seront perdues.',
- 'behavior_not_ready' => 'Le behavior formulaire n’a pas encore été initialisé, vérifier que la méthode initForm() est appelée par le contrôleur.',
+ 'behavior_not_ready' => 'Le behavior formulaire n\'a pas encore été initialisé, vérifier que la méthode initForm() est appelée par le contrôleur.',
'preview_no_files_message' => 'Les fichiers ne sont pas envoyés.',
'preview_no_media_message' => 'Aucun média sélectionné.',
- 'preview_no_record_message' => 'Il n’y a aucun enregistrement sélectionné.',
+ 'preview_no_record_message' => 'Il n\'y a aucun enregistrement sélectionné.',
'select' => 'Sélectionner',
'select_all' => 'tout sélectionner',
'select_none' => 'Ne rien sélectionner',
@@ -292,13 +292,13 @@
'select_page' => 'Sélectionnez une page...'
],
'relation' => [
- 'missing_config' => 'La behavior relation n’a pas de configuration pour ":config".',
- 'missing_definition' => 'La behavior relation n’a pas de définition pour le champ ":field".',
- 'missing_model' => 'La behavior relation utilisée dans la classe :class n’a pas de modèle défini.',
+ 'missing_config' => 'La behavior relation n\'a pas de configuration pour ":config".',
+ 'missing_definition' => 'La behavior relation n\'a pas de définition pour le champ ":field".',
+ 'missing_model' => 'La behavior relation utilisée dans la classe :class n\'a pas de modèle défini.',
'invalid_action_single' => 'Cette action ne peut être effectuée sur une relation unitaire.',
'invalid_action_multi' => 'Cette action ne peut être effectuée sur une relation multiple.',
'relationwidget_unsupported_type' => 'Le type de relation ":type" n\'est pas supporté par le widget Relation.',
- 'help' => 'Cliquer sur un élément pour l’ajouter',
+ 'help' => 'Cliquer sur un élément pour l\'ajouter',
'related_data' => 'Donnée :name liée',
'add' => 'Ajouter',
'add_selected' => 'Ajouter la sélection',
@@ -311,13 +311,13 @@
'create' => 'Créer',
'create_name' => 'Créer un(e) :name',
'update' => 'Mettre à jour',
- 'update_name' => 'Mise à jour d’un(e) :name',
+ 'update_name' => 'Mise à jour d\'un(e) :name',
'preview' => 'Aperçu',
- 'preview_name' => 'Aperçu d’un(e) :name',
+ 'preview_name' => 'Aperçu d\'un(e) :name',
'remove' => 'Retirer',
'remove_name' => 'Retirer un(e) :name',
'delete' => 'Supprimer',
- 'delete_name' => 'Suppression d’un(e) :name',
+ 'delete_name' => 'Suppression d\'un(e) :name',
'delete_confirm' => 'Êtes-vous sûr(e) ?',
'link' => 'Lier',
'link_name' => 'Lier un(e) :name',
@@ -327,27 +327,27 @@
],
'reorder' => [
'default_title' => 'Réorganiser les enregistrements',
- 'no_records' => 'Il n’y a aucun enregistrement à trier.',
+ 'no_records' => 'Il n\'y a aucun enregistrement à trier.',
],
'model' => [
'name' => 'Modèle',
- 'not_found' => 'Aucun modèle ":class" ne correspond à l’ID :id',
- 'missing_id' => 'Il manque l’ID de l’enregistrement.',
+ 'not_found' => 'Aucun modèle ":class" ne correspond à l\'ID :id',
+ 'missing_id' => 'Il manque l\'ID de l\'enregistrement.',
'missing_relation' => 'Le modèle ":class" ne contient pas de définition ":relation".',
'missing_method' => 'Le modèle ":class" ne contient pas de méthode ":method".',
'invalid_class' => 'Le modèle :model utilisé dans la classe :class est invalide, il doit hériter de la classe \Model.',
- 'mass_assignment_failed' => 'L’affectation de masse a échoué pour l’attribut ":attribute" du modèle.'
+ 'mass_assignment_failed' => 'L\'affectation de masse a échoué pour l\'attribut ":attribute" du modèle.'
],
'warnings' => [
'tips' => 'Astuces de configuration du système',
'tips_description' => 'Il y a des éléments à prendre en compte pour configurer le système correctement.',
'permissions' => 'PHP ne peut pas écrire dans le répertoire :name et ses sous-dossiers. Veuillez modifier les permissions en écriture du serveur web pour ce répertoire.',
- 'extension' => 'L’extension PHP :name n’est pas installée. Veuillez installer la librairie et activer l’extension.',
+ 'extension' => 'L\'extension PHP :name n\'est pas installée. Veuillez installer la librairie et activer l\'extension.',
'plugin_missing' => 'Le plugin :name est une dépendance mais n\'est pas installé. Veuillez installer le plugin.',
],
'editor' => [
- 'menu_label' => 'Préférences de l’éditeur de code',
- 'menu_description' => 'Personnaliser la configuration de l’éditeur de code, telle que la taille de la police ou la coloration syntaxique.',
+ 'menu_label' => 'Préférences de l\'éditeur de code',
+ 'menu_description' => 'Personnaliser la configuration de l\'éditeur de code, telle que la taille de la police ou la coloration syntaxique.',
'font_size' => 'Taille de la police',
'tab_size' => 'Taille de la tabulation',
'use_hard_tabs' => 'Indentation par tabulation',
@@ -363,8 +363,8 @@
'basic_autocompletion'=> 'Auto-complétion basique (Ctrl + Espace)',
'live_autocompletion'=> 'Auto-complétion en temps réel',
'enable_snippets'=> 'Activer les extraits de code (Tab)',
- 'display_indent_guides'=> 'Afficher les guides d’indentation',
- 'show_print_margin'=> 'Afficher les marges d’impression',
+ 'display_indent_guides'=> 'Afficher les guides d\'indentation',
+ 'show_print_margin'=> 'Afficher les marges d\'impression',
'mode_off' => 'Désactivé',
'mode_fluid' => 'Fluide',
'40_characters' => '40 caractères',
@@ -372,18 +372,18 @@
'theme' => 'Coloration syntaxique',
'markup_styles' => 'Styles du balisage',
'custom_styles' => 'Feuille de styles personnalisée',
- 'custom styles_comment' => 'Styles personnalisés à inclure dans l’editeur HTML.',
+ 'custom styles_comment' => 'Styles personnalisés à inclure dans l\'editeur HTML.',
'markup_classes' => 'Classes de style',
'paragraph' => 'Paragraphe',
'link' => 'Lien',
'table' => 'Tableau',
- 'table_cell' => 'Cellule d’un tableau',
+ 'table_cell' => 'Cellule d\'un tableau',
'image' => 'Image',
'label' => 'Libellé',
'class_name' => 'Nom de la classe',
'markup_tags' => 'Balises',
'allowed_empty_tags' => 'Autoriser les balises vides',
- 'allowed_empty_tags_comment' => 'Liste des balises qui ne sont pas supprimées lorsqu’elles sont vides.',
+ 'allowed_empty_tags_comment' => 'Liste des balises qui ne sont pas supprimées lorsqu\'elles sont vides.',
'allowed_tags' => 'Balises autorisées',
'allowed_tags_comment' => 'Liste des balises autorisées.',
'no_wrap' => 'Balises non encadrées',
@@ -404,23 +404,23 @@
],
'myaccount' => [
'menu_label' => 'Mon compte',
- 'menu_description' => 'Modifier les informations de votre compte comme le nom, l’adresse e-mail ou le mot de passe.',
+ 'menu_description' => 'Modifier les informations de votre compte comme le nom, l\'adresse e-mail ou le mot de passe.',
'menu_keywords' => 'identification de sécurité'
],
'branding' => [
- 'menu_label' => 'Personnaliser l’interface d’administration',
- 'menu_description' => 'Personnaliser l’interface d’administration comme le nom, les couleurs ou le logo.',
+ 'menu_label' => 'Personnaliser l\'interface d\'administration',
+ 'menu_description' => 'Personnaliser l\'interface d\'administration comme le nom, les couleurs ou le logo.',
'brand' => 'Marque',
'logo' => 'Logo',
- 'logo_description' => 'Envoyer un logo personnalisé à utiliser dans l’interface d’administration.',
- 'app_name' => 'Nom de l’application',
- 'app_name_description' => 'Ce nom est affiché comme titre dans l’interface d’administration.',
- 'app_tagline' => 'Slogan de l’application',
- 'app_tagline_description' => 'Ce slogan est affiché sur la page d’inscription à l’interface d’administration.',
+ 'logo_description' => 'Envoyer un logo personnalisé à utiliser dans l\'interface d\'administration.',
+ 'app_name' => 'Nom de l\'application',
+ 'app_name_description' => 'Ce nom est affiché comme titre dans l\'interface d\'administration.',
+ 'app_tagline' => 'Slogan de l\'application',
+ 'app_tagline_description' => 'Ce slogan est affiché sur la page d\'inscription à l\'interface d\'administration.',
'colors' => 'Couleurs',
'primary_color' => 'Couleur principale',
'secondary_color' => 'Couleur secondaire',
- 'accent_color' => 'Couleur d’accentuation',
+ 'accent_color' => 'Couleur d\'accentuation',
'styles' => 'Styles',
'custom_stylesheet' => 'Feuille de styles personnalisée',
'navigation' => 'Navigation',
@@ -430,7 +430,7 @@
'menu_mode_collapsed' => 'Replié',
],
'backend_preferences' => [
- 'menu_label' => 'Préférences d’administration',
+ 'menu_label' => 'Préférences d\'administration',
'menu_description' => 'Gérer les préférences de votre compte tel que la langue utilisée.',
'region' => 'Région',
'code_editor' => 'Éditeur de code',
@@ -440,9 +440,9 @@
'locale_comment' => 'Choisir une langue.'
],
'access_log' => [
- 'hint' => 'Ce journal affiche la liste des tentatives d’authentification réussies des administrateurs. Les données sont sauvegardées pendant :days jours.',
+ 'hint' => 'Ce journal affiche la liste des tentatives d\'authentification réussies des administrateurs. Les données sont sauvegardées pendant :days jours.',
'menu_label' => 'Journal des accès',
- 'menu_description' => 'Affiche la liste des authentifications réussies des utilisateurs de l’interface d’administration.',
+ 'menu_description' => 'Affiche la liste des authentifications réussies des utilisateurs de l\'interface d\'administration.',
'id' => 'ID',
'created_at' => 'Date et heure',
'type' => 'Type',
@@ -467,18 +467,18 @@
'match_columns' => '2. Faire correspondre les colonnes du fichier avec les champs du modèle de données',
'file_columns' => 'Colonnes du fichier',
'database_fields' => 'Champs de la base de données',
- 'set_import_options' => '3. Fixer les options d’importation',
- 'export_output_format' => '1. Format de sortie de l’export',
+ 'set_import_options' => '3. Fixer les options d\'importation',
+ 'export_output_format' => '1. Format de sortie de l\'export',
'file_format' => 'Format du fichier',
'standard_format' => 'Format Standard',
'custom_format' => 'Format Personnalisé',
'delimiter_char' => 'Caractère séparateur',
- 'enclosure_char' => 'Caractère d’encadrement',
- 'escape_char' => 'Caractère d’échappement',
+ 'enclosure_char' => 'Caractère d\'encadrement',
+ 'escape_char' => 'Caractère d\'échappement',
'select_columns' => '2. Choisissez les colonnes à exporter',
'column' => 'Colonne',
'columns' => 'Colonnes',
- 'set_export_options' => '3. Définir les options d’exportation',
+ 'set_export_options' => '3. Définir les options d\'exportation',
'show_ignored_columns' => 'Voir les colonnes ignorées',
'auto_match_columns' => 'Correspondance automatique des colonnes',
'created' => 'Créés',
@@ -487,28 +487,28 @@
'warnings' => 'Alertes',
'errors' => 'Erreurs',
'skipped_rows' => 'Lignes ignorées',
- 'import_progress' => 'Progression de l’import',
+ 'import_progress' => 'Progression de l\'import',
'processing' => 'Traitement',
- 'import_error' => 'Erreur d’import',
+ 'import_error' => 'Erreur d\'import',
'upload_valid_csv' => 'Veuillez envoyer un fichier CSV valide.',
'drop_column_here' => 'Déposez les colonnes ici...',
'ignore_this_column' => 'Ignorer cette colonne',
- 'processing_successful_line1' => 'Le processus d’export du fichier s’est terminé avec succès !',
+ 'processing_successful_line1' => 'Le processus d\'export du fichier s\'est terminé avec succès !',
'processing_successful_line2' => 'Le navigateur devrait automatiquement vous rediriger vers le téléchargement du fichier.',
- 'export_progress' => 'Progression de l’export',
- 'export_error' => 'Erreur d’export',
+ 'export_progress' => 'Progression de l\'export',
+ 'export_error' => 'Erreur d\'export',
'column_preview' => 'Prévisualisation des colonnes',
'file_not_found_error' => 'Fichier non trouvé',
'empty_error' => 'Il n‘y a aucune donnée à exporter',
'empty_import_columns_error' => 'Veuillez indiquer quelques colonnes à importer.',
- 'match_some_column_error' => 'Veuillez d’abord faire correspondre quelques colonnes.',
+ 'match_some_column_error' => 'Veuillez d\'abord faire correspondre quelques colonnes.',
'required_match_column_error' => 'Veuillez faire correspondre la colonne obligatoire :label.',
'empty_export_columns_error' => 'Veuillez indiquer quelques colonnes à exporter.',
- 'behavior_missing_uselist_error' => 'Vous devez implémenter le behavior ListController avec l’option d’export "useList" activée.',
+ 'behavior_missing_uselist_error' => 'Vous devez implémenter le behavior ListController avec l\'option d\'export "useList" activée.',
'missing_model_class_error' => 'Veuillez préciser la propriété modelClass pour :type',
'missing_column_id_error' => 'Identifiant de colonne manquant',
'unknown_column_error' => 'Colonne inconnue',
- 'encoding_not_supported_error' => 'L’encodage de votre fichier source n’est pas reconnu. Veuillez sélectionner le format d’import personnalisé avec l’encodage adapté pour importer votre fichier.',
+ 'encoding_not_supported_error' => 'L\'encodage de votre fichier source n\'est pas reconnu. Veuillez sélectionner le format d\'import personnalisé avec l\'encodage adapté pour importer votre fichier.',
'encoding_format' => 'Encodage du fichier',
'encodings' => [
'utf_8' => 'UTF-8',
@@ -521,7 +521,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, arabe)',
'iso_8859_7' => 'ISO-8859-7 (Latin, grec)',
'iso_8859_8' => 'ISO-8859-8 (Latin, hébreu)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, turc)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, turc)',
'iso_8859_10' => 'ISO-8859-10 (Latin-6, nordique)',
'iso_8859_11' => 'ISO-8859-11 (Latin, thaï)',
'iso_8859_13' => 'ISO-8859-13 (Latin-7, balte)',
@@ -574,7 +574,7 @@
'no_files_found' => 'Aucun fichier trouvé.',
'delete_empty' => 'Veuillez sélectionner les éléments à supprimer.',
'delete_confirm' => 'Confirmer la suppression de ces éléments ?',
- 'error_renaming_file' => 'Erreur lors du renommage de l’élément.',
+ 'error_renaming_file' => 'Erreur lors du renommage de l\'élément.',
'new_folder_title' => 'Nouveau répertoire',
'folder_name' => 'Nom du répertoire',
'error_creating_folder' => 'Erreur lors de la création du répertoire',
@@ -588,7 +588,7 @@
'insert' => 'Insérer',
'crop_and_insert' => 'Rogner et insérer',
'select_single_image' => 'Veuillez sélectionner une seule image.',
- 'selection_not_image' => 'L’élément sélectionné n’est pas une image.',
+ 'selection_not_image' => 'L\'élément sélectionné n\'est pas une image.',
'restore' => 'Annuler tous les changements',
'resize' => 'Redimensionner…',
'selection_mode_normal' => 'Normal',
@@ -597,8 +597,8 @@
'height' => 'Hauteur',
'width' => 'Largeur',
'selection_mode' => 'Mode de sélection',
- 'resize_image' => 'Redimensionner l’image',
- 'image_size' => 'Taille de l’image :',
+ 'resize_image' => 'Redimensionner l\'image',
+ 'image_size' => 'Taille de l\'image :',
'selected_size' => 'Sélectionnée :',
'rename_popup_title' => 'Renommer',
'rename_new_name' => 'Nouveau nom',
diff --git a/modules/backend/lang/hu/lang.php b/modules/backend/lang/hu/lang.php
index df225536d..f1b3f15cd 100644
--- a/modules/backend/lang/hu/lang.php
+++ b/modules/backend/lang/hu/lang.php
@@ -559,7 +559,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arab)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Görög)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Héber)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Török)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Török)',
'iso_8859_10' => 'ISO-8859-10 (Latin-6, Északi)',
'iso_8859_11' => 'ISO-8859-11 (Latin, Thai)',
'iso_8859_13' => 'ISO-8859-13 (Latin-7, Balti)',
diff --git a/modules/backend/lang/it/lang.php b/modules/backend/lang/it/lang.php
index 56d8263f9..0bc6974a8 100644
--- a/modules/backend/lang/it/lang.php
+++ b/modules/backend/lang/it/lang.php
@@ -287,9 +287,9 @@
'related_data' => 'Dati :name correlati',
'add' => 'Aggiungi',
'add_selected' => 'Aggiungi selezionati',
- 'add_a_new' => 'Aggiungi nuovo :name',
+ 'add_a_new' => 'Aggiungi :name',
'link_selected' => 'Collega selezionati',
- 'link_a_new' => 'Collega nuovo :name',
+ 'link_a_new' => 'Collega :name',
'cancel' => 'Annulla',
'close' => 'Chiudi',
'add_name' => 'Aggiungi :name',
@@ -513,7 +513,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latinp, Arabo)',
'iso_8859_7' => 'ISO-8859-7 (Latino, Greco)',
'iso_8859_8' => 'ISO-8859-8 (Latino, Ebraico)',
- 'iso_8859_0' => 'ISO-8859-9 (Latino-5, Turco)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latino-5, Turco)',
'iso_8859_10' => 'ISO-8859-10 (Latino-6, Nordico)',
'iso_8859_11' => 'ISO-8859-11 (Latino, Thai)',
'iso_8859_13' => 'ISO-8859-13 (Latino-7, Baltico)',
diff --git a/modules/backend/lang/ja/lang.php b/modules/backend/lang/ja/lang.php
index 42d385e18..e81b0a53a 100644
--- a/modules/backend/lang/ja/lang.php
+++ b/modules/backend/lang/ja/lang.php
@@ -2,26 +2,45 @@
return [
'auth' => [
- 'title' => 'システム管理者領域'
+ 'title' => 'システム管理者領域',
+ 'invalid_login' => '入力された内容は、存在しませんでした。再度ご確認の上、お試しください。',
],
'field' => [
'invalid_type' => '無効なフィールドタイプ :type が使用されています。',
- 'options_method_not_exists' => 'モデルクラスの:modelは、":field"フォームフィールドのためにオプションを返す、:method()メソッドを定義しなくてはなりません。',
+ 'options_method_not_exists' => "モデルクラスの:modelは、':field'フォームフィールドのためにオプションを返す、:method()メソッドを定義しなくてはなりません。",
+ 'options_method_invalid_model' => "属性 ':field' は有効なモデルに解決されません。モデルクラス :model のオプションメソッドを明示的に指定してみてください。",
+ 'options_static_method_invalid_value' => "class の静的メソッド ':method()' が有効なオプション配列を返しませんでした。",
+ 'colors_method_not_exists' => "モデルクラス :model は、フォームフィールド ':field' の html カラー HEX コードを返すメソッド :method() を定義する必要があります。",
],
'widget' => [
'not_registered' => "':name'は、ウィジット名として登録されていません。",
'not_bound' => "ウィジットクラス名の':name'は、コントローラーと結び付けられていません。",
],
'page' => [
- 'untitled' => "タイトル無し",
+ 'untitled' => 'タイトル無し',
+ '404' => [
+ 'label' => 'Page Not Found(ページが見つかりません)。',
+ 'help' => '検索しましたが、要求されたURLは見つかりませんでした。おそらく他のものをお探しだったのでしょう。',
+ 'back_link' => '前のページに戻る',
+ ],
'access_denied' => [
- 'label' => "アクセスが拒否されました",
- 'help' => "このページを表示するために必要な権限がありません。",
- 'cms_link' => "CMSのバックエンドに行く",
+ 'label' => 'アクセスが拒否されました',
+ 'help' => 'このページを表示するために必要な権限がありません。',
+ 'cms_link' => 'CMSのバックエンドに行く',
+ ],
+ 'no_database' => [
+ 'label' => 'データベースがありません',
+ 'help' => 'バックエンドにアクセスするためにデータベースが必要です。再試行する前に、データベースが設定され、移行されていることを確認してください。',
+ 'cms_link' => 'ホームページに戻る',
],
],
'partial' => [
'not_found_name' => "':name'パーシャルは見つかりません。",
+ 'invalid_name' => "無効なパーシャル名: :name",
+ ],
+ 'ajax_handler' =>[
+ 'invalid_name' => "無効なAJAXハンドラ名: :name",
+ 'not_found' => "AJAXハンドラ ':name' が見つかりませんでした。",
],
'account' => [
'sign_out' => 'ログアウト',
@@ -30,20 +49,32 @@
'restore' => '元に戻す',
'login_placeholder' => 'ユーザー名',
'password_placeholder' => 'パスワード',
- 'forgot_password' => "パスワードを忘れましたか?",
- 'enter_email' => "メールアドレスを入力してください",
- 'enter_login' => "ユーザー名を入力してください",
- 'email_placeholder' => "メールアドレス",
- 'enter_new_password' => "新しいパスワードを入力してください",
- 'password_reset' => "パスワードリセット",
- 'restore_success' => "パスワードを元に戻すための手順を説明したメールを送信しました。",
- 'reset_success' => "パスワードがリセットされました。",
- 'reset_error' => "間違ったパスワードリセットデータが送信されました。再実行してください。",
- 'reset_fail' => "パスワードをリセットできませんでした。",
+ 'forgot_password' => 'パスワードを忘れましたか?',
+ 'enter_email' => 'メールアドレスを入力してください',
+ 'enter_login' => 'ユーザー名を入力してください',
+ 'email_placeholder' => 'メールアドレス',
+ 'enter_new_password' => '新しいパスワードを入力してください',
+ 'password_reset' => 'パスワードリセット',
+ 'restore_success' => 'パスワードを元に戻すための手順を説明したメールを送信しました。',
+ 'reset_success' => 'パスワードがリセットされました。',
+ 'reset_error' => '間違ったパスワードリセットデータが送信されました。再実行してください。',
+ 'reset_fail' => 'パスワードをリセットできませんでした。',
'apply' => '適用',
'cancel' => 'キャンセル',
'delete' => '削除',
'ok' => 'OK',
+ 'impersonate' => 'このユーザーに偽装する',
+ 'impersonate_confirm' => '本当にこのユーザーになりすましますか?ログアウトすれば、元の状態に戻せます。',
+ 'impersonate_success' => 'あなたは今、このユーザーになりすましています',
+ 'impersonate_working' => 'なりすまし中...',
+ 'impersonating' => "あなたは一時的に :impersonatee としてログインしています。ログからはまだ :impersonator として識別することができます。",
+ 'stop_impersonating' => 'なりすましをやめる',
+ 'unsuspend' => '利用停止',
+ 'unsuspend_confirm' => '本当にこのユーザーの利用停止を解除しますか?',
+ 'unsuspend_success' => 'ユーザーが利用可能になりました。',
+ 'unsuspend_working' => '利用停止解除中...',
+ 'signed_in_as' => "サインイン名 :full_name",
+ 'remember_me' => 'ログインしたままにする',
],
'dashboard' => [
'menu_label' => 'ダッシュボード',
@@ -61,9 +92,37 @@
'widget_new_row_description' => 'ウィジェットを次の行に配置します。',
'widget_title_label' => 'ウィジェットタイトル',
'widget_title_error' => 'ウィジェットタイトルを指定してください。',
+ 'manage_widgets' => 'ウィジェットの管理',
+ 'reset_layout' => 'レイアウトをリセット',
+ 'reset_layout_confirm' => 'レイアウトをデフォルトに戻す?',
+ 'reset_layout_success' => 'レイアウトがリセットされました',
+ 'make_default' => 'デフォルトにする',
+ 'make_default_confirm' => '現在のレイアウトをデフォルトに設定しますか?',
+ 'make_default_success' => '現在のレイアウトがデフォルトになりました。',
+ 'collapse_all' => 'すべて閉じる',
+ 'expand_all' => 'すべて展開する',
'status' => [
'widget_title_default' => 'システム状態',
- 'update_available' => '{0}個のアップデートが見つかりました。|{1} 個のアップデートが見つかりました。|[2,Inf] 個のアップデートが見つかりました。'
+ 'update_available' => '{0}個のアップデートが見つかりました。|{1} 個のアップデートが見つかりました。|[2,Inf] 個のアップデートが見つかりました。',
+ 'updates_pending' => 'ソフトウェアの更新が保留中',
+ 'updates_nil' => 'ソフトウェアは最新です',
+ 'updates_link' => 'アップデート',
+ 'warnings_pending' => 'いくつかの問題は注意が必要です',
+ 'warnings_nil' => '表示する警告がありません',
+ 'warnings_link' => '表示',
+ 'core_build' => 'システムビルド',
+ 'event_log' => 'イベントログ',
+ 'request_log' => 'リクエストログ',
+ 'app_birthday' => 'オンライン以来',
+ ],
+ 'welcome' => [
+ 'widget_title_default' => 'ようこそ',
+ 'welcome_back_name' => 'お帰りなさい、:app, :name.',
+ 'welcome_to_name' => 'ようこそ、:app, :name.へ',
+ 'first_sign_in' => '今回が初めてのログインです',
+ 'last_sign_in' => '前回のサインインは',
+ 'view_access_logs' => 'アクセスログを見る',
+ 'nice_message' => '良い一日を!!',
],
],
'user' => [
@@ -72,19 +131,19 @@
'menu_description' => 'バックエンドの管理ユーザーとグループ、権限の管理。',
'list_title' => 'システム管理者の管理',
'new' => '新規システム管理者',
- 'login' => "ログイン",
- 'first_name' => "名",
- 'last_name' => "姓",
- 'full_name' => "姓名",
- 'email' => "メールアドレス",
- 'groups' => "グループ",
- 'groups_comment' => "このユーザーが所属するグループを指定してください。",
- 'avatar' => "アバター",
- 'password' => "パスワード",
- 'password_confirmation' => "パスワード確認",
+ 'login' => 'ログイン',
+ 'first_name' => '名',
+ 'last_name' => '姓',
+ 'full_name' => '姓名',
+ 'email' => 'メールアドレス',
+ 'groups' => 'グループ',
+ 'groups_comment' => 'このユーザーが所属するグループを指定してください。',
+ 'avatar' => 'アバター',
+ 'password' => 'パスワード',
+ 'password_confirmation' => 'パスワード確認',
'permissions' => '権限',
- 'superuser' => "スーパーユーザー",
- 'superuser_comment' => "全領域へのアクセスをこのユーザーに許可する場合、ボックスをチェックしてください。",
+ 'superuser' => 'スーパーユーザー',
+ 'superuser_comment' => '全領域へのアクセスをこのユーザーに許可する場合、ボックスをチェックしてください。',
'send_invite' => 'メールにより招待送信',
'send_invite_comment' => 'このユーザーに、メールで招待状を送る場合、ボックスをチェックしてください。',
'delete_confirm' => 'この管理者を本当に削除しますか?',
@@ -92,11 +151,23 @@
'allow' => '許可',
'inherit' => '継承',
'deny' => '拒否',
+ 'role_field' => 'ロール',
+ 'role_comment' => 'ロールはユーザー権限を定義し、権限タブでユーザーレベルで上書きすることができます',
+ 'account' => 'アカウント',
+ 'activated' => 'アクティベートされた',
+ 'last_login' => '最終ログイン',
+ 'created_at' => '作成日',
+ 'updated_at' => '更新日',
+ 'deleted_at' => '削除日',
+ 'show_deleted' => '削除済み表示',
'group' => [
'name' => 'グループ',
'name_field' => '名前',
+ 'name_comment' => 'この名前は、管理者フォームのグループリストに表示されます。',
'description_field' => '説明',
'is_new_user_default_field' => 'デフォルトで新しいシステム管理者をこのグループに追加する。',
+ 'is_new_user_default_field_label' => 'デフォルトグループ',
+ 'is_new_user_default_field_comment' => '新しい管理者をこのグループにデフォルトで追加する',
'code_field' => 'コード',
'code_comment' => 'もしあなたがAPIでアクセスしたい場合は、ユニークなコードを入力してください。',
'menu_label' => 'グループ',
@@ -104,10 +175,27 @@
'new' => '新規グループ',
'delete_confirm' => '本当にこの管理者グループを削除しますか?',
'return' => 'グループリストへ戻る',
+ 'users_count' => 'ユーザー数',
+ ],
+ 'role' => [
+ 'name' => 'ロール',
+ 'name_field' => '名前',
+ 'name_comment' => 'この名前は、管理者フォームのロールリストに表示されます。',
+ 'description_field' => '説明',
+ 'code_field' => 'コード',
+ 'code_comment' => 'APIでロールオブジェクトにアクセスする場合は、一意のコードを入力してください。',
+ 'menu_label' => 'ロール管理',
+ 'list_title' => 'ロール管理',
+ 'new' => '新しいロール',
+ 'delete_confirm' => 'この管理者ロールを削除しますか?',
+ 'return' => 'ロール一覧に戻る',
+ 'users_count' => 'ユーザー一覧',
],
'preferences' => [
- 'not_authenticated' => '設定を読み込み/保存する、認証されたユーザーが存在していません。'
- ]
+ 'not_authenticated' => '設定を読み込み/保存する、認証されたユーザーが存在していません。',
+ ],
+ 'trashed_hint_title' => 'このアカウントは削除されました',
+ 'trashed_hint_desc' => 'このアカウントは削除され、このアカウントでサインインすることはできません。復元するには、右下のユーザー復元アイコンをクリックします。',
],
'list' => [
'default_title' => 'リスト',
@@ -117,31 +205,56 @@
'missing_column' => ':columnsに対する、カラム定義がありません。',
'missing_columns' => ':classクラスの中のリストには、リストするカラムが定義されていません。',
'missing_definition' => "リストビヘイビアーは、':field'に対するカラムを持っていません。",
+ 'missing_parent_definition' => "リストビヘイビアに':definition'の定義がありません。",
'behavior_not_ready' => 'リストビヘイビアーは初期化されていません。コントローラーで、makeLists()を呼び出しているか確認してください。',
'invalid_column_datetime' => "カラムの値 ':column' はDateTimeオブジェクトではありません。モデル内の \$dates に指定していますか?",
'pagination' => '表示中のレコード: :from-:to / :total',
+ 'first_page' => '初めのページ',
+ 'last_page' => '最後のページ',
'prev_page' => '前のページ',
'next_page' => '次のページ',
'loading' => 'ロード中...',
+ 'refresh' => 'リフレッシュ',
+ 'updating' => '更新中',
'setup_title' => 'リストのセットアップ',
'setup_help' => 'リストの表示したいカラムをチェックボックスで選択してください。カラムの位置は上下にドラッグして変更できます。',
'records_per_page' => 'ページ中のレコード数',
- 'records_per_page_help' => 'ページあたりの表示レコード数を選択してください。1ページにたくさん表示するとパフォーマンスに影響があるので留意してください。'
+ 'records_per_page_help' => 'ページあたりの表示レコード数を選択してください。1ページにたくさん表示するとパフォーマンスに影響があるので留意してください。',
+ 'check' => 'チェック',
+ 'delete_selected' => '選択した項目を削除する',
+ 'delete_selected_empty' => '削除する選択されたレコードはありません。',
+ 'delete_selected_confirm' => '選択したレコードを削除しますか?',
+ 'delete_selected_success' => '選択したレコードを削除しました。',
+ 'column_switch_true' => 'Yes',
+ 'column_switch_false' => 'No',
],
'fileupload' => [
'attachment' => '添付',
'help' => 'この添付のタイトルと説明を追加します。',
'title_label' => 'タイトル',
- 'description_label' => '説明'
+ 'description_label' => '説明',
+ 'default_prompt' => '%s をクリックするか、ここにファイルをドラッグしてアップロードしてください。',
+ 'attachment_url' => '添付ファイルURL',
+ 'upload_file' => 'ファイルのアップロード',
+ 'upload_error' => 'アップロードエラー',
+ 'remove_confirm' => '本当によろしいですか?',
+ 'remove_file' => 'ファイルを削除する',
+ ],
+ 'repeater' => [
+ 'add_new_item' => '新規アイテム',
+ 'min_items_failed' => ':name は最低でも :min の項目が必要ですが、:item しか提供されていません。',
+ 'max_items_failed' => ':name は :max items までしか許可しないが、:item は提供された。',
],
'form' => [
- 'create_title' => "新規 :name",
- 'update_title' => "編集 :name",
- 'preview_title' => "プレビュー :name",
+ 'create_title' => '新規 :name',
+ 'update_title' => '編集 :name',
+ 'preview_title' => 'プレビュー :name',
'create_success' => ':nameを作成しました。',
'update_success' => ':nameを更新しました。',
'delete_success' => ':nameを削除しました。',
- 'missing_id' => "フォームのレコードIDが指定されていません。",
+ 'restore_success' => ':nameを復元しました。',
+ 'reset_success' => 'リセット完了しました。',
+ 'missing_id' => 'フォームのレコードIDが指定されていません。',
'missing_model' => ':classクラスで使用している、フォームビヘイビアーは、モデル定義を持っていません。',
'missing_definition' => "フォームビヘイビアーは、':field'フィールドを持っていません。",
'not_found' => 'IDが:idのフォームレコードが見つかりません。',
@@ -156,7 +269,12 @@
'saving_name' => ':name を保存中...',
'delete' => '削除',
'deleting' => '削除中...',
+ 'confirm_delete' => 'レコードを削除しますか?',
+ 'confirm_delete_multiple' => '選択したレコードを削除しますか?',
'deleting_name' => ':name を削除中...',
+ 'restore' => '復元',
+ 'restoring' => '復元中...',
+ 'confirm_restore' => '本当にこのレコードを復元するのですか?',
'reset_default' => '初期値にリセット',
'resetting' => 'リセット',
'resetting_name' => ':name を初期値にリセット中...',
@@ -169,60 +287,79 @@
'close' => '閉じる',
'confirm' => '確認',
'reload' => 'リロード',
+ 'complete' => '完了',
'ok' => 'OK',
'or' => 'または',
'confirm_tab_close' => '本当にタブを閉じますか? 保存されていない変更は消えてしまいます。',
'behavior_not_ready' => 'フォームビヘイビアーは初期化されていません。コントローラーでinitForm()を呼び出しているか確認してください。',
'preview_no_files_message' => 'ファイルはアップロードされません。',
+ 'preview_no_media_message' => '選択されたメディアはありません。',
+ 'preview_no_record_message' => '選択されたレコードはありません',
'select' => '選択',
'select_all' => 'すべて選択',
'select_none' => 'どれも選択しない',
'select_placeholder' => '選択してください',
'insert_row' => '行を挿入',
+ 'insert_row_below' => '下の行を挿入する',
'delete_row' => '行を削除',
'concurrency_file_changed_title' => 'ファイルは変更されていません。',
'concurrency_file_changed_description' => '編集中のファイルが他のユーザーにより変更されました。リロードして加えた変更を破棄するか、そのまま上書きできます。',
+ 'return_to_list' => 'リストに戻る',
+ ],
+ 'recordfinder' => [
+ 'find_record' => 'レコードの検索',
+ 'invalid_model_class' => 'レコードファインダーのモデルクラス":modelClass "が無効です。',
+ 'cancel' => 'キャンセル',
+ ],
+ 'pagelist' => [
+ 'page_link' => 'ページリンク',
+ 'select_page' => 'ページを選択してください...',
],
'relation' => [
'missing_config' => "リレーションビヘイビアは、':config'に対する設定を持っていません。",
'missing_definition' => "リレーションビヘイビアは、':field'フィールドに対する定義を持っていません。",
- 'missing_model' => ":classクラスで使用している、リレーションビヘイビアは、モデル定義を持っていません。",
- 'invalid_action_single' => "このアクションは、単一リレーションでは実行できません。",
- 'invalid_action_multi' => "このアクションは、複数リレーションでは実行できません。",
+ 'missing_model' => ':classクラスで使用している、リレーションビヘイビアは、モデル定義を持っていません。',
+ 'invalid_action_single' => 'このアクションは、単一リレーションでは実行できません。',
+ 'invalid_action_multi' => 'このアクションは、複数リレーションでは実行できません。',
+ 'relationwidget_unsupported_type' => 'リレーションタイプ「:type」はリレーションウィジェットではサポートされていません',
'help' => '項目をクリックすると追加されます。',
- 'related_data' => "関連するデータ :name",
- 'add' => "追加",
- 'add_name' => "追加 :name",
- 'add_selected' => "追加は選択されています",
- 'add_a_new' => "新しい追加 :name",
- 'link_selected' => "リンクは選択されています",
- 'link_a_new' => "新しいリンク :name",
- 'cancel' => "キャンセル",
- 'close' => "閉じる",
- 'create' => "作成",
- 'create_name' => "作成 :name",
- 'update' => "更新",
- 'update_name' => "更新 :name",
- 'preview' => "プレビュー",
- 'preview_name' => "プレビュー :name",
- 'remove' => "削除",
- 'remove_name' => "削除 :name",
- 'delete' => "削除",
- 'delete_name' => "削除 :name",
- 'delete_confirm' => "削除していいですか?",
- 'link' => "リンク",
- 'link_name' => "リンク :name",
- 'unlink' => "リンク解除",
- 'unlink_name' => "リンク解除 :name",
- 'unlink_confirm' => "リンクを解除していいですか?",
+ 'related_data' => '関連するデータ :name',
+ 'add' => '追加',
+ 'add_name' => '追加 :name',
+ 'add_selected' => '追加は選択されています',
+ 'add_a_new' => '新しい追加 :name',
+ 'link_selected' => 'リンクは選択されています',
+ 'link_a_new' => '新しいリンク :name',
+ 'cancel' => 'キャンセル',
+ 'close' => '閉じる',
+ 'create' => '作成',
+ 'create_name' => '作成 :name',
+ 'update' => '更新',
+ 'update_name' => '更新 :name',
+ 'preview' => 'プレビュー',
+ 'preview_name' => 'プレビュー :name',
+ 'remove' => '削除',
+ 'remove_name' => '削除 :name',
+ 'delete' => '削除',
+ 'delete_name' => '削除 :name',
+ 'delete_confirm' => '削除していいですか?',
+ 'link' => 'リンク',
+ 'link_name' => 'リンク :name',
+ 'unlink' => 'リンク解除',
+ 'unlink_name' => 'リンク解除 :name',
+ 'unlink_confirm' => 'リンクを解除していいですか?',
+ ],
+ 'reorder' => [
+ 'default_title' => 'レコードの並べ替え',
+ 'no_records' => 'ソート可能なレコードはありません。',
],
'model' => [
- 'name' => "モデル",
+ 'name' => 'モデル',
'not_found' => "IDが:idの、':class'モデルは見つかりません。",
- 'missing_id' => "モデルレコードを探すためのIDが、指定されていません。",
+ 'missing_id' => 'モデルレコードを探すためのIDが、指定されていません。',
'missing_relation' => "':class'モデルは、':relation'の定義を持っていません。",
'missing_method' => "モデル ':class' にメソッド ':method' は定義されていません。",
- 'invalid_class' => ":classクラスで使用している、:modelモデルは正しくありません。\Modelクラスを継承してください。",
+ 'invalid_class' => ':classクラスで使用している、:modelモデルは正しくありません。\Modelクラスを継承してください。',
'mass_assignment_failed' => "モデル属性':attribute'の一括設定に失敗しました。",
],
'warnings' => [
@@ -230,31 +367,86 @@
'tips_description' => 'あなたが、システムを適切に設定するために注意を払う必要がある問題があります。',
'permissions' => 'ディレクトリ":name"またはそのサブディレクトリは、PHPから書き込みできません。このディレクトリのパーミッションを設定してください。',
'extension' => 'PHP拡張":name"はインストールされていません。PHP拡張をインストールして有効にしてください。',
+ 'plugin_missing' => 'プラグイン :name は依存関係にありますが、インストールされていません。このプラグインをインストールしてください。',
+ 'debug' => 'デバッグモードが有効です。これは、実稼働環境でのインストールにはお勧めしません。',
+ 'decompileBackendAssets' => '現在、Backendのアセットがデコンパイルされています。これは、実稼働環境でのインストールにはお勧めしません。',
+ 'default_backend_user' => 'デフォルトのログイン情報を持つユーザー(admin / admin@domain.tld)が見つかりました。システムを保護するために、ユーザー名および/またはメールアドレスを変更してください。',
],
'editor' => [
'menu_label' => 'エディタ',
'menu_description' => 'コードエディタ設定の管理',
+ 'preview' => 'プレビュー',
'font_size' => 'フォントサイズ',
'tab_size' => 'タブサイズ',
'use_hard_tabs' => 'インデントにタブを使用する',
'code_folding' => 'コード折りたたみ',
+ 'code_folding_begin' => 'マーク開始',
+ 'code_folding_begin_end' => '開始と終了のマーク',
+ 'autocompletion' => 'オートコンプリート',
'word_wrap' => 'ワードラップ',
'highlight_active_line' => '選択行のハイライト',
+ 'auto_closing' => 'タグを自動で閉じる',
'show_invisibles' => '見えない文字を表示する',
'show_gutter' => '行番号を表示する',
+ 'basic_autocompletion' => '基本的なオートコンプリート(Ctrl + Space)',
+ 'live_autocompletion' => 'ライブオートコンプリート',
+ 'enable_snippets' => 'コードスニペットを有効にする(タブ)',
+ 'display_indent_guides' => 'インデントガイドを表示する',
+ 'show_print_margin' => 'プリントマージンを表示する',
+ 'mode_off' => 'オフ',
+ 'mode_fluid' => 'Fluid',
+ '40_characters' => '40文字',
+ '80_characters' => '80文字',
'theme' => 'カラーテーマ',
+ 'markup_styles' => 'マークアップ・スタイル',
+ 'custom_styles' => 'カスタムスタイルシート',
+ 'custom styles_comment' => 'HTMLエディタに含めるカスタムスタイル',
+ 'markup_classes' => 'マークアップクラス',
+ 'paragraph' => '段落',
+ 'link' => 'リンク',
+ 'table' => 'テーブル',
+ 'table_cell' => 'テーブルセル',
+ 'image' => 'イメージ',
+ 'label' => 'ラベル',
+ 'class_name' => 'クラス名',
+ 'markup_tags' => 'マークアップタグ',
+ 'markup_tag' => 'マークアップタグ',
+ 'allowed_empty_tags' => '許可されたからのタグ',
+ 'allowed_empty_tags_comment' => '内部に内容がない場合に削除されないタグのリスト',
+ 'allowed_tags' => '許可されたタグ',
+ 'allowed_tags_comment' => '許可されたタグのリスト',
+ 'allowed_attributes' => '許可された属性',
+ 'allowed_attributes_comment' => '許可された属性のリスト',
+ 'no_wrap' => 'タグを折り返さない',
+ 'no_wrap_comment' => 'ブロックタグの内側にラップしてはいけないタグのリスト',
+ 'remove_tags' => 'タグを削除する',
+ 'remove_tags_comment' => '内容とともに削除されるタグのリスト',
+ 'line_breaker_tags' => '改行タグ',
+ 'line_breaker_tags_comment' => '改行要素の間に配置するためのタグのリストです。',
+ 'toolbar_options' => 'ツールバーオプション',
+ 'toolbar_buttons' => 'ツールバーボタン',
+ 'toolbar_buttons_comment' => 'リッチエディタにデフォルトで表示されるツールバーボタン',
+ 'toolbar_buttons_preset' => 'プリセットのツールバーボタン構成を挿入します',
+ 'toolbar_buttons_presets' => [
+ 'default' => 'デフォルト',
+ 'minimal' => '最小',
+ 'full' => 'フル',
+ ],
+ 'paragraph_formats' => '段落の書式',
+ 'paragraph_formats_comment' => '段落形式のドロップダウンに表示されるオプション',
+
],
'tooltips' => [
- 'preview_website' => 'Webサイトをプレビューする'
+ 'preview_website' => 'Webサイトをプレビューする',
],
'mysettings' => [
'menu_label' => 'マイ設定',
- 'menu_description' => 'この管理者アカウントの設定をします。'
+ 'menu_description' => 'この管理者アカウントの設定をします。',
],
'myaccount' => [
'menu_label' => 'アカウント',
'menu_description' => '名前、電子メールアドレス、パスワードなどのあなたのアカウント詳細をアップデートしてください。',
- 'menu_keywords' => 'セキュリティ ログイン'
+ 'menu_keywords' => 'セキュリティ ログイン',
],
'branding' => [
'menu_label' => 'バックエンドのカスタマイズ',
@@ -262,38 +454,204 @@
'brand' => 'ブランド',
'logo' => 'ロゴ',
'logo_description' => 'バックエンドで使用するロゴをアップロードします。',
+ 'favicon' => 'Favicon',
+ 'favicon_description' => 'バックエンドで使用するカスタムファビコンをアップロードする',
'app_name' => 'アプリ名',
'app_name_description' => 'この名前はバックエンドのタイトル領域に表示されます。',
'app_tagline' => 'アプリタグライン',
'app_tagline_description' => 'この名前はバックエンドのサインインページに表示されます。',
'colors' => '配色',
+ 'branding_colors' => 'ブランディングカラー',
+ 'branding_colors_comment' => 'これらの色は、あなたのブランディングに合わせてBackend UI全体で使用されます',
+ 'default_colors' => 'デフォルトカラー',
+ 'default_colors_comment' => 'これらの色は、オーバーライドされない限り、すべてのカラーピッカーでスウォッチとして利用できます。',
+ 'add_default_color' => 'デフォルトカラーを追加する',
'primary_color' => 'プライマリ color',
'secondary_color' => 'セカンダリ color',
'accent_color' => 'Accent color',
'styles' => 'スタイル',
- 'custom_stylesheet' => 'カスタムスタイルシート'
+ 'custom_stylesheet' => 'カスタムスタイルシート',
+ 'navigation' => 'ナビゲーション',
+ 'menu_mode' => 'メニューのスタイル',
+ 'menu_mode_inline' => 'インライン',
+ 'menu_mode_inline_no_icons' => 'インライン (アイコンなし)',
+ 'menu_mode_tile' => 'タイル',
+ 'menu_mode_collapsed' => '折りたたみ',
],
'backend_preferences' => [
- 'menu_label' => 'バックエンド',
+ 'menu_label' => 'バックエンド設定',
'menu_description' => '言語の設定などを行います。',
+ 'region' => '地域',
+ 'code_editor' => 'コードエディター',
+ 'timezone' => 'タイムゾーン',
+ 'timezone_comment' => '表示されている日付をこのタイムゾーンに合わせる。',
'locale' => 'ロケール',
- 'locale_comment' => '使用する言語のロケールを選択してください。'
+ 'locale_comment' => '使用する言語のロケールを選択してください。',
],
'access_log' => [
'hint' => 'このログは管理者のサインインが成功したリストです。記録は:days日間保持されます。',
'menu_label' => 'アクセスログ',
'menu_description' => 'サインインに成功したバックエンドユーザーを表示します。',
+ 'type' => 'タイプ',
'created_at' => '日時',
'login' => 'ログイン',
'ip_address' => 'IPアドレス',
'first_name' => '名',
'last_name' => '姓',
- 'email' => 'メール'
+ 'email' => 'メール',
],
'filter' => [
- 'all' => 'すべて'
+ 'all' => 'すべて',
+ 'options_method_not_exists' => "モデルクラス :model は ':filter' フィルタのオプションを返すメソッド :method() を定義する必要があります。",
+ 'date_all' => '全期間',
+ 'number_all' => '全数',
+ ],
+ 'import_export' => [
+ 'upload_csv_file' => '1.CSVファイルをアップロードする',
+ 'import_file' => 'インポートファイル',
+ 'row' => '行 :row',
+ 'first_row_contains_titles' => '最初の行には列のタイトルが含まれています。',
+ 'first_row_contains_titles_desc' => 'CSVの最初の行を列タイトルとして使用する場合は、これをチェックしたままにしてください。',
+ 'match_columns' => '2.ファイルのカラムとデータベースのフィールドを一致させる',
+ 'file_columns' => 'ファイルのカラム',
+ 'database_fields' => 'データベース・フィールド',
+ 'set_import_options' => '3.インポートオプションの設定',
+ 'export_output_format' => '1.エクスポートの出力形式',
+ 'file_format' => 'ファイル形式',
+ 'standard_format' => '標準のフォーマット',
+ 'custom_format' => 'カスタムフォーマット',
+ 'delimiter_char' => 'デリミタ文字',
+ 'enclosure_char' => '囲み文字',
+ 'escape_char' => 'エスケープ文字',
+ 'select_columns' => '2.エクスポートする列を選択する',
+ 'column' => '列',
+ 'columns' => '列',
+ 'set_export_options' => '3.エクスポートオプションの設定',
+ 'show_ignored_columns' => '無視された列を表示する',
+ 'auto_match_columns' => '自動一致する列',
+ 'created' => '作成された',
+ 'updated' => '更新',
+ 'skipped' => 'スキップ',
+ 'warnings' => '警告',
+ 'errors' => 'エラー',
+ 'skipped_rows' => 'スキップされた行',
+ 'import_progress' => 'インポート進捗状況',
+ '処理' => '処理',
+ 'import_error' => 'インポートエラー',
+ 'upload_valid_csv' => '有効なCSVファイルをアップロードしてください',
+ 'drop_column_here' => 'ここにカラムをドロップしてください...',
+ 'ignore_this_column' => 'このカラムは無視します',
+ 'processing_successful_line1' => 'ファイルエクスポート処理完了!',
+ 'processing_successful_line2' => 'ブラウザはファイルのダウンロードにリダイレクトします',
+ 'export_progress' => 'エクスポートの進捗状況',
+ 'export_error' => 'エクスポートのエラー',
+ 'column_preview' => '列のプレビュー',
+ 'file_not_found_error' => 'ファイルが見つかりません',
+ 'empty_error' => 'エクスポートに提供されたデータがありません',
+ 'empty_import_columns_error' => 'インポートする列をいくつか指定してください',
+ 'match_some_column_error' => '最初にいくつかのカラムをマッチさせてください。',
+ 'required_match_column_error' => '必須フィールド:labelにマッチするものをご指定ください。',
+ 'empty_export_columns_error' => 'エクスポートするカラムをいくつか指定してください',
+ 'behavior_missing_uselist_error' => 'コントローラの動作であるListControllerを、エクスポートの「useList」オプションを有効にして実装しなければなりません',
+ 'missing_model_class_error' => ':typeにmodelClassプロパティを指定してください',
+ 'missing_column_id_error' => 'カラムの識別子がありません',
+ 'unknown_column_error' => '不明なカラム',
+ 'encoding_not_supported_error' => 'ソースファイルのエンコーディングが認識されません。ファイルをインポートするには、適切なエンコーディングのカスタムファイル形式オプションを選択してください',
+ 'encoding_format' => 'ファイルのエンコーディング',
+ 'エンコーディング' => [
+ 'utf_8' => 'UTF-8',
+ 'us_ascii' => 'US-ASCII',
+ 'iso_8859_1' => 'ISO-8859-1 (Latin-1, 西ヨーロッパ)',
+ 'iso_8859_2' => 'ISO-8859-2 (Latin-2, 中央ヨーロッパ)',
+ 'iso_8859_3' => 'ISO-8859-3 (ラテン語-3、南ヨーロッパ)',
+ 'iso_8859_4' => 'ISO-8859-4 (ラテン語-4, 北欧)',
+ 'iso_8859_5' => 'ISO-8859-5 (ラテン語、キリル文字)',
+ 'iso_8859_6' => 'ISO-8859-6(ラテン語、アラビア語)',
+ 'iso_8859_7' => 'ISO-8859-7 (ラテン語、ギリシャ語)',
+ 'iso_8859_8' => 'ISO-8859-8 (ラテン語、ヘブライ語)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, トルコ語)',
+ 'iso_8859_10' => 'ISO-8859-10 (Latin-6, Nordic)',
+ 'iso_8859_11' => 'ISO-8859-11 (ラテン語、タイ語)',
+ 'iso_8859_13' => 'ISO-8859-13 (Latin-7, Baltic Rim)',
+ 'iso_8859_14' => 'ISO-8859-14 (Latin-8, Celtic)',
+ 'iso_8859_15' => 'ISO-8859-15 (Latin-9, ユーロ記号を含む西ヨーロッパ修正)',
+ 'windows_1250' => 'Windows-1250 (CP1250、中・東欧系)',
+ 'windows_1251' => 'Windows-1251 (CP1251)',
+ 'windows_1252' => 'Windows-1252 (CP1252)',
+ ],
+ ],
+ 'permissions' => [
+ 'manage_media' => 'メディアコンテンツ(画像、動画、音声、ドキュメント)のアップロードと管理',
+ 'allow_unsafe_markdown' => '安全でないMarkdownを使用する(Javascriptを含むことができる)',
+ ],
+ 'mediafinder' => [
+ 'label' => 'メディアファインダー',
+ 'default_prompt' => 'メディアアイテムを探すには%sボタンをクリックします',
+ 'no_image' => '画像が見つかりませんでした',
],
'media' => [
+ 'menu_label' => 'メディア',
+ 'アップロード' => 'アップロード',
+ 'move' => '移動',
+ 'delete' => '削除',
+ 'add_folder' => 'フォルダの追加',
+ 'search' => '検索',
+ 'display' => '表示',
+ 'filter_everything' => 'すべて',
+ 'filter_images' => '画像',
+ 'filter_video' => 'ビデオ',
+ 'filter_audio' => 'オーディオ',
+ 'filter_documents' => 'Documents',
+ 'library' => 'ライブラリ',
+ 'size' => 'サイズ',
+ 'title' => 'タイトル',
+ 'last_modified' => '最終更新日',
+ 'public_url' => 'URL',
+ 'click_here' => 'ここをクリック',
+ 'thumbnail_error' => 'サムネイルの生成に失敗しました',
+ 'return_to_parent' => '親フォルダに戻る',
+ 'return_to_parent_label' => '上に行く ...',
+ 'nothing_selected' => '何も選択されていません',
+ 'multiple_selected' => '複数の項目が選択されています',
+ 'uploading_file_num' => 'ファイル(複数可)をアップロードしています...',
+ 'uploading_complete' => 'アップロードが完了しました',
+ 'uploading_error' => 'アップロードに失敗しました',
+ 'type_blocked' => '使用されているファイルタイプはセキュリティ上の理由でブロックされています',
+ 'order_by' => '順序',
+ 'direction' => '方向',
+ 'direction_asc' => '昇順',
+ 'direction_desc' => '降順',
+ 'folder' => 'フォルダ',
+ 'no_files_found' => 'リクエストによってファイルが見つかりませんでした',
+ 'delete_empty' => '削除する項目を選択してください',
+ 'delete_confirm' => '選択したアイテムを削除しますか?',
+ 'error_renaming_file' => 'アイテムの名前変更に失敗しました',
+ 'new_folder_title' => '新しいフォルダー',
+ 'folder_name' => 'フォルダ名',
+ 'error_creating_folder' => 'フォルダの作成エラー',
+ 'folder_or_file_exist' => '指定された名前のフォルダまたはファイルはすでに存在しています',
+ 'move_empty' => '移動するアイテムを選択してください。',
+ 'move_popup_title' => 'ファイルまたはフォルダの移動',
+ 'move_popup_title' => 'ファイルまたはフォルダの移動',
+ 'move_destination' => '移動先フォルダ',
+ 'please_select_move_dest' => '保存先フォルダを選択してください',
+ 'move_dest_src_match' => '他の保存先フォルダを選択してください',
+ 'empty_library' => 'ここは少し空っぽのようです。ファイルをアップロードするか、フォルダを作成して始めましょう',
+ 'insert' => '挿入',
+ 'crop_and_insert' => 'クロップ&インサート',
+ 'select_single_image' => '画像を1枚選択してください。',
+ 'selection_not_image' => '選択された項目は画像ではありません',
+ 'restore' => 'すべての変更を元に戻す',
+ 'resize' => 'リサイズ...',
+ 'selection_mode_normal' => '通常',
+ 'selection_mode_fixed_ratio' => '固定比',
+ 'selection_mode_fixed_size' => 'サイズ固定',
+ 'height' => '高さ',
+ 'width' => '幅',
+ 'selection_mode' => '選択モード',
+ 'resize_image' => '画像のリサイズ',
+ 'image_size' => '画像サイズ:',
+ 'selected_size' => '選択されたサイズ:',
'rename_popup_title' => '名前変更',
'rename_new_name' => '新しい名前',
'move_please_select' => '選択してください',
diff --git a/modules/backend/lang/kr/lang.php b/modules/backend/lang/kr/lang.php
index 29983a896..df63f138d 100644
--- a/modules/backend/lang/kr/lang.php
+++ b/modules/backend/lang/kr/lang.php
@@ -479,7 +479,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/lt/lang.php b/modules/backend/lang/lt/lang.php
index aa712a82b..4e5dd3a60 100644
--- a/modules/backend/lang/lt/lang.php
+++ b/modules/backend/lang/lt/lang.php
@@ -477,7 +477,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/lv/lang.php b/modules/backend/lang/lv/lang.php
index 30911c28b..990432132 100644
--- a/modules/backend/lang/lv/lang.php
+++ b/modules/backend/lang/lv/lang.php
@@ -165,7 +165,7 @@
'name_field' => 'Nosaukums',
'name_comment' => 'Nosaukums tiek attēlots administratoru formas grupu sarakstā.',
'description_field' => 'Apraksts',
- 'is_new_user_default_field_label' => 'Noklusējama grupa',
+ 'is_new_user_default_field_label' => 'Noklusējuma grupa',
'is_new_user_default_field_comment' => 'Pievienot jaunos administratorus šai grupai pēc noklusējuma',
'code_field' => 'Kods',
'code_comment' => 'Norādiet unikālu kodu, ja vēlaties piekļūt grupas objektam ar API.',
@@ -320,6 +320,7 @@
'missing_model' => 'Relācijas uzvedībai klasē :class nav definēts modelis.',
'invalid_action_single' => 'Šī darbība nevar tikt veikta ar vienu relāciju.',
'invalid_action_multi' => 'Šī darbība nevar tikt veikta ar daudzām relācijām.',
+ 'relationwidget_unsupported_type' => 'Relāciju logrīks neatbalsta relāciju veidu ":type".',
'help' => 'Noklikšķiniet uz vienuma, lai pievienotu',
'related_data' => 'Saistītie :name dati',
'add' => 'Pievienot',
@@ -368,6 +369,7 @@
'plugin_missing' => 'Papildus nepieciešams :name spraudnis, taču tas nav instalēts. Lūdzu, instalējiet šo spraudni.',
'debug' => 'Ieslēgts atkļūdošanas režīms. Nav ieteicams produkcijas vides instalācijām.',
'decompileBackendAssets' => 'Backend papildu resursu faili šobrīd ir dekompilētā stāvoklī. Nav ieteicams produkcijas vides instalācijām.',
+ 'default_backend_user' => 'Tika atrasts lietotājs ar noklusējuma pieteikšanās datiem (admin / admin@domain.tld). Nomainiet viņu lietotājvārdu un / vai e-pasta adresi, lai palīdzētu aizsargāt sistēmu.',
],
'editor' => [
'menu_label' => 'Koda redaktora iestatījumi',
@@ -457,6 +459,11 @@
'app_tagline' => 'Lietotnes apraksts',
'app_tagline_description' => 'Šis apraksts tiek rādīts back-end pieslēgšanās lapā.',
'colors' => 'Krāsas',
+ 'branding_colors' => 'Zīmola krāsas',
+ 'branding_colors_comment' => 'Šīs krāsas tiks lietotas back-end lietotāja saskarnē, lai pielāgotu to jūsu zīmolam.',
+ 'default_colors' => 'Noklusējuma krāsas',
+ 'default_colors_comment' => 'Šīs krāsas būs pieejamas visās krāsu izvēles logrīku paletēs, ja vien netiks pārrakstītas.',
+ 'add_default_color' => 'Pievienot noklusējuma krāsu',
'primary_color' => 'Primārā krāsa',
'secondary_color' => 'Sekundārā krāsa',
'accent_color' => 'Akcenta krāsa',
@@ -561,7 +568,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latīņu, Arābu)',
'iso_8859_7' => 'ISO-8859-7 (Latīņu, Grieķu)',
'iso_8859_8' => 'ISO-8859-8 (Latīņu, Ebreju)',
- 'iso_8859_0' => 'ISO-8859-9 (Latīņu-5, Turku)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latīņu-5, Turku)',
'iso_8859_10' => 'ISO-8859-10 (Latīņu-6, Ziemeļvalstu)',
'iso_8859_11' => 'ISO-8859-11 (Latīņu, Taju)',
'iso_8859_13' => 'ISO-8859-13 (Latīņu-7, Baltijas reģiona)',
diff --git a/modules/backend/lang/nb-no/lang.php b/modules/backend/lang/nb-no/lang.php
index dc38311d5..993a3c315 100644
--- a/modules/backend/lang/nb-no/lang.php
+++ b/modules/backend/lang/nb-no/lang.php
@@ -468,7 +468,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/nl/lang.php b/modules/backend/lang/nl/lang.php
index 0473bd492..b53ca4389 100644
--- a/modules/backend/lang/nl/lang.php
+++ b/modules/backend/lang/nl/lang.php
@@ -559,7 +559,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/pl/lang.php b/modules/backend/lang/pl/lang.php
index 609396aba..284076f69 100644
--- a/modules/backend/lang/pl/lang.php
+++ b/modules/backend/lang/pl/lang.php
@@ -542,7 +542,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabski)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Grecki)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrajski)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turecki)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turecki)',
'iso_8859_10' => 'ISO-8859-10 (Latin-6, Nordycki)',
'iso_8859_11' => 'ISO-8859-11 (Latin, Tajski)',
'iso_8859_13' => 'ISO-8859-13 (Latin-7, Kraje Nadbałtyckie)',
diff --git a/modules/backend/lang/pt-br/lang.php b/modules/backend/lang/pt-br/lang.php
index b045fa8ff..f0c372d24 100644
--- a/modules/backend/lang/pt-br/lang.php
+++ b/modules/backend/lang/pt-br/lang.php
@@ -559,7 +559,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/pt-pt/lang.php b/modules/backend/lang/pt-pt/lang.php
index ecb7d34c2..f9e84ca52 100644
--- a/modules/backend/lang/pt-pt/lang.php
+++ b/modules/backend/lang/pt-pt/lang.php
@@ -542,7 +542,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/ro/lang.php b/modules/backend/lang/ro/lang.php
index 6f9943f08..0d42a5076 100644
--- a/modules/backend/lang/ro/lang.php
+++ b/modules/backend/lang/ro/lang.php
@@ -561,7 +561,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, arab)',
'iso_8859_7' => 'ISO-8859-7 (Latin, grec)',
'iso_8859_8' => 'ISO-8859-8 (Latin, ebraic)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, turc)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, turc)',
'iso_8859_10' => 'ISO-8859-10 (Latină-6, nordic)',
'iso_8859_11' => 'ISO-8859-11 (Latină, thailandez)',
'iso_8859_13' => 'ISO-8859-13 (Latin-7, baltic)',
diff --git a/modules/backend/lang/rs/lang.php b/modules/backend/lang/rs/lang.php
index bdac795df..41e3d9920 100644
--- a/modules/backend/lang/rs/lang.php
+++ b/modules/backend/lang/rs/lang.php
@@ -553,7 +553,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arapski)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Grčki)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrejski)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turski)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turski)',
'iso_8859_10' => 'ISO-8859-10 (Latin-6, Nordijski)',
'iso_8859_11' => 'ISO-8859-11 (Latin, Tajlandski)',
'iso_8859_13' => 'ISO-8859-13 (Latin-7, Baltički region)',
diff --git a/modules/backend/lang/ru/lang.php b/modules/backend/lang/ru/lang.php
index a94f76b47..057f83fee 100644
--- a/modules/backend/lang/ru/lang.php
+++ b/modules/backend/lang/ru/lang.php
@@ -3,7 +3,7 @@
return [
'auth' => [
'title' => 'Панель управления',
- 'invalid_login' => 'Вы ввели некорректные данные. Пожалуйста, перепроверьте их и попробуйте ещё раз.'
+ 'invalid_login' => 'Вы ввели некорректные данные. Пожалуйста, перепроверьте их и попробуйте ещё раз.',
],
'field' => [
'invalid_type' => 'Использован неверный тип поля: :type.',
@@ -77,7 +77,7 @@
'ok' => 'OK',
],
'dashboard' => [
- 'menu_label' => 'Панель управления',
+ 'menu_label' => 'Дашборд',
'widget_label' => 'Виджет',
'widget_width' => 'Ширина',
'full_width' => 'полная ширина',
@@ -320,6 +320,7 @@
'missing_model' => 'Для поведения отношения, используемого в :class не определена модель.',
'invalid_action_single' => 'Это действие не может быть выполнено для особого отношения.',
'invalid_action_multi' => 'Это действие не может быть выполнено для множественных отношений.',
+ 'relationwidget_unsupported_type' => '":type" тип связи не поддерживается виджетом Relation.',
'help' => 'Нажмите на элемент, который нужно добавить',
'related_data' => 'Связанные :name данные',
'add' => 'Добавить',
@@ -368,6 +369,7 @@
'plugin_missing' => 'Плагин :name имеет зависимость. Установите этот плагин.',
'debug' => 'Режим отладки включен. Это не рекомендуется для рабочих инсталяций.',
'decompileBackendAssets' => 'Ассеты в бэкенде в настоящее время декомпилированы. Это не рекомендуется для рабочих инсталяций.',
+ 'default_backend_user' => 'Был обнаружен пользователь с данными для входа по умолчанию (admin / admin@domain.tld). Измените свое имя пользователя и/или email-адрес, чтобы защитить систему.',
],
'editor' => [
'menu_label' => 'Настройки редактора',
@@ -457,8 +459,13 @@
'app_tagline' => 'Слоган приложения',
'app_tagline_description' => 'Слоган будет отображаться на экране входа в панель управления.',
'colors' => 'Цвета',
- 'primary_color' => 'Первичный color',
- 'secondary_color' => 'Вторичный color',
+ 'branding_colors' => 'Брендовые цвета',
+ 'branding_colors_comment' => 'Эти цвета будут использоваться во всем интерфейсе Backend UI, чтобы соответствовать вашему бренду.',
+ 'default_colors' => 'Цвета по умолчанию',
+ 'default_colors_comment' => 'Эти цвета будут доступны как образцы во всех палитрах цветов, если они не будут переопределены.',
+ 'add_default_color' => 'Добавить цвет по умолчанию',
+ 'primary_color' => 'Первичный цвет',
+ 'secondary_color' => 'Вторичный цвет',
'accent_color' => 'Цвет акцента',
'styles' => 'Стили',
'custom_stylesheet' => 'Пользовательские стили',
@@ -561,7 +568,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
@@ -569,7 +576,7 @@
'iso_8859_15' => 'ISO-8859-15 (Latin-9, Western European revision with euro sign)',
'windows_1250' => 'Windows-1250 (CP1250, Central and Eastern European)',
'windows_1251' => 'Windows-1251 (CP1251)',
- 'windows_1252' => 'Windows-1252 (CP1252)'
+ 'windows_1252' => 'Windows-1252 (CP1252)',
]
],
'permissions' => [
diff --git a/modules/backend/lang/sk/lang.php b/modules/backend/lang/sk/lang.php
index 2f7abced9..3a33d3378 100644
--- a/modules/backend/lang/sk/lang.php
+++ b/modules/backend/lang/sk/lang.php
@@ -8,32 +8,32 @@
'field' => [
'invalid_type' => 'Bol použitý zlý typ :type.',
'options_method_invalid_model' => "Vlastnosť ':field' nezodpovedá platnému modelu. Skúste špecifikovať metódu možností pre triedu modelu :model explicitne.",
- 'options_method_not_exists' => "Trieda modelu :model musí implementovať metódu :method(), ktorá vracia možností pre formulárové pole ':field'."
+ 'options_method_not_exists' => "Trieda modelu :model musí implementovať metódu :method(), ktorá vracia možnosti pre formulárové pole ':field'."
],
'widget' => [
- 'not_registered' => "Trieda widgetu s menom ':name' nie je zaregistrovaná",
+ 'not_registered' => "Trieda widget s menom ':name' nie je registrovaná",
'not_bound' => "Widget s názvom triedy ':name' nie je naviazaný na controller"
],
'page' => [
'untitled' => 'Bez názvu',
'404' => [
'label' => 'Stránka nenájdená',
- 'help' => "Hľadali sme a hľadali, ale požadovanú adresu URL jednoducho nebolo možné nájsť. Možno ste hľadali niečo iné?",
- 'back_link' => 'Späť na predchodzú stránku',
+ 'help' => "Hľadali sme a hľadali, ale požadovanú adresu URL jednoducho nebolo možné nájsť. Možno ste hľadali niečo iné ...",
+ 'back_link' => 'Späť na predošlú stránku',
],
'access_denied' => [
- 'label' => 'Prístup odmietnutý',
+ 'label' => 'Prístup bol odmietnutý',
'help' => "Nemáte potrebné oprávnenia na zobrazenie tejto stránky.",
'cms_link' => 'Späť do administrácie'
],
'no_database' => [
'label' => 'Chýba databáza',
- 'help' => "Pre prístup do administrácie je potrebná databáza. Zkontrolujte, či je databáza nakonfigurovaná a zmigrovaná a skúste to znova.",
+ 'help' => "Pre prístup do administrácie je potrebná databáza. Zkontrolujte, či je databáza nakonfigurovaná, migrovaná a skúste to znova.",
'cms_link' => 'Späť na úvodnú stránku'
],
],
'partial' => [
- 'not_found_name' => "Čiastočná šablona ':name' nebola nájdená."
+ 'not_found_name' => "Čiastočná šablóna ':name' nebola nájdená."
],
'account' => [
'signed_in_as' => 'Prihlásený ako :full_name',
@@ -398,7 +398,7 @@
],
'branding' => [
'menu_label' => 'Nastavenia administrácie',
- 'menu_description' => 'Nastavte si mano, použité farby a logo v administrácií.',
+ 'menu_description' => 'Nastavte si meno, použité farby a logo v administrácií.',
'brand' => 'Značka',
'logo' => 'Logo',
'logo_description' => 'Nahrajte vlastné logo, ktoré bude použité v administrácií.',
@@ -508,7 +508,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/sl/lang.php b/modules/backend/lang/sl/lang.php
index 73b8c28dc..97bfb057c 100644
--- a/modules/backend/lang/sl/lang.php
+++ b/modules/backend/lang/sl/lang.php
@@ -558,7 +558,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/tr/lang.php b/modules/backend/lang/tr/lang.php
index 102203358..4918db495 100644
--- a/modules/backend/lang/tr/lang.php
+++ b/modules/backend/lang/tr/lang.php
@@ -520,7 +520,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arapça)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Yunanca)',
'iso_8859_8' => 'ISO-8859-8 (Latin, İbranice)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Türkçe)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Türkçe)',
'iso_8859_10' => 'ISO-8859-10 (Latin-6, Norveççe)',
'iso_8859_11' => 'ISO-8859-11 (Latin, Thai Dili)',
'iso_8859_13' => 'ISO-8859-13 (Latin-7, Baltık Rim)',
diff --git a/modules/backend/lang/uk/lang.php b/modules/backend/lang/uk/lang.php
index d16947d21..2f650899c 100644
--- a/modules/backend/lang/uk/lang.php
+++ b/modules/backend/lang/uk/lang.php
@@ -2,34 +2,58 @@
return [
'auth' => [
- 'title' => 'Зона адміністрування'
+ 'title' => 'Панель керування',
+ 'invalid_login' => 'Ви ввели некоректні дані. Будь ласка, перевірте їх та спробуйте ще раз.',
],
'field' => [
- 'invalid_type' => 'Неправильний тип поля :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.",
+ 'invalid_type' => 'Застосовано невірний тип поля: :type.',
+ 'options_method_invalid_model' => "Атрибут ':field' не відповідає допустимій моделі. Спробуйте явно вказати метод параметрів для класу :model .",
'options_method_not_exists' => "Клас моделі :model повинен містити метод :method(), що повертає опції для поля ':field'.",
+ 'options_static_method_invalid_value' => "Статический метод ':method()' в :class не вернул допустимый массив параметров.",
+ 'colors_method_not_exists' => "Класс модели :model должен содержать метод :method(), возвращающий HTML цвет в HEX для поля формы ':field'.",
],
'widget' => [
- 'not_registered' => "Назву класу віджету ':name' не зареєстровано",
- 'not_bound' => "Клас віджета ':name' не прив'зано до контролера",
+ 'not_registered' => "Клас віджету ':name' не зареєстровано",
+ 'not_bound' => "Віджет з назвою класу ':name' не прив'зано до контролера",
],
'page' => [
'untitled' => 'Без назви',
+ '404' => [
+ 'label' => 'Сторінка не знайдена',
+ 'help' => 'Ми ретельно шукали, але запрошенный URL так і не змогли знайти. Можливо ви шукали щось інше?',
+ 'back_link' => 'Повернутися до попередньої сторінки',
+ ],
'access_denied' => [
'label' => 'Доступ заборонено',
- 'help' => 'У Вас немає необхідних прав для перегляду цієї сторінки.',
- 'cms_link' => 'Повернутися до back-end-у'
+ 'help' => 'У вас немає необхідних прав для перегляду цієї сторінки.',
+ 'cms_link' => 'Повернутися до бекенду',
],
'no_database' => [
- 'label' => 'База данних відсутня',
- 'help' => 'Для доступу до back-end - потрібна база данних. Перевірте, налаштування та міграції бази данних, перш ніж спробувати знову.',
- 'cms_link' => 'Повернутися на домашню сторінку'
+ 'label' => 'Відсутня база данних',
+ 'help' => 'Для доступу до бекенду потрібна база данних. Перевірте, налаштування та міграції бази данних, перш ніж спробувати знову.',
+ 'cms_link' => 'Повернутися на домашню сторінку',
],
],
'partial' => [
- 'not_found_name' => "Частину ':name' не знайдено.",
+ 'not_found_name' => "Фрагмент ':name' не знайдено.",
+ 'invalid_name' => 'Неправильне назва фрагменту: :name.',
+ ],
+ 'ajax_handler' => [
+ 'invalid_name' => 'Невірна назва AJAX обробника: :name.',
+ 'not_found' => "AJAX обробника ':name' не знайден.",
],
'account' => [
+ 'impersonate' => 'Імперсонація користувача',
+ 'impersonate_confirm' => 'Ви впевнені, що хочете імперсонувати себе як цього користувача? Ви зможете повернутися у вихідний стан вийшовши з системи.',
+ 'impersonate_success' => 'Тепер ви імперсоновані як цей користувач',
+ 'impersonate_working' => 'Імперсонація...',
+ 'impersonating' => 'Ви тимчасово увійшли до системи як :impersonatee. Журнали логів, як і раніше, можуть ідентифікувати вас як :impersonator',
+ 'stop_impersonating' => 'Зупинити імперсонацію',
+ 'unsuspend' => 'Припинено',
+ 'unsuspend_confirm' => 'Ви впевнені, що хочете призупинити цього користувача?',
+ 'unsuspend_success' => 'Користувач був припинений.',
+ 'unsuspend_working' => 'Призупинення...',
+ 'signed_in_as' => 'Виконано вхід як :full_name',
'sign_out' => 'Вийти',
'login' => 'Увійти',
'reset' => 'Скинути',
@@ -50,7 +74,7 @@
'apply' => 'Застосувати',
'cancel' => 'Скасувати',
'delete' => 'Видалити',
- 'ok' => 'ОК'
+ 'ok' => 'ОК',
],
'dashboard' => [
'menu_label' => 'Панель керування',
@@ -89,7 +113,7 @@
'core_build' => 'Версія системи',
'event_log' => 'Журнал подій',
'request_log' => 'Журнал запитів',
- 'app_birthday' => 'Сайт засновано'
+ 'app_birthday' => 'Сайт засновано',
],
'welcome' => [
'widget_title_default' => 'Ласкаво просимо',
@@ -98,7 +122,7 @@
'first_sign_in' => 'Це ваша перша авторизація.',
'last_sign_in' => 'Дата останньої авторизації',
'view_access_logs' => 'Перегляд журналу доступу',
- 'nice_message' => 'Гарного дня!'
+ 'nice_message' => 'Гарного дня!',
],
],
'user' => [
@@ -134,6 +158,8 @@
'last_login' => 'Останній вхід',
'created_at' => 'Створено',
'updated_at' => 'Оновлено',
+ 'deleted_at' => 'Вилучен',
+ 'show_deleted' => 'Показати вилучених',
'group' => [
'name' => 'Група',
'name_comment' => 'Назва відображається на сторінці стоврення/редагування адміністратора.',
@@ -148,7 +174,7 @@
'new' => 'Нова група',
'delete_confirm' => 'Видалити цю групу адміністраторів?',
'return' => 'Повернутись до списку груп',
- 'users_count' => 'Користувачі'
+ 'users_count' => 'Користувачі',
],
'role' => [
'name' => 'Роль',
@@ -162,11 +188,13 @@
'new' => 'Нова роль',
'delete_confirm' => 'Видалити цю роль адміністратора?',
'return' => 'Повернутися до списку ролей',
- 'users_count' => 'Користувачі'
+ 'users_count' => 'Користувачі',
],
'preferences' => [
- 'not_authenticated' => 'Немає автентифікованих користувачів, чиї налаштування можна завантажити або зберегти.'
+ 'not_authenticated' => 'Немає автентифікованих користувачів, чиї налаштування можна завантажити або зберегти.',
],
+ 'trashed_hint_title' => 'Цей аккаунт був вилучен',
+ 'trashed_hint_desc' => 'Цей аккаунт був вилучен та не може бути авторизован. Щоб відновити його, натисніть іконку відновлення користувача в правому нижньому куті.',
],
'list' => [
'default_title' => 'Список',
@@ -197,7 +225,7 @@
'delete_selected_confirm' => 'Видалити обрані записи?',
'delete_selected_success' => 'Обрані записи успішно видалено',
'column_switch_true' => 'Так',
- 'column_switch_false' => 'Ні'
+ 'column_switch_false' => 'Ні',
],
'fileupload' => [
'attachment' => 'Прикріплення',
@@ -209,7 +237,12 @@
'upload_file' => 'Завантажити файл',
'upload_error' => 'Помилка завантаження',
'remove_confirm' => 'Ви впевнені?',
- 'remove_file' => 'Видалити файл'
+ 'remove_file' => 'Видалити файл',
+ ],
+ 'repeater' => [
+ 'add_new_item' => 'Додати новий об\'єкт',
+ 'min_items_failed' => ':name потребує мінімум :min об\'єктів, було передано тільки :items',
+ 'max_items_failed' => ':name дозволяє передати максимум :max об\'єктів, було передано :items',
],
'form' => [
'create_title' => 'Нова :name',
@@ -218,6 +251,7 @@
'create_success' => ':name створено',
'update_success' => ':name оновлено',
'delete_success' => ':name видалено',
+ 'restore_success' => ':name відновленно',
'reset_success' => 'Успішно скасовано',
'missing_id' => 'Ідентифікатор запису форми не було вказано.',
'missing_model' => 'Поведінка форми, що використовується в :class не містить визначення моделі.',
@@ -237,6 +271,9 @@
'confirm_delete' => 'Ви дійсно хочете видалити цей запис?',
'confirm_delete_multiple' => 'Ви дійсно хочете видалити обрані записи?',
'deleting_name' => 'Видалення :name…',
+ 'restore' => 'Відновити',
+ 'restoring' => 'Відновлення...',
+ 'confirm_restore' => 'Ви впевненні, що хочете відновити цей запис?',
'reset_default' => 'Скинути за замовчуванням',
'resetting' => 'Скидання',
'resetting_name' => 'Скидання :name',
@@ -266,15 +303,16 @@
'delete_row' => 'Видалити рядок',
'concurrency_file_changed_title' => 'Файл було змінено',
'concurrency_file_changed_description' => 'Файл, що Ви редагуєте, було змінено на диску іншим користувачем. Ви можете або перезавантажити файл та втратити свої зміни, або перезаписати файл на диску.',
- 'return_to_list' => 'Повернутися до списку'
+ 'return_to_list' => 'Повернутися до списку',
],
'recordfinder' => [
'find_record' => 'Знайти запис',
- 'cancel' => 'Скасувати'
+ 'invalid_model_class' => 'Наданий клас моделі ":modelClass" для пошуку запису є недійсним',
+ 'cancel' => 'Скасувати',
],
'pagelist' => [
'page_link' => 'Посилання на сторінку',
- 'select_page' => 'Обрати сторынку...'
+ 'select_page' => 'Обрати сторінку...',
],
'relation' => [
'missing_config' => "Поведінка відношення не містить жодного налаштування для ':config'.",
@@ -282,6 +320,7 @@
'missing_model' => 'Поведінка відношення, що використовується в :class не містить визначення моделі.',
'invalid_action_single' => 'Ця дія не може бути здійсненою щодо одного відношення.',
'invalid_action_multi' => 'Ця дія не може бути здійснена щодо кількох відношень.',
+ 'relationwidget_unsupported_type' => '":type" тип зв\'язку не підтримується віджетом Relation.',
'help' => 'Натисніть по елементу щоб додати',
'related_data' => 'Відносяться дані :name',
'add' => 'Додати',
@@ -307,11 +346,11 @@
'link_name' => 'Зв\'язати :name',
'unlink' => 'Відв\'язати',
'unlink_name' => 'Відв\'язати :name',
- 'unlink_confirm' => 'Ви впевнені?'
+ 'unlink_confirm' => 'Ви впевнені?',
],
'reorder' => [
'default_title' => 'Перевпорядкувати записи',
- 'no_records' => 'Немає доступних до сортування записів.'
+ 'no_records' => 'Немає доступних до сортування записів.',
],
'model' => [
'name' => 'Модель',
@@ -327,11 +366,15 @@
'tips_description' => 'Є речі, на які потрібно звернути увагу щоб правильно налаштувати систему.',
'permissions' => 'Директорія :name та її субдиректорії не мають дозволу для запису для PHP. Будь ласка, встановіть відповідні дозволи для веб-серверу для цієї директорії.',
'extension' => 'Розширення PHP :name не встановлено. Будь ласка, встановіть цю бібліотеку та активуйте розширення.',
- 'plugin_missing' => 'Плагін :name має залежності, які не встановлено. Будь ласка, встановіть ці плагіни.'
+ 'plugin_missing' => 'Плагін :name має залежності, які не встановлено. Будь ласка, встановіть ці плагіни.',
+ 'debug' => 'Режим налагодження увімкнено. Це не рекомендується для робочих інсталяцій.',
+ 'decompileBackendAssets' => 'Ассети в бекенді нині декомпільовані. Це не рекомендується для робочих інсталяцій.',
+ 'default_backend_user' => 'Був виявлен користувач з даними для входу за замовчуванням (admin / admin@domain.tld). Змініть своє ім\'я користувача та/або email-адресу, щоб захистити систему.',
],
'editor' => [
'menu_label' => 'Налаштування редактору коду',
'menu_description' => 'Персоналізуйте свої налаштування редактору коду, такі як розмір шрифту та колір.',
+ 'preview' => 'Передпрогляд',
'font_size' => 'Розмір шрифту',
'tab_size' => 'Розмір табуляції',
'use_hard_tabs' => 'Відступ табуляціями',
@@ -366,40 +409,61 @@
'label' => 'Назва поля',
'class_name' => 'Назва класу',
'markup_tags' => 'Теги розмітки',
+ 'markup_tag' => 'Тег розмітки',
'allowed_empty_tags' => 'Дозволені порожні теги',
'allowed_empty_tags_comment' => 'Контактні дані, які не видаляються, коли вони не мають ніякого змісту всередині.',
'allowed_tags' => 'Дозволені теги',
'allowed_tags_comment' => 'Список дозволених тегів.',
+ 'allowed_attributes' => 'Разрешенные атрибуты',
+ 'allowed_attributes_comment' => 'Список дозволених атрибутів.',
'no_wrap' => 'Не обертати теги',
'no_wrap_comment' => 'Список тегів, які не повинні бути обгорнуті в блокові елементи.',
'remove_tags' => 'Видаляємі теги',
'remove_tags_comment' => 'Теги які видаляються разом з їх вмістом.',
+ 'line_breaker_tags' => 'Теги з перенесенням рядку',
+ 'line_breaker_tags_comment' => 'Список тегів, в яких буде використовуватися тег перенесення рядку',
+ 'toolbar_options' => 'Опции панели инструментов',
'toolbar_buttons' => 'Кнопки панелі інструментів',
- 'toolbar_buttons_comment' => 'Кнопки панелі інструментів, які за замовчуванням відображаються в Rich Editor.'
+ 'toolbar_buttons_comment' => 'Кнопки панелі інструментів, які за замовчуванням відображаються в Rich Editor.',
+ 'toolbar_buttons_preset' => 'Вставити предустановленний набір кнопок панелі інструментів:',
+ 'toolbar_buttons_presets' => [
+ 'default' => 'За замовчуванням',
+ 'minimal' => 'Мінімальний',
+ 'full' => 'Повний',
+ ],
+ 'paragraph_formats' => 'Формати абзаців',
+ 'paragraph_formats_comment' => 'Опції що з\'являються у списку Формати абзаців.',
],
'tooltips' => [
- 'preview_website' => 'Перегляд веб-сайту'
+ 'preview_website' => 'Перегляд сайту',
],
'mysettings' => [
'menu_label' => 'Мої налаштування',
- 'menu_description' => 'Налаштування, що стосуються Вашого адміністративного облікового запису'
+ 'menu_description' => 'Управління налаштуваннями облікового запису адміністратора',
],
'myaccount' => [
'menu_label' => 'Мій обліковий запис',
- 'menu_description' => 'Оновіть свої деталі облікового запису, такі як ім\'я, електронна адреса та пароль.',
- 'menu_keywords' => 'безпека ім\'я користувача'
+ 'menu_description' => 'Управління особистою інформацією (ім\'я, пошта та пароль).',
+ 'menu_keywords' => 'безпека логін',
],
'branding' => [
- 'menu_label' => 'Налаштування вигляду back-end-у',
- 'menu_description' => 'Налаштуйте адміністративну зону, зокрема ім\'я, кольори та лого.',
+ 'menu_label' => 'Персоналізація панелі керування',
+ 'menu_description' => 'Налаштування зовньошнього виду панелі керування (назва, кольори, логотип).',
'brand' => 'Бренд',
- 'logo' => 'Лого',
- 'logo_description' => 'Завантажити власне лого для використання в back-end-і.',
+ 'logo' => 'Логотип',
+ 'logo_description' => 'Завантажте логотип для панелі керування.',
+ 'favicon' => 'Фавікон',
+ 'favicon_description' => 'Завантажте власний фавікон для бекенду',
'app_name' => 'Назва додатку',
- 'app_name_description' => 'Ця назва показується в заголовку back-end-у.',
+ 'app_name_description' => 'Ця назва показується в заголовку панелі керування.',
'app_tagline' => 'Девіз додатку',
- 'app_tagline_description' => 'Ця назва показується на екрані входу до back-end-у.',
+ 'app_tagline_description' => 'Ця назва показується на екрані входу до панелі керування.',
'colors' => 'Кольори',
+ 'branding_colors' => 'Брендові кольори',
+ 'branding_colors_comment' => 'Ці кольори будуть використовуватись у всьому інтерфейсі Backend UI, щоб відповідати вашому бренду.',
+ 'default_colors' => 'Кольори за замовчуванням',
+ 'default_colors_comment' => 'Ці кольори будуть доступні як зразки у всіх палітрах кольорів, якщо вони не будуть перевизначені.',
+ 'add_default_color' => 'Додати колір за замовчуванням',
'primary_color' => 'Основний колір',
'secondary_color' => 'Вторинний колір',
'accent_color' => 'Акцентний колір',
@@ -408,8 +472,9 @@
'navigation' => 'Навігація',
'menu_mode' => 'Cтиль меню',
'menu_mode_inline' => 'В лінію',
+ 'menu_mode_inline_no_icons' => 'Рядковий (без іконок)',
'menu_mode_tile' => 'Плитка',
- 'menu_mode_collapsed' => 'Схлопнутий'
+ 'menu_mode_collapsed' => 'Схлопнутий',
],
'backend_preferences' => [
'menu_label' => 'Налаштування back-end-у',
@@ -419,27 +484,31 @@
'timezone' => 'Часовий пояс',
'timezone_comment' => 'Виводити дати в обраному часовому поясі.',
'locale' => 'Мова',
- 'locale_comment' => 'Оберіть бажану мову для використання.'
+ 'locale_comment' => 'Оберіть бажану мову для використання.',
],
'access_log' => [
'hint' => 'Цей журнал показує список успішних спроб входу адміністраторів. Записи зберігаються :days днів.',
'menu_label' => 'Журнал доступу',
'menu_description' => 'Переглянути список успішних входів користувачів back-end-у.',
+ 'id' => 'ID',
'created_at' => 'Дата та час',
+ 'type' => 'Тип',
'login' => 'Ім\'я користувача',
'ip_address' => 'IP-адреса',
'first_name' => 'Ім\'я',
'last_name' => 'Прізвище',
- 'email' => 'Електронна адреса'
+ 'email' => 'Електронна адреса',
],
'filter' => [
'all' => 'всі',
'options_method_not_exists' => "Модель класу :model повинна містити метод :method() який повертає значення для поля ':filter'.",
- 'date_all' => 'весь період'
+ 'date_all' => 'весь період',
+ 'number_all' => 'всі числа',
],
'import_export' => [
'upload_csv_file' => '1. Завантажити CSV-файл',
'import_file' => 'Імпортувати файл',
+ 'row' => 'Рядок :row',
'first_row_contains_titles' => 'Перший рядок містить назви колонок',
'first_row_contains_titles_desc' => 'Залиште це відміченим якщо перший рядок CSV використовується для назв колонок.',
'match_columns' => '2. Віднести колонки файлу до полів бази даних',
@@ -499,22 +568,25 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
'iso_8859_14' => 'ISO-8859-14 (Latin-8, Celtic)',
'iso_8859_15' => 'ISO-8859-15 (Latin-9, Western European revision with euro sign)',
+ 'windows_1250' => 'Windows-1250 (CP1250, Central and Eastern European)',
'windows_1251' => 'Windows-1251 (CP1251)',
- 'windows_1252' => 'Windows-1252 (CP1252)'
- ]
+ 'windows_1252' => 'Windows-1252 (CP1252)',
+ ],
],
'permissions' => [
- 'manage_media' => 'Керування медіафайлами'
+ 'manage_media' => 'Завантаження та керування медіаконтентом - зображеннями, відео, звуками, документами',
+ 'allow_unsafe_markdown' => 'Використовувати небезпечний Markdown (може включати Javascript)',
],
'mediafinder' => [
'label' => 'Пошук медіа',
- 'default_prompt' => 'Натисніть на кнопку %s, щоб знайти медіафайл'
+ 'default_prompt' => 'Натисніть на кнопку %s, щоб знайти медіафайл',
+ 'no_image' => 'Не вдалося знайти зображення',
],
'media' => [
'menu_label' => 'Медіафайли',
@@ -545,6 +617,9 @@
'uploading_error' => 'Помилка завантаження',
'type_blocked' => 'Тип файлу, який використовується заблокований з міркувань безпеки.',
'order_by' => 'Сортувати за',
+ 'direction' => 'Напрямок сортування',
+ 'direction_asc' => 'За зростанням',
+ 'direction_desc' => 'За зменшенням',
'folder' => 'Папка',
'no_files_found' => 'Жоден з файлів не задовольняє вашому запиту.',
'delete_empty' => 'Будь ласка, оберіть об\'єкти для видалення.',
diff --git a/modules/backend/lang/vn/lang.php b/modules/backend/lang/vn/lang.php
index 3c3e7c42d..5041660e8 100644
--- a/modules/backend/lang/vn/lang.php
+++ b/modules/backend/lang/vn/lang.php
@@ -501,7 +501,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/lang/zh-cn/lang.php b/modules/backend/lang/zh-cn/lang.php
index 42737debd..3a1e21442 100644
--- a/modules/backend/lang/zh-cn/lang.php
+++ b/modules/backend/lang/zh-cn/lang.php
@@ -532,7 +532,7 @@
'iso_8859_6' => 'ISO-8859-6 (Latin, Arabic)',
'iso_8859_7' => 'ISO-8859-7 (Latin, Greek)',
'iso_8859_8' => 'ISO-8859-8 (Latin, Hebrew)',
- 'iso_8859_0' => 'ISO-8859-9 (Latin-5, Turkish)',
+ 'iso_8859_9' => 'ISO-8859-9 (Latin-5, Turkish)',
'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, Baltic Rim)',
diff --git a/modules/backend/layouts/auth.htm b/modules/backend/layouts/auth.htm
index 8624007fb..1ecd0ad1a 100644
--- a/modules/backend/layouts/auth.htm
+++ b/modules/backend/layouts/auth.htm
@@ -22,7 +22,7 @@
Url::asset('modules/system/assets/js/framework.js'),
Url::asset('modules/system/assets/ui/storm-min.js'),
Backend::skinAsset('assets/js/winter-min.js'),
- Url::to('modules/backend/assets/js/auth/auth.js'),
+ Url::asset('modules/backend/assets/js/auth/auth.js'),
Url::asset('modules/system/assets/js/lang/lang.'.App::getLocale().'.js'),
];
?>
diff --git a/modules/backend/models/ImportModel.php b/modules/backend/models/ImportModel.php
index 47bf78a55..8552d08b4 100644
--- a/modules/backend/models/ImportModel.php
+++ b/modules/backend/models/ImportModel.php
@@ -228,7 +228,7 @@ public function getFormatEncodingOptions()
'iso-8859-6',
'iso-8859-7',
'iso-8859-8',
- 'iso-8859-0',
+ 'iso-8859-9',
'iso-8859-10',
'iso-8859-11',
'iso-8859-13',
diff --git a/modules/backend/models/User.php b/modules/backend/models/User.php
index b04d68159..2d2a9e670 100644
--- a/modules/backend/models/User.php
+++ b/modules/backend/models/User.php
@@ -230,9 +230,9 @@ public function getMergedPermissions()
if (BackendAuth::isImpersonator()) {
$impersonator = BackendAuth::getImpersonator();
if ($impersonator && $impersonator !== $this) {
- foreach ($permissions as $i => $permission) {
+ foreach ($permissions as $permission => $status) {
if (!$impersonator->hasAccess($permission)) {
- unset($permissions[$i]);
+ unset($permissions[$permission]);
}
}
$this->mergedPermissions = $permissions;
diff --git a/modules/backend/models/UserRole.php b/modules/backend/models/UserRole.php
index a194b549f..f80e6ea49 100644
--- a/modules/backend/models/UserRole.php
+++ b/modules/backend/models/UserRole.php
@@ -23,8 +23,8 @@ class UserRole extends RoleBase
* @var array Validation rules
*/
public $rules = [
- 'name' => 'required|between:2,128|unique:backend_user_roles',
- 'code' => 'unique:backend_user_roles',
+ 'name' => 'required|between:2,128|unique',
+ 'code' => 'unique',
];
/**
@@ -37,7 +37,8 @@ class UserRole extends RoleBase
public function filterFields($fields)
{
- if ($this->is_system) {
+ // System roles cannot have their code or permissions changed
+ if ($this->isSystemRole()) {
$fields->code->disabled = true;
$fields->permissions->disabled = true;
}
@@ -45,25 +46,33 @@ public function filterFields($fields)
public function afterFetch()
{
- if ($this->is_system) {
+ // System role permissions are determined by the permissions that attach
+ // themselves to the given role's code via the `roles` property.
+ if ($this->isSystemRole()) {
$this->permissions = $this->getDefaultPermissions();
}
}
public function beforeSave()
{
+ // System roles cannot have their code or permissions changed
if ($this->isSystemRole()) {
$this->is_system = true;
$this->permissions = [];
+ if ($this->exists) {
+ $this->code = $this->getOriginal('code');
+ }
}
}
public function isSystemRole()
{
+ // System roles must have a valid code property
if (!$this->code || !strlen(trim($this->code))) {
return false;
}
+ // Winter default system roles
if ($this->is_system || in_array($this->code, [
self::CODE_DEVELOPER,
self::CODE_PUBLISHER
@@ -71,11 +80,22 @@ public function isSystemRole()
return true;
}
+ // If any permission attaches itself to a given role's code
+ // that role is now considered a system role
return AuthManager::instance()->hasPermissionsForRole($this->code);
}
- public function getDefaultPermissions()
+ /**
+ * Get the permissions that have attached themselves to the current role
+ */
+ public function getDefaultPermissions(): array
{
- return AuthManager::instance()->listPermissionsForRole($this->code);
+ // Only the Develper role inherits all "orphaned" / unassigned permissions by default
+ $includeOrphanedPermissions = false;
+ if ($this->code === self::CODE_DEVELOPER) {
+ $includeOrphanedPermissions = true;
+ }
+
+ return AuthManager::instance()->listPermissionsForRole($this->code, $includeOrphanedPermissions);
}
}
diff --git a/modules/backend/models/brandsetting/fields.yaml b/modules/backend/models/brandsetting/fields.yaml
index 0dc962538..ff7769feb 100644
--- a/modules/backend/models/brandsetting/fields.yaml
+++ b/modules/backend/models/brandsetting/fields.yaml
@@ -106,4 +106,4 @@ tabs:
type: codeeditor
tab: backend::lang.branding.styles
size: giant
- language: css
+ language: less
diff --git a/modules/backend/models/usergroup/columns.yaml b/modules/backend/models/usergroup/columns.yaml
index 0086d159e..9647fe534 100644
--- a/modules/backend/models/usergroup/columns.yaml
+++ b/modules/backend/models/usergroup/columns.yaml
@@ -22,3 +22,15 @@ columns:
valueFrom: count
default: 0
sortable: false
+
+ created_at:
+ label: backend::lang.user.created_at
+ searchable: true
+ invisible: true
+ type: datetime
+
+ updated_at:
+ label: backend::lang.user.updated_at
+ searchable: true
+ invisible: true
+ type: datetime
diff --git a/modules/backend/models/userrole/columns.yaml b/modules/backend/models/userrole/columns.yaml
index 20f27aa59..164bca69d 100644
--- a/modules/backend/models/userrole/columns.yaml
+++ b/modules/backend/models/userrole/columns.yaml
@@ -14,7 +14,7 @@ columns:
description:
label: backend::lang.user.role.description_field
- searchable: yes
+ searchable: true
users_count:
label: backend::lang.user.role.users_count
@@ -22,3 +22,15 @@ columns:
valueFrom: count
default: 0
sortable: false
+
+ created_at:
+ label: backend::lang.user.created_at
+ searchable: true
+ invisible: true
+ type: datetime
+
+ updated_at:
+ label: backend::lang.user.updated_at
+ searchable: true
+ invisible: true
+ type: datetime
diff --git a/modules/backend/models/userrole/fields.yaml b/modules/backend/models/userrole/fields.yaml
index f0d7ccfd7..4ec8fd776 100644
--- a/modules/backend/models/userrole/fields.yaml
+++ b/modules/backend/models/userrole/fields.yaml
@@ -12,6 +12,9 @@ fields:
label: backend::lang.user.role.code_field
commentAbove: backend::lang.user.role.code_comment
span: auto
+ preset:
+ type: slug
+ field: name
description:
label: backend::lang.user.role.description_field
@@ -20,3 +23,12 @@ fields:
tabs:
stretch: true
+ fields:
+ permissions:
+ tab: 'backend::lang.user.permissions'
+ type: 'Backend\FormWidgets\PermissionEditor'
+ mode: 'checkbox'
+ _users@update:
+ label: ''
+ tab: backend::lang.user.menu_label
+ type: partial
\ No newline at end of file
diff --git a/modules/backend/traits/InspectableContainer.php b/modules/backend/traits/InspectableContainer.php
index a089518f9..5beabba5d 100644
--- a/modules/backend/traits/InspectableContainer.php
+++ b/modules/backend/traits/InspectableContainer.php
@@ -34,7 +34,23 @@ public function onInspectableGetOptions()
throw new ApplicationException('The options cannot be loaded for the specified class.');
}
- $obj = new $className(null);
+ // Determine constructor requirements and pass the default value (or NULL) to all required arguments
+ $reflection = new \ReflectionClass($className);
+ $constructor = $reflection->getConstructor();
+ if (is_null($constructor) || !count($constructor->getParameters())) {
+ $obj = new $className();
+ } else {
+ $args = $constructor->getParameters();
+ $passed = [];
+ foreach ($args as $arg) {
+ if ($arg->isOptional()) {
+ $passed[] = $arg->getDefaultValue();
+ continue;
+ }
+ $passed[] = null;
+ }
+ $obj = $reflection->newInstanceArgs($passed);
+ }
// Nested properties have names like object.property.
// Convert them to Object.Property.
diff --git a/modules/backend/views/404.php b/modules/backend/views/404.php
index 73951c509..8e7dc9f92 100644
--- a/modules/backend/views/404.php
+++ b/modules/backend/views/404.php
@@ -3,7 +3,7 @@
= Lang::get('backend::lang.page.404.label') ?>
-
+
diff --git a/modules/backend/views/access_denied.php b/modules/backend/views/access_denied.php
index 988424c22..e90e2af75 100644
--- a/modules/backend/views/access_denied.php
+++ b/modules/backend/views/access_denied.php
@@ -3,7 +3,7 @@
= Lang::get('backend::lang.page.access_denied.label') ?>
-
+
diff --git a/modules/backend/views/no_database.php b/modules/backend/views/no_database.php
index 3100d1ba7..e1c0bc177 100644
--- a/modules/backend/views/no_database.php
+++ b/modules/backend/views/no_database.php
@@ -3,7 +3,7 @@
= Lang::get('backend::lang.page.no_database.label') ?>
-
+
diff --git a/modules/backend/widgets/mediamanager/partials/_item-sidebar-preview.htm b/modules/backend/widgets/mediamanager/partials/_item-sidebar-preview.htm
index 44b0ebe51..e5dfdc8e9 100644
--- a/modules/backend/widgets/mediamanager/partials/_item-sidebar-preview.htm
+++ b/modules/backend/widgets/mediamanager/partials/_item-sidebar-preview.htm
@@ -9,7 +9,7 @@
diff --git a/modules/cms/ServiceProvider.php b/modules/cms/ServiceProvider.php
index ed2101d14..dac29cb1a 100644
--- a/modules/cms/ServiceProvider.php
+++ b/modules/cms/ServiceProvider.php
@@ -228,42 +228,43 @@ protected function registerBackendPermissions()
'cms.manage_content' => [
'label' => 'cms::lang.permissions.manage_content',
'tab' => 'cms::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
'order' => 100
],
'cms.manage_assets' => [
'label' => 'cms::lang.permissions.manage_assets',
'tab' => 'cms::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
'order' => 100
],
'cms.manage_pages' => [
'label' => 'cms::lang.permissions.manage_pages',
'tab' => 'cms::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
'order' => 100
],
'cms.manage_layouts' => [
'label' => 'cms::lang.permissions.manage_layouts',
'tab' => 'cms::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
'order' => 100
],
'cms.manage_partials' => [
'label' => 'cms::lang.permissions.manage_partials',
'tab' => 'cms::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
'order' => 100
],
'cms.manage_themes' => [
'label' => 'cms::lang.permissions.manage_themes',
'tab' => 'cms::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
'order' => 100
],
'cms.manage_theme_options' => [
'label' => 'cms::lang.permissions.manage_theme_options',
'tab' => 'cms::lang.permissions.name',
+ 'roles' => [UserRole::CODE_DEVELOPER, UserRole::CODE_PUBLISHER],
'order' => 100
],
]);
diff --git a/modules/cms/classes/ComponentBase.php b/modules/cms/classes/ComponentBase.php
index 337d6c984..5d959438d 100644
--- a/modules/cms/classes/ComponentBase.php
+++ b/modules/cms/classes/ComponentBase.php
@@ -99,7 +99,14 @@ public function __construct(CodeBase $cmsObject = null, $properties = [])
}
/**
- * Returns information about this component, including name and description.
+ * Returns information about this component.
+ *
+ * This method must be defined in your component and, at a minimum, should return an array with two keys:
+ *
+ * - `name`: The name of your component.
+ * - `description`: The description or purpose of your component.
+ *
+ * @return array
*/
abstract public function componentDetails();
@@ -309,7 +316,7 @@ public function __call($method, $parameters)
catch (BadMethodCallException $ex) {
}
- if (method_exists($this->controller, $method)) {
+ if (isset($this->controller) && method_exists($this->controller, $method)) {
return call_user_func_array([$this->controller, $method], $parameters);
}
diff --git a/modules/cms/classes/Controller.php b/modules/cms/classes/Controller.php
index cd6c1bf8f..f982b6502 100644
--- a/modules/cms/classes/Controller.php
+++ b/modules/cms/classes/Controller.php
@@ -1336,7 +1336,7 @@ public function getLayout()
* @param mixed $name Specifies the Cms Page file name.
* @param array $parameters Route parameters to consider in the URL.
* @param bool $routePersistence By default the existing routing parameters will be included
- * @return string
+ * @return string|null
*/
public function pageUrl($name, $parameters = [], $routePersistence = true)
{
@@ -1519,7 +1519,7 @@ public function findComponentByHandler($handler)
/**
* Searches the layout and page components by a partial file
* @param string $partial
- * @return ComponentBase The component object, if found
+ * @return ComponentBase|null The component object, if found
*/
public function findComponentByPartial($partial)
{
diff --git a/modules/cms/classes/Page.php b/modules/cms/classes/Page.php
index 4186fd58a..610030f6b 100644
--- a/modules/cms/classes/Page.php
+++ b/modules/cms/classes/Page.php
@@ -117,7 +117,7 @@ public static function getNameList()
* Helper that makes a URL for a page in the active theme.
* @param mixed $page Specifies the Cms Page file name.
* @param array $params Route parameters to consider in the URL.
- * @return string
+ * @return string|null
*/
public static function url($page, array $params = [])
{
diff --git a/modules/cms/controllers/ThemeLogs.php b/modules/cms/controllers/ThemeLogs.php
index 504cf4d45..60abff6f6 100644
--- a/modules/cms/controllers/ThemeLogs.php
+++ b/modules/cms/controllers/ThemeLogs.php
@@ -20,19 +20,9 @@ class ThemeLogs extends Controller
*/
public $implement = [
\Backend\Behaviors\FormController::class,
- \Backend\Behaviors\ListController::class
+ \Backend\Behaviors\ListController::class,
];
- /**
- * @var array `FormController` configuration.
- */
- public $formConfig = 'config_form.yaml';
-
- /**
- * @var array `ListController` configuration.
- */
- public $listConfig = 'config_list.yaml';
-
/**
* @var array Permissions required to view this page.
*/
diff --git a/modules/cms/controllers/ThemeOptions.php b/modules/cms/controllers/ThemeOptions.php
index 64afc5c69..a2706215f 100644
--- a/modules/cms/controllers/ThemeOptions.php
+++ b/modules/cms/controllers/ThemeOptions.php
@@ -22,14 +22,9 @@ class ThemeOptions extends Controller
* @var array Extensions implemented by this controller.
*/
public $implement = [
- \Backend\Behaviors\FormController::class
+ \Backend\Behaviors\FormController::class,
];
- /**
- * @var array `FormController` configuration.
- */
- public $formConfig = 'config_form.yaml';
-
/**
* @var array Permissions required to view this page.
*/
diff --git a/modules/cms/controllers/Themes.php b/modules/cms/controllers/Themes.php
index 39521f0ed..8d2df966c 100644
--- a/modules/cms/controllers/Themes.php
+++ b/modules/cms/controllers/Themes.php
@@ -1,5 +1,6 @@
deleteTheme(post('theme'));
- Flash::success(trans('cms::lang.theme.delete_theme_success'));
+ Flash::success(Lang::get('cms::lang.theme.delete_theme_success'));
return Redirect::refresh();
}
@@ -140,15 +142,15 @@ public function index_onCreate()
$data = array_except($data, 'dir_name');
if (!strlen(trim(array_get($data, 'name')))) {
- throw new ValidationException(['name' => trans('cms::lang.theme.create_theme_required_name')]);
+ throw new ValidationException(['name' => Lang::get('cms::lang.theme.create_theme_required_name')]);
}
if (!preg_match('/^[a-z0-9\_\-]+$/i', $newDirName)) {
- throw new ValidationException(['dir_name' => trans('cms::lang.theme.dir_name_invalid')]);
+ throw new ValidationException(['dir_name' => Lang::get('cms::lang.theme.dir_name_invalid')]);
}
if (File::isDirectory($destinationPath)) {
- throw new ValidationException(['dir_name' => trans('cms::lang.theme.dir_name_taken')]);
+ throw new ValidationException(['dir_name' => Lang::get('cms::lang.theme.dir_name_taken')]);
}
File::makeDirectory($destinationPath);
@@ -162,7 +164,7 @@ public function index_onCreate()
$theme = CmsTheme::load($newDirName);
$theme->writeConfig($data);
- Flash::success(trans('cms::lang.theme.create_theme_success'));
+ Flash::success(Lang::get('cms::lang.theme.create_theme_success'));
return Redirect::refresh();
}
@@ -197,11 +199,11 @@ public function index_onDuplicateTheme()
$destinationPath = themes_path().'/'.$newDirName;
if (!preg_match('/^[a-z0-9\_\-]+$/i', $newDirName)) {
- throw new ValidationException(['new_dir_name' => trans('cms::lang.theme.dir_name_invalid')]);
+ throw new ValidationException(['new_dir_name' => Lang::get('cms::lang.theme.dir_name_invalid')]);
}
if (File::isDirectory($destinationPath)) {
- throw new ValidationException(['new_dir_name' => trans('cms::lang.theme.dir_name_taken')]);
+ throw new ValidationException(['new_dir_name' => Lang::get('cms::lang.theme.dir_name_taken')]);
}
File::copyDirectory($sourcePath, $destinationPath);
@@ -209,7 +211,7 @@ public function index_onDuplicateTheme()
$newName = $newTheme->getConfigValue('name') . ' - Copy';
$newTheme->writeConfig(['name' => $newName]);
- Flash::success(trans('cms::lang.theme.duplicate_theme_success'));
+ Flash::success(Lang::get('cms::lang.theme.duplicate_theme_success'));
return Redirect::refresh();
}
@@ -265,6 +267,10 @@ protected function makeExportFormWidget($theme)
public function index_onLoadImportForm()
{
+ if (CmsHelper::safeModeEnabled()) {
+ throw new ApplicationException(Lang::get('cms::lang.cms_object.safe_mode_enabled'));
+ }
+
$theme = $this->findThemeObject();
$this->vars['widget'] = $this->makeImportFormWidget($theme);
$this->vars['themeDir'] = $theme->getDirName();
@@ -274,13 +280,17 @@ public function index_onLoadImportForm()
public function index_onImport()
{
+ if (CmsHelper::safeModeEnabled()) {
+ throw new ApplicationException(Lang::get('cms::lang.cms_object.safe_mode_enabled'));
+ }
+
$theme = $this->findThemeObject();
$widget = $this->makeImportFormWidget($theme);
$model = new ThemeImport;
$model->import($theme, $widget->getSaveData(), $widget->getSessionKey());
- Flash::success(trans('cms::lang.theme.import_theme_success'));
+ Flash::success(Lang::get('cms::lang.theme.import_theme_success'));
return Redirect::refresh();
}
@@ -306,7 +316,7 @@ protected function findThemeObject($name = null)
}
if (!$name || (!$theme = CmsTheme::load($name))) {
- throw new ApplicationException(trans('cms::lang.theme.not_found_name', ['name' => $name]));
+ throw new ApplicationException(Lang::get('cms::lang.theme.not_found_name', ['name' => $name]));
}
return $theme;
diff --git a/modules/cms/lang/fa/lang.php b/modules/cms/lang/fa/lang.php
index 6015be3c4..914a4145b 100644
--- a/modules/cms/lang/fa/lang.php
+++ b/modules/cms/lang/fa/lang.php
@@ -1,5 +1,4 @@
[
'invalid_file' => 'نام :name برای فایل نام معتبر است. نام فایل میتواند شامل کاراکتر انگلیسی ، خط تیره و نقطه باشد. بعنوان مثال page.htm، page و subdirectory/page',
@@ -16,7 +15,7 @@
'dashboard' => [
'active_theme' => [
'widget_title_default' => 'وب سایت',
- 'online' => 'online',
+ 'online' => 'برخط',
'maintenance' => 'در حال به روز رسانی',
'manage_themes' => 'مدیریت قالب ها',
'customize_theme' => 'سفارشی سازی قالب'
@@ -100,6 +99,13 @@
'is_enabled' => 'فعال سازی حالت تعمیرات',
'is_enabled_comment' => 'اگر فعال شود کاربران به این صفحه هدایت خواهند شد.',
'hint' => 'حالت تعمیرات کاربرانی را که در بخش مدیریت وارد نشده اند را به صفحه تعمیرات منتقل می کند.',
+ 'allowed_ips' => [
+ 'name' => 'آی پی آدرس های مجاز',
+ 'description' => 'آدرسهای آی پی که مجاز به مشاهده سایت در هنگام فعال بودن حالت تعمیر و نگهداری هستند',
+ 'prompt' => 'افزودن آی پی آدرس',
+ 'ip' => 'آی پی آدرس',
+ 'label' => 'توضیحات',
+ ],
],
'page' => [
'not_found_name' => "صفحه ای با نام ':name' یافت نشد",
@@ -210,7 +216,7 @@
'move' => 'جابحایی',
'select' => 'انتخاب',
'new' => 'فایل جدید',
- 'invalid_path' => 'مسیر می تواند فقط شامل اعداد، حروف لاتین، حط فاصله و این کاراکتر ها باشد: ._-/',
+ 'invalid_path' => 'مسیر می تواند فقط شامل اعداد، حروف لاتین، خط فاصله و این کاراکتر ها باشد: ._-/',
'error_deleting_file' => 'در حذف فایل :name مشکلی به وجود آمده است.',
'error_deleting_dir_not_empty' => 'در حذف پوشه ی :name مشکلی به وجود آمده است. پوشه خالی نیست.',
'error_deleting_dir' => 'خطایی در حذف :name به وجود آمده است.',
@@ -295,6 +301,6 @@
'preview_title' => 'تغییرات قالب',
'template_updated' => 'قالب به روزرسانی شد',
'template_created' => 'قالب جدید ایجاد شد',
- 'template_deleted' => 'قالب جذف شد',
+ 'template_deleted' => 'قالب حذف شد',
],
];
diff --git a/modules/cms/lang/fr/lang.php b/modules/cms/lang/fr/lang.php
index 9f3dba60a..f2651b97a 100644
--- a/modules/cms/lang/fr/lang.php
+++ b/modules/cms/lang/fr/lang.php
@@ -3,9 +3,9 @@
return [
'cms_object' => [
'invalid_file' => 'Nom de fichier invalide : :name. Les noms de fichiers ne peuvent contenir que des caractères alphanumériques, des tirets bas, des tirets et des points. Voici des exemples de noms de fichiers valides : page.htm, ma-page, sous_repertoire/nouvelle.page',
- 'invalid_property' => 'L’attribut ":name" ne peut pas être défini',
+ 'invalid_property' => 'L\'attribut ":name" ne peut pas être défini',
'file_already_exists' => 'Le fichier ":name" existe déjà.',
- 'error_saving' => 'Erreur lors de l’enregistrement du fichier ":name". Veuillez vérifier les permissions en écriture.',
+ 'error_saving' => 'Erreur lors de l\'enregistrement du fichier ":name". Veuillez vérifier les permissions en écriture.',
'error_creating_directory' => 'Erreur lors de la création du répertoire :name. Veuillez vérifier les permissions en écriture.',
'invalid_file_extension' => 'Extension de fichier invalide : :invalid. Les extensions autorisées sont : :allowed.',
'error_deleting' => 'Erreur lors de la suppression du modèle ":name". Veuillez vérifier les permissions en écriture.',
@@ -23,16 +23,16 @@
]
],
'theme' => [
- 'not_found_name' => 'Le thème ":name" n’a pas été trouvé.',
+ 'not_found_name' => 'Le thème ":name" n\'a pas été trouvé.',
'by_author' => 'Par :name',
'active' => [
- 'not_set' => 'Aucun thème n’est activé.',
+ 'not_set' => 'Aucun thème n\'est activé.',
'not_found' => 'Le thème activé est introuvable.',
],
'edit' => [
- 'not_set' => 'Le thème à modifier n’est pas activé.',
+ 'not_set' => 'Le thème à modifier n\'est pas activé.',
'not_found' => 'Le thème à modifier est introuvable.',
- 'not_match' => 'L’objet auquel vous souhaitez accéder n’appartient pas au thème en cours de modification. Veuillez recharger la page.'
+ 'not_match' => 'L\'objet auquel vous souhaitez accéder n\'appartient pas au thème en cours de modification. Veuillez recharger la page.'
],
'settings_menu' => 'Thème frontal',
'settings_menu_description' => 'Aperçu des thèmes installés et sélection du thème actif.',
@@ -43,12 +43,12 @@
'author_placeholder' => 'Nom de la personne ou de la compagnie',
'description_label' => 'Description',
'description_placeholder' => 'Description du thème',
- 'homepage_label' => 'Page d’accueil',
+ 'homepage_label' => 'Page d\'accueil',
'homepage_placeholder' => 'Adresse URL du site Web',
'code_label' => 'Code',
'code_placeholder' => 'Un nom de code unique pour la distribution de ce thème',
'preview_image_label' => 'Aperçu',
- 'preview_image_placeholder' => 'Chemin de l’aperçu.',
+ 'preview_image_placeholder' => 'Chemin de l\'aperçu.',
'dir_name_label' => 'Nom du répertoire',
'dir_name_create_label' => 'Le répertoire de destination du thème',
'theme_label' => 'Thème',
@@ -79,7 +79,7 @@
'export_folders_comment' => 'Sélectionner les répertoires du thème à exporter',
'delete_button' => 'Supprimer',
'delete_confirm' => 'Confirmer la suppression de ce thème ? Cette action est irréversible !',
- 'delete_active_theme_failed' => 'Impossible de supprimer le thème actif, merci d’activer un autre thème au préalable.',
+ 'delete_active_theme_failed' => 'Impossible de supprimer le thème actif, merci d\'activer un autre thème au préalable.',
'delete_theme_success' => 'Thème supprimé avec succès !',
'create_title' => 'Créer un thème',
'create_button' => 'Créer',
@@ -99,7 +99,14 @@
'settings_menu_description' => 'Configurer la page de maintenance et ajuster ses options.',
'is_enabled' => 'Activer la maintenance',
'is_enabled_comment' => 'Si activé, la page choisie ci-dessous sera affichée pour les visiteurs du site Web.',
- 'hint' => 'Le mode maintenance affichera la page de maintenance pour les visiteurs qui ne sont pas authentifiés dans l’interface d’administration.'
+ 'hint' => 'Le mode maintenance affichera la page de maintenance pour les visiteurs qui ne sont pas authentifiés dans l\'interface d\'administration.',
+ 'allowed_ips' => [
+ 'name' => 'Adresses IP autorisées',
+ 'description' => 'Adresses IP autorisées à consulter le site lorsque le mode de maintenance est actif.',
+ 'prompt' => 'Ajouter une adresse IP',
+ 'ip' => 'Adresse IP',
+ 'label' => 'Description',
+ ],
],
'page' => [
'not_found_name' => 'La page ":name" est introuvable',
@@ -113,9 +120,9 @@
],
'menu_label' => 'Pages',
'unsaved_label' => 'Page(s) non enregistrée(s)',
- 'no_list_records' => 'Aucune page n’a été trouvée',
+ 'no_list_records' => 'Aucune page n\'a été trouvée',
'new' => 'Nouvelle page',
- 'invalid_url' => 'Format d’adresse URL invalide. L’adresse URL doit commencer par un / et peut contenir des chiffres, des lettres et les symboles suivants : ._-[]:?|/+*^$',
+ 'invalid_url' => 'Format d\'adresse URL invalide. L\'adresse URL doit commencer par un / et peut contenir des chiffres, des lettres et les symboles suivants : ._-[]:?|/+*^$',
'delete_confirm_multiple' => 'Confirmer la suppression des pages sélectionnées ?',
'delete_confirm_single' => 'Confirmer la suppression de cette page ?',
'no_layout' => '-- aucune maquette --',
@@ -128,7 +135,7 @@
'not_found_name' => 'La maquette ":name" est introuvable',
'menu_label' => 'Maquettes',
'unsaved_label' => 'Maquette(s) non enregistrée(s)',
- 'no_list_records' => 'Aucune maquette n’a été trouvée',
+ 'no_list_records' => 'Aucune maquette n\'a été trouvée',
'new' => 'Nouvelle maquette',
'delete_confirm_multiple' => 'Confirmer la suppression des maquettes sélectionnées ?',
'delete_confirm_single' => 'Confirmer la suppression de cette maquette ?'
@@ -138,7 +145,7 @@
'invalid_name' => 'Nom du modèle partiel invalide : :name.',
'menu_label' => ' Modèles partiels',
'unsaved_label' => 'Modèle(s) partiel(s) non enregistré(s)',
- 'no_list_records' => 'Aucun modèle partiel n’a été trouvé',
+ 'no_list_records' => 'Aucun modèle partiel n\'a été trouvé',
'delete_confirm_multiple' => 'Confirmer la suppression des modèles partiels sélectionnés ?',
'delete_confirm_single' => 'Confirmer la suppression de ce modèle partiel ?',
'new' => 'Nouveau modèle partiel'
@@ -147,7 +154,7 @@
'not_found_name' => 'Le fichier de contenu ":name" est introuvable.',
'menu_label' => 'Contenu',
'unsaved_label' => 'Contenu non enregistré',
- 'no_list_records' => 'Aucun fichier de contenu n’a été trouvé',
+ 'no_list_records' => 'Aucun fichier de contenu n\'a été trouvé',
'delete_confirm_multiple' => 'Confirmer la suppression des fichiers de contenu ou répertoires sélectionnés ?',
'delete_confirm_single' => 'Confirmer la suppression de ce fichier de contenu ?',
'new' => 'Nouveau fichier de contenu'
@@ -212,7 +219,7 @@
'new' => 'Nouveau fichier',
'invalid_path' => 'Le chemin doit contenir uniquement des chiffres, des lettres, des espaces et les symboles suivants : ._-/',
'error_deleting_file' => 'Erreur lors de la suppression du fichier :name.',
- 'error_deleting_dir_not_empty' => 'Erreur lors de la suppression du répertoire :name. Le répertoire n’est pas vide.',
+ 'error_deleting_dir_not_empty' => 'Erreur lors de la suppression du répertoire :name. Le répertoire n\'est pas vide.',
'error_deleting_dir' => 'Erreur lors de la suppression du répertoire :name.',
'invalid_name' => 'Le nom doit contenir uniquement des chiffres, des lettres, des espaces et les symboles suivants : ._-',
'original_not_found' => 'Le fichier original ou son répertoire est introuvable',
@@ -230,7 +237,7 @@
'destination_not_found' => 'Le répertoire de destination est introuvable',
'error_moving_file' => 'Erreur lors du déplacement du fichier :file',
'error_moving_directory' => 'Erreur lors du déplacement du répertoire :dir',
- 'error_deleting_directory' => 'Erreur lors de la suppression du répertoire d’origine :dir',
+ 'error_deleting_directory' => 'Erreur lors de la suppression du répertoire d\'origine :dir',
'no_list_records' => 'Aucun fichier trouvé',
'delete_confirm' => 'Supprimer les fichiers ou répertoires sélectionnés ?',
'path' => 'Chemin'
@@ -238,14 +245,14 @@
'component' => [
'menu_label' => 'Composants',
'unnamed' => 'Sans nom',
- 'no_description' => 'Aucune description n’a été fournie',
+ 'no_description' => 'Aucune description n\'a été fournie',
'alias' => 'Alias',
- 'alias_description' => 'Nom unique fourni lors de l’utilisation du composant sur une page ou une maquette.',
+ 'alias_description' => 'Nom unique fourni lors de l\'utilisation du composant sur une page ou une maquette.',
'validation_message' => 'Les alias du composant sont requis et doivent contenir uniquement des symboles latins, des chiffres et des tirets bas. Les alias doivent commencer par un symbole latin.',
- 'invalid_request' => 'Le modèle ne peut être enregistré car les données d’un composant ne sont pas valides.',
- 'no_records' => 'Aucun composant n’a été trouvé',
+ 'invalid_request' => 'Le modèle ne peut être enregistré car les données d\'un composant ne sont pas valides.',
+ 'no_records' => 'Aucun composant n\'a été trouvé',
'not_found' => 'Le composant ":name" est introuvable.',
- 'no_default_partial' => 'Ce composant n’as aucun partiel par défaut',
+ 'no_default_partial' => 'Ce composant n\'as aucun partiel par défaut',
'method_not_found' => 'Le composant ":name" ne contient pas de méthode ":method".',
'soft_component' => 'Composant Soft',
'soft_component_description' => 'Ce composant est manquant mais facultatif.',
@@ -269,7 +276,7 @@
'manage_theme_options' => 'Gérer les options de personnalisation du thème actif',
],
'theme_log' => [
- 'hint' => 'Ce journal affiche tous les changements fait sur le thème actif par les administrateurs via le panneau d’administration.',
+ 'hint' => 'Ce journal affiche tous les changements fait sur le thème actif par les administrateurs via le panneau d\'administration.',
'menu_label' => 'Journal du thème',
'menu_description' => 'Affiche la liste des modifications apportées au thème actif.',
'empty_link' => 'Purger le journal du thème',
diff --git a/modules/cms/lang/ja/lang.php b/modules/cms/lang/ja/lang.php
index c754e75de..34914932a 100644
--- a/modules/cms/lang/ja/lang.php
+++ b/modules/cms/lang/ja/lang.php
@@ -11,22 +11,26 @@
'error_deleting' => '":name"一時ファイル削除エラー',
'delete_success' => ':count個のテンプレートを削除しました。',
'file_name_required' => 'ファイル名フィールドが必要です。',
+ 'safe_mode_enabled' => '現在、セーフモードが有効になっています。CMS テンプレートの PHP コードの編集はできません。セーフモードを無効にするには、`cms.enableSafeMode`設定値を`false`に設定します。',
],
'dashboard' => [
'active_theme' => [
'online' => 'オンライン',
'maintenance' => 'メンテナンスモード',
+ 'manage_themes' => 'テーマの管理',
+ 'customize_theme' => 'テーマのカスタマイズ',
]
],
'theme' => [
+ 'not_found_name' => 'テーマ":name"が見つかりませんでした。',
'active' => [
- 'not_set' => "アクティブなテーマが設定されていません。",
+ 'not_set' => 'アクティブなテーマが設定されていません。',
'not_found' => 'アクティブなテーマが見つかりません。',
],
'edit' => [
- 'not_set' => "編集テーマが設定されていません。",
- 'not_found' => "編集テーマが見つかりません。",
- 'not_match' => "アクセスしようとしてるオブジェクトは、編集中のテーマに所属していません。ページを再読み込みしてください。",
+ 'not_set' => '編集テーマが設定されていません。',
+ 'not_found' => '編集テーマが見つかりません。',
+ 'not_match' => 'アクセスしようとしてるオブジェクトは、編集中のテーマに所属していません。ページを再読み込みしてください。',
],
'settings_menu' => 'フロントエンドのテーマ',
'settings_menu_description' => 'インストール済みのテーマのプレビュー一覧とアクティブテーマの選択。',
@@ -34,21 +38,84 @@
'activate_button' => 'これをアクティブにする',
'active_button' => '現在アクティブ中です',
'customize_button' => 'カスタマイズ',
+ 'default_tab' => 'プロパティ',
+ 'name_label' => '名前',
+ 'name_create_placeholder' => '新しいテーマ名',
+ 'author_label' => '作者',
+ 'author_placeholder' => '個人名または会社名',
+ 'description_label' => '説明',
+ 'description_placeholder' => 'テーマの説明',
+ 'homepage_label' => 'ホームページ',
+ 'homepage_placeholder' => 'ウェブサイトのURL',
+ 'code_label' => 'コード',
+ 'code_placeholder' => '配布に使用されるこのテーマ独自のコード',
+ 'preview_image_label' => 'プレビュー画像',
+ 'preview_image_placeholder' => 'テーマのプレビュー画像のパス',
+ 'dir_name_label' => 'ディレクトリ名',
+ 'dir_name_create_label' => 'テーマディレクトリの保存先',
+ 'theme_label' => 'テーマ',
+ 'theme_title' => 'テーマ',
+ 'customize_theme' => 'テーマのカスタマイズ',
+ 'duplicate_button' => '複製',
+ 'duplicate_title' => 'テーマを複製する',
+ 'duplicate_theme_success' => 'テーマが複製されました!',
+ 'manage_button' => '管理',
+ 'manage_title' => 'テーマの管理',
+ 'edit_properties_title' => 'テーマ',
+ 'edit_properties_button' => 'プロパティを編集する',
+ 'save_properties' => 'プロパティを保存',
+ 'import_button' => 'インポート',
+ 'import_title' => 'テーマのインポート',
+ 'import_theme_success' => 'テーマがインポートされました!',
+ 'import_uploaded_file' => 'テーマのアーカイブファイル',
+ 'import_overwrite_label' => '既存ファイルを上書きする',
+ 'import_overwrite_comment' => '新しいファイルだけをインポートするにはこのボックスをオフにします',
+ 'import_folders_label' => 'フォルダー',
+ 'import_folders_comment' => 'インポートしたいテーマフォルダを選択してください',
+ 'export_button' => 'エクスポート',
+ 'export_title' => 'テーマのエクスポート',
+ 'export_folders_label' => 'フォルダー',
+ 'export_folders_comment' => 'エクスポートしたいテーマフォルダを選択してください',
+ 'delete_button' => '削除',
+ 'delete_confirm' => 'このテーマを削除しますか?元に戻せません!',
+ 'delete_active_theme_failed' => 'アクティブなテーマを削除できません、まず他のテーマをアクティブにしてみてください',
+ 'delete_theme_success' => 'テーマが削除されました!',
+ 'create_title' => 'テーマを作成',
+ 'create_button' => '作成',
+ 'create_new_blank_theme' => '新しい空白のテーマを作成する',
+ 'create_theme_success' => 'テーマが作成されました!',
+ 'create_theme_required_name' => 'テーマの名前を指定してください',
+ 'new_directory_name_label' => 'テーマディレクトリ',
+ 'new_directory_name_comment' => '複製されたテーマに新しいディレクトリ名を提供します',
+ 'dir_name_invalid' => '名前には数字、ラテン文字、および以下の記号のみを含めることができます。_-',
+ 'dir_name_taken' => '希望のテーマディレクトリは既に存在します。',
+ 'find_more_themes' => '他のテーマを検索する',
+ 'saving' => 'テーマの保存...',
+ 'return' => 'テーマ一覧に戻る',
],
'maintenance' => [
'settings_menu' => 'メンテナンスモード',
'settings_menu_description' => 'メンテナンスモードページの設定と切り替えをします。',
'is_enabled' => 'メンテナンスモードを有効にする',
'is_enabled_comment' => 'メンテナンスモードの時、Webサイト訪問者が見るページを選択してください。',
+ 'hint' => 'メンテナンスモードは、バックエンド領域にサインインしていない訪問者にメンテナンスページを表示します',
+ 'allowed_ips' => [
+ 'name' => '許可されたIPアドレス',
+ 'description' => 'メンテナンスモードが有効な間、サイトの閲覧が許可されるIPアドレス',
+ 'prompt' => 'IPアドレスを追加',
+ 'ip' => 'IPアドレス',
+ 'label' => '説明',
+ ],
],
'page' => [
+ 'not_found_name' => "ページ ':name' が見つかりません。",
'not_found' => [
- 'label' => "ページが見つかりません。",
- 'help' => "要求されているページが見つかりません。",
+ 'label' => 'ページが見つかりません。',
+ 'help' => '要求されているページが見つかりません。',
],
'custom_error' => [
- 'label' => "ページエラー。",
- 'help' => "申し訳ありません。何かが間違っているようで、ページが表示できません。",
+ 'label' => 'ページエラー。',
+ 'help' => '申し訳ありません。何かが間違っているようで、ページが表示できません。',
],
'menu_label' => 'ページ',
'unsaved_label' => '保存されていないページ',
@@ -58,6 +125,10 @@
'delete_confirm_multiple' => '指定した全ページを本当に削除しますか?',
'delete_confirm_single' => '本当にこのページを削除しますか?',
'no_layout' => '-- レイアウト無し --',
+ 'cms_page' => 'CMSページ',
+ 'title' => 'ページタイトル',
+ 'url' => 'ページURL',
+ 'file_name' => 'ページファイル名',
],
'layout' => [
'not_found_name' => "レイアウト':name'が見つかりません。",
@@ -92,7 +163,7 @@
'not_found' => "':name' Ajaxハンドラが見つかりません。",
],
'cms' => [
- 'menu_label' => "CMS",
+ 'menu_label' => 'CMS',
],
'sidebar' => [
'add' => '追加',
@@ -117,9 +188,21 @@
'hidden_comment' => 'フロントエンドでページを表示しないようにします。バックエンドでのみ閲覧・編集できます。',
'enter_fullscreen' => '全画面モードに移行する',
'exit_fullscreen' => '全画面モードを解除する',
+ 'open_searchbox' => '検索ボックスを開く',
+ 'close_searchbox' => '検索ボックスを閉じる',
+ 'open_replacebox' => '置換ボックスを開く',
+ 'close_replacebox' => '置換ボックスを閉じる',
+ 'commit' => 'コミット',
+ 'reset' => 'リセット',
+ 'commit_confirm' => 'このファイルへの変更をファイルシステムにコミットしてよろしいですか?これは、ファイルシステム上の既存のファイルを上書きします。',
+ 'reset_confirm' => '本当にこのファイルをファイルシステム上のコピーにリセットしますか?これは、ファイルシステム上にあるファイルに完全に置き換えます。',
+ 'committing' => 'コミット',
+ 'resetting' => 'リセット',
+ 'commit_success' => 'タイプはファイルシステムにコミットされました。',
+ 'reset_success' => 'ファイルシステムのバージョンに :type がリセットされました。',
],
'asset' => [
- 'menu_label' => "アセット",
+ 'menu_label' => 'アセット',
'unsaved_label' => '保存されていないアセット',
'drop_down_add_title' => '追加...',
'drop_down_operation_title' => 'アクション...',
@@ -154,24 +237,32 @@
'error_moving_file' => ':fileファイル移動エラー',
'error_moving_directory' => ':dirディレクトリー移動エラー',
'error_deleting_directory' => '移動元:dirディレクトリー削除エラー',
+ 'no_list_records' => 'ファイルが見つかりません。',
+ 'delete_confirm' => 'ファイルが見つかりません、選択したファイルやディレクトリを削除しましたか?',
'path' => 'パス',
],
'component' => [
- 'menu_label' => "コンポーネント",
- 'unnamed' => "名前なし",
- 'no_description' => "説明なし",
- 'alias' => "エイリアス",
- 'alias_description' => "ページやレイアウトコードの中で使用される、一意のコンポーネント名。",
- 'validation_message' => "ラテン文字、数字、下線(_)で構成された、コンポーネントエイリアスが必要です。エイリアスはラテン文字で始まらなくてなりません。",
- 'invalid_request' => "コンポーネントデータが正しくないため、テンプレートは保存できません。",
+ 'menu_label' => 'コンポーネント',
+ 'unnamed' => '名前なし',
+ 'no_description' => '説明なし',
+ 'alias' => 'エイリアス',
+ 'alias_description' => 'ページやレイアウトコードの中で使用される、一意のコンポーネント名。',
+ 'validation_message' => 'ラテン文字、数字、下線(_)で構成された、コンポーネントエイリアスが必要です。エイリアスはラテン文字で始まらなくてなりません。',
+ 'invalid_request' => 'コンポーネントデータが正しくないため、テンプレートは保存できません。',
'no_records' => 'コンポーネントが見つかりません。',
'not_found' => "':name'コンポーネントが見つかりません。",
+ 'no_default_partial' => 'このコンポーネントは、「デフォルト」のパーシャルを持ちません。',
'method_not_found' => "':name'コンポーネントは、':method'メソッドを持っていません。",
+ 'soft_component' => 'ソフトコンポーネント',
+ 'soft_component_description' => 'このコンポーネントは欠落しているが、オプションである。',
],
'template' => [
- 'invalid_type' => "未知のテンプレートタイプ。",
- 'not_found' => "リクエストされたテンプレートが見つかりません。",
- 'saved'=> "テンプレートを保存しました。",
+ 'invalid_type' => '未知のテンプレートタイプ。',
+ 'not_found' => 'リクエストされたテンプレートが見つかりません。',
+ 'saved'=> 'テンプレートを保存しました。',
+ 'no_list_records' => 'レコードが見つかりません',
+ 'delete_confirm' => '選択したテンプレートを削除しますか?',
+ 'order_by' => 'Order by',
],
'permissions' => [
'name' => 'CMS',
@@ -181,5 +272,35 @@
'manage_layouts' => 'レイアウト管理',
'manage_partials' => 'パーシャル管理',
'manage_themes' => 'テーマ管理',
+ 'manage_theme_options' => 'アクティブなテーマのカスタマイズ・オプションを設定する',
+ ],
+ 'theme_log' => [
+ 'hint' => 'このログは、バックエンドエリアで管理者がテーマに加えた変更を表示します。',
+ 'menu_label' => 'テーマログ',
+ 'menu_description' => 'アクティブなテーマに加えられた変更を表示します',
+ 'empty_link' => 'テーマログを空にする',
+ 'empty_loading' => 'テーマログを空にしています...',
+ 'empty_success' => 'テーマログを空にしました',
+ 'return_link' => 'テーマログに戻る',
+ 'id' => 'ID',
+ 'id_label' => 'ログID',
+ 'created_at' => '日付と時刻',
+ 'user' => 'ユーザー',
+ 'type' => 'タイプ',
+ 'type_create' => '作成',
+ 'type_update' => '更新',
+ 'type_delete' => '削除',
+ 'theme_name' => 'テーマ',
+ 'theme_code' => 'テーマコード',
+ 'old_template' => 'テンプレート(旧)',
+ 'new_template' => 'テンプレート(新規)',
+ 'template' => 'テンプレート',
+ 'diff' => '変更点',
+ 'old_value' => '古い値',
+ 'new_value' => '新しい値',
+ 'preview_title' => 'テンプレートの変更',
+ 'template_updated' => 'テンプレートが更新されました',
+ 'template_created' => 'テンプレートが作成されました',
+ 'template_deleted' => 'テンプレートは削除されました',
],
];
diff --git a/modules/cms/lang/ru/lang.php b/modules/cms/lang/ru/lang.php
index a6e695113..fb526a514 100644
--- a/modules/cms/lang/ru/lang.php
+++ b/modules/cms/lang/ru/lang.php
@@ -132,13 +132,13 @@
'file_name' => 'Имя файла страницы',
],
'layout' => [
- 'not_found_name' => "Не удалось найти макет с именем :name.",
- 'menu_label' => 'Макеты',
- 'unsaved_label' => 'Несохранённый(е) макет(ы)',
- 'no_list_records' => 'Макет не найдено',
- 'new' => 'Новый макет',
- 'delete_confirm_multiple' => 'Удалить выделенные макеты?',
- 'delete_confirm_single' => 'Удалить этот макет?',
+ 'not_found_name' => "Не удалось найти шаблон с именем :name.",
+ 'menu_label' => 'Шаблоны',
+ 'unsaved_label' => 'Несохранённый(е) шаблон(ы)',
+ 'no_list_records' => 'Шаблонов не найдено',
+ 'new' => 'Новый шаблон',
+ 'delete_confirm_multiple' => 'Удалить выделенные шаблоны?',
+ 'delete_confirm_single' => 'Удалить этот шаблон?',
],
'partial' => [
'not_found_name' => "Не удалось найти фрагмент с именем :name.",
@@ -152,8 +152,8 @@
],
'content' => [
'not_found_name' => "Не удалось найти контент-файл: ':name'.",
- 'menu_label' => 'Контент',
- 'unsaved_label' => 'Несохранённый контент',
+ 'menu_label' => 'Content',
+ 'unsaved_label' => 'Несохранённый контент-файл',
'no_list_records' => 'Контент-файлы не найдены',
'delete_confirm_multiple' => 'Вы действительно хотите удалить выделенные файлы?',
'delete_confirm_single' => 'Вы действительно хотите удалить этот файл?',
@@ -203,7 +203,7 @@
'reset_success' => ':type был сброшен до версии из файловой системы',
],
'asset' => [
- 'menu_label' => 'Ассеты',
+ 'menu_label' => 'Assets',
'unsaved_label' => 'Несохранённый(е) файл(ы)',
'drop_down_add_title' => 'Добавить...',
'drop_down_operation_title' => 'Действие...',
diff --git a/modules/cms/lang/uk/lang.php b/modules/cms/lang/uk/lang.php
index 2a7f64014..98a83e7d9 100644
--- a/modules/cms/lang/uk/lang.php
+++ b/modules/cms/lang/uk/lang.php
@@ -2,7 +2,7 @@
return [
'cms_object' => [
- 'invalid_file' => 'Помилка в імені файлу: :name. Імена файлів можуть містити тільки латинські букви, цифри, знаки підкреслення і точки. Приклад правильних імен файлів: page.htm, page, subdirectory/page',
+ 'invalid_file' => 'Помилка в назві файлу: :name. Назви файлів можуть містити тільки латинські букви, цифри, знаки підкреслення і крапки. Приклад правильних назв файлів: page.htm, page, subdirectory/page',
'invalid_property' => "Параметр ':name' не можна змінити.",
'file_already_exists' => "Файл ':name' вже існує.",
'error_saving' => "Помилка збереження файлу ':name'. Будь ласка, перевірте права на запис.",
@@ -11,7 +11,7 @@
'error_deleting' => "Неможливо видалити файл шаблону ':name'. Будь ласка, перевірте права на запис.",
'delete_success' => 'Шаблони були успішно видалені: :count.',
'file_name_required' => 'Будь ласка, вкажіть ім\'я файлу шаблону.',
- 'safe_mode_enabled' => 'Безпечний режим в даний момент включений.'
+ 'safe_mode_enabled' => 'Безпечний режим в даний момент включений.',
],
'dashboard' => [
'active_theme' => [
@@ -19,7 +19,7 @@
'online' => 'Онлайн',
'maintenance' => 'В розробці',
'manage_themes' => 'Керування темами',
- 'customize_theme' => 'Налаштування теми'
+ 'customize_theme' => 'Налаштування теми',
],
],
'theme' => [
@@ -27,12 +27,12 @@
'by_author' => 'Автор :name',
'active' => [
'not_set' => 'Активна тема не встановлена.',
- 'not_found' => 'Активна тема не знайдена.'
+ 'not_found' => 'Активна тема не знайдена.',
],
'edit' => [
'not_set' => 'Тема для редагування не встановлена.',
'not_found' => 'Тема для редагування не знайдена.',
- 'not_match' => 'Об\'єкт, який ви намагаєтеся відкрити, не належить редагованої темі. Будь ласка, оновіть сторінку.'
+ 'not_match' => 'Об\'єкт, який ви намагаєтеся відкрити, не належить редагованої темі. Будь ласка, оновіть сторінку.',
],
'settings_menu' => 'Фронтенд теми',
'settings_menu_description' => 'Керування інтерфейсною темою',
@@ -92,24 +92,31 @@
'dir_name_taken' => 'Зазначений каталог вже існує.',
'find_more_themes' => 'Знайти нові теми',
'saving' => 'Збереження теми...',
- 'return' => 'Повернутися до списку тем'
+ 'return' => 'Повернутися до списку тем',
],
'maintenance' => [
'settings_menu' => 'Режим обслуговування',
'settings_menu_description' => 'Керування режимом роботи сайту.',
'is_enabled' => 'Увімкнути режим обслуговування',
'is_enabled_comment' => 'При активації цього режиму відвідувачі сайту побачать сторінку обрану нижче.',
- 'hint' => 'Режим обслуговування покаже сторінку обслуговування для відвідувачів, що не авторизувалися в CMS.'
+ 'hint' => 'Режим обслуговування покаже сторінку обслуговування для відвідувачів, що не авторизувалися в CMS.',
+ 'allowed_ips' => [
+ 'name' => 'Дозволені IP-адреси',
+ 'description' => 'IP-адреси з яких дозволено перегляд сайту коли увімкнено режим обслуговування',
+ 'prompt' => 'Додати IP-адресу',
+ 'ip' => 'IP-адреса',
+ 'label' => 'Опис',
+ ],
],
'page' => [
'not_found_name' => "Сторінка ':name' не знайдена",
'not_found' => [
'label' => 'Сторінка не знайдена',
- 'help' => 'Запрошення сторінка не знайдена.'
+ 'help' => 'Запрошення сторінка не знайдена.',
],
'custom_error' => [
'label' => 'Помилка на сторінці',
- 'help' => 'На жаль, сторінка не може бути відображена через помилку.'
+ 'help' => 'На жаль, сторінка не може бути відображена через помилку.',
],
'menu_label' => 'Сторінки',
'unsaved_label' => 'Незбережена(і) сторінка(и)',
@@ -122,7 +129,7 @@
'cms_page' => 'CMS сторінка',
'title' => 'Заголовок сторінки',
'url' => 'URL сторінки',
- 'file_name' => 'Файл сторінки'
+ 'file_name' => 'Файл сторінки',
],
'layout' => [
'not_found_name' => "Не вдалося знайти шаблон (layout) з ім'ям :name.",
@@ -131,7 +138,7 @@
'no_list_records' => 'Шаблони не знайдені',
'new' => 'Новий шаблон',
'delete_confirm_multiple' => 'Ви дійсно хочете видалити виділені шаблони?',
- 'delete_confirm_single' => 'Ви дійсно хочете видалити цей шаблон?'
+ 'delete_confirm_single' => 'Ви дійсно хочете видалити цей шаблон?',
],
'partial' => [
'not_found_name' => "Не вдалося знайти шаблон (partial) з ім'ям :name.",
@@ -141,7 +148,7 @@
'no_list_records' => 'Фрагменти не знайдені',
'delete_confirm_multiple' => 'Ви дійсно хочете видалити виділені фрагменти?',
'delete_confirm_single' => 'Ви дійсно хочете видалити цей фрагмент?',
- 'new' => 'Новий фрагмент'
+ 'new' => 'Новий фрагмент',
],
'content' => [
'not_found_name' => "Не вдалося знайти файл вмісту (content file): ':name'.",
@@ -150,18 +157,18 @@
'no_list_records' => 'Файли з вмістом не знайдені',
'delete_confirm_multiple' => 'Ви дійсно хочете видалити виділені файли?',
'delete_confirm_single' => 'Ви дійсно хочете видалити цей файл?',
- 'new' => 'Новий файл вмісту'
+ 'new' => 'Новий файл вмісту',
],
'ajax_handler' => [
'invalid_name' => 'Помилка в імені обробника AJAX: :name.',
'not_found' => "Обробник AJAX не найден: ':name'.",
],
'cms' => [
- 'menu_label' => 'CMS'
+ 'menu_label' => 'CMS',
],
'sidebar' => [
'add' => 'Створити',
- 'search' => 'Пошук...'
+ 'search' => 'Пошук...',
],
'editor' => [
'settings' => 'Налаштування',
@@ -185,7 +192,15 @@
'open_searchbox' => 'Відкрити вікно пошуку',
'close_searchbox' => 'Закрити вікно пошуку',
'open_replacebox' => 'Відкрито вікно замінити',
- 'close_replacebox' => 'Закрити вікно замінити'
+ 'close_replacebox' => 'Закрити вікно замінити',
+ 'commit' => 'Зафіксувати',
+ 'reset' => 'Скидання',
+ 'commit_confirm' => 'Ви впевнені, що хочете зберегти зміни цього файлу у файловій системі? Це перезапише існуючий у файловій системі файл',
+ 'reset_confirm' => 'Ви впевнені, що хочете скинути файл до копії, яка знаходиться у файловій системі? Це повністю замінить його файлом, який знаходиться у файловій системі.',
+ 'committing' => 'Зафіксувати',
+ 'resetting' => 'Скинути',
+ 'commit_success' => ':type був зафіксований у файловій системі',
+ 'reset_success' => ':type був скинутий до версії із файлової системи',
],
'asset' => [
'menu_label' => 'Ресурси',
@@ -225,7 +240,7 @@
'error_deleting_directory' => 'Не вдалося видалити директорію :dir',
'no_list_records' => 'Файли не знайдені',
'delete_confirm' => 'Видалити обрані файли або каталоги?',
- 'path' => 'Шлях'
+ 'path' => 'Шлях',
],
'component' => [
'menu_label' => 'Компоненти',
@@ -237,7 +252,10 @@
'invalid_request' => 'Шаблон не може бути збережений, так як містить пошкоджену інформацію про компоненти.',
'no_records' => 'Компоненти не знайдені',
'not_found' => "Компонент ':name' не знайдено.",
+ 'no_default_partial' => "Цей компонент не має 'default' фрагменту (partial)",
'method_not_found' => "Компонент ':name' не містить метод ':method'.",
+ 'soft_component' => 'М\'який компонент',
+ 'soft_component_description' => 'Цей компонент відсутній, але не є обов\'язковим.',
],
'template' => [
'invalid_type' => 'Невідомий тип шаблону.',
@@ -245,7 +263,7 @@
'saved' => 'Шаблон був успішно збережений.',
'no_list_records' => 'Записів не знайдено',
'delete_confirm' => 'Видалити вибрані шаблони?',
- 'order_by' => 'Сортувати за'
+ 'order_by' => 'Сортувати за',
],
'permissions' => [
'name' => 'Керування CMS',
@@ -284,6 +302,6 @@
'preview_title' => 'Зміни в шаблоні',
'template_updated' => 'Шаблон було оновлено',
'template_created' => 'Шаблон був створений',
- 'template_deleted' => 'Шаблон був видалений'
+ 'template_deleted' => 'Шаблон був видалений',
]
];
diff --git a/modules/cms/twig/Extension.php b/modules/cms/twig/Extension.php
index c8e2eeb83..867087888 100644
--- a/modules/cms/twig/Extension.php
+++ b/modules/cms/twig/Extension.php
@@ -73,6 +73,7 @@ public function getTokenParsers()
new PlaceholderTokenParser,
new DefaultTokenParser,
new FrameworkTokenParser,
+ new SnowboardTokenParser,
new ComponentTokenParser,
new FlashTokenParser,
new ScriptsTokenParser,
diff --git a/modules/cms/twig/SnowboardNode.php b/modules/cms/twig/SnowboardNode.php
new file mode 100644
index 000000000..6a0928dea
--- /dev/null
+++ b/modules/cms/twig/SnowboardNode.php
@@ -0,0 +1,65 @@
+ $modules], $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param TwigCompiler $compiler A TwigCompiler instance
+ */
+ public function compile(TwigCompiler $compiler)
+ {
+ $build = Parameter::get('system::core.build', 'winter');
+ $cacheBust = '?v=' . $build;
+ $modules = $this->getAttribute('modules');
+
+ $compiler
+ ->addDebugInfo($this)
+ ->write("\$_minify = ".CombineAssets::class."::instance()->useMinify;" . PHP_EOL);
+
+ $moduleMap = [
+ 'base' => (Config::get('app.debug', false) === true) ? 'snowboard.base.debug' : 'snowboard.base',
+ 'request' => 'snowboard.request',
+ 'attr' => 'snowboard.data-attr',
+ 'extras' => 'snowboard.extras',
+ ];
+ $basePath = Request::getBasePath() . '/modules/system/assets/js/snowboard/build/';
+
+ if (!static::$baseLoaded) {
+ // Add base script
+ $baseJs = $moduleMap['base'];
+ $compiler
+ ->write("echo ''.PHP_EOL;" . PHP_EOL);
+ static::$baseLoaded = true;
+ }
+
+ foreach ($modules as $module) {
+ $moduleJs = $moduleMap[$module];
+ $compiler
+ ->write("echo ''.PHP_EOL;" . PHP_EOL);
+ }
+ }
+}
diff --git a/modules/cms/twig/SnowboardTokenParser.php b/modules/cms/twig/SnowboardTokenParser.php
new file mode 100644
index 000000000..2bfb4d34a
--- /dev/null
+++ b/modules/cms/twig/SnowboardTokenParser.php
@@ -0,0 +1,60 @@
+getLine();
+ $stream = $this->parser->getStream();
+
+ $modules = [];
+
+ do {
+ $token = $stream->next();
+
+ if ($token->getType() === TwigToken::NAME_TYPE) {
+ $modules[] = $token->getValue();
+ }
+ } while ($token->getType() !== TwigToken::BLOCK_END_TYPE);
+
+ // Filter out invalid types
+ $modules = array_filter(
+ array_map(function ($item) {
+ return strtolower($item);
+ }, $modules),
+ function ($item) {
+ return in_array($item, ['request', 'attr', 'extras', 'all']);
+ }
+ );
+
+ if (in_array('all', $modules)) {
+ $modules = [
+ 'request',
+ 'attr',
+ 'extras',
+ ];
+ }
+
+ return new SnowboardNode($modules, $lineno, $this->getTag());
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getTag()
+ {
+ return 'snowboard';
+ }
+}
diff --git a/modules/cms/widgets/AssetList.php b/modules/cms/widgets/AssetList.php
index 6c61fea93..5cc92e131 100644
--- a/modules/cms/widgets/AssetList.php
+++ b/modules/cms/widgets/AssetList.php
@@ -433,7 +433,7 @@ protected function getAssetsPath()
protected function getThemeFileUrl($path)
{
- return Url::to('themes/'.$this->theme->getDirName().'/assets'.$path);
+ return Url::asset('themes/'.$this->theme->getDirName().'/assets'.$path);
}
public function getCurrentRelativePath()
@@ -535,6 +535,13 @@ protected function getDirectoryContents($dir)
}
}
+ // Sort directories & files in alphabetical order
+ $sortByName = function ($a, $b) {
+ return strcmp($a->name, $b->name);
+ };
+ usort($result, $sortByName);
+ usort($files, $sortByName);
+
foreach ($files as $file) {
$result[] = $file;
}
diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php
index 2c85ff576..bffd0eca1 100644
--- a/modules/system/ServiceProvider.php
+++ b/modules/system/ServiceProvider.php
@@ -32,6 +32,7 @@
use Winter\Storm\Router\Helper as RouterHelper;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Schema;
+use System\Classes\MixAssets;
class ServiceProvider extends ModuleServiceProvider
{
@@ -185,7 +186,6 @@ protected function registerMarkupTags()
'link_to_asset' => 'link_to_asset',
'link_to_route' => 'link_to_route',
'link_to_action' => 'link_to_action',
- 'asset' => 'asset',
'action' => 'action',
'url' => 'url',
'route' => 'route',
@@ -213,6 +213,7 @@ protected function registerMarkupTags()
'transchoice' => ['Lang', 'choice'],
'md' => ['Markdown', 'parse'],
'md_safe' => ['Markdown', 'parseSafe'],
+ 'md_line' => ['Markdown', 'parseLine'],
'time_since' => ['System\Helpers\DateTime', 'timeSince'],
'time_tense' => ['System\Helpers\DateTime', 'timeTense'],
]);
@@ -251,32 +252,37 @@ protected function registerConsole()
/*
* Register console commands
*/
- $this->registerConsoleCommand('winter.up', 'System\Console\WinterUp');
- $this->registerConsoleCommand('winter.down', 'System\Console\WinterDown');
- $this->registerConsoleCommand('winter.update', 'System\Console\WinterUpdate');
- $this->registerConsoleCommand('winter.util', 'System\Console\WinterUtil');
- $this->registerConsoleCommand('winter.mirror', 'System\Console\WinterMirror');
- $this->registerConsoleCommand('winter.fresh', 'System\Console\WinterFresh');
- $this->registerConsoleCommand('winter.env', 'System\Console\WinterEnv');
- $this->registerConsoleCommand('winter.install', 'System\Console\WinterInstall');
- $this->registerConsoleCommand('winter.passwd', 'System\Console\WinterPasswd');
- $this->registerConsoleCommand('winter.version', 'System\Console\WinterVersion');
- $this->registerConsoleCommand('winter.manifest', 'System\Console\WinterManifest');
- $this->registerConsoleCommand('winter.test', 'System\Console\WinterTest');
-
- $this->registerConsoleCommand('plugin.install', 'System\Console\PluginInstall');
- $this->registerConsoleCommand('plugin.remove', 'System\Console\PluginRemove');
- $this->registerConsoleCommand('plugin.disable', 'System\Console\PluginDisable');
- $this->registerConsoleCommand('plugin.enable', 'System\Console\PluginEnable');
- $this->registerConsoleCommand('plugin.refresh', 'System\Console\PluginRefresh');
- $this->registerConsoleCommand('plugin.rollback', 'System\Console\PluginRollback');
- $this->registerConsoleCommand('plugin.list', 'System\Console\PluginList');
-
- $this->registerConsoleCommand('theme.install', 'System\Console\ThemeInstall');
- $this->registerConsoleCommand('theme.remove', 'System\Console\ThemeRemove');
- $this->registerConsoleCommand('theme.list', 'System\Console\ThemeList');
- $this->registerConsoleCommand('theme.use', 'System\Console\ThemeUse');
- $this->registerConsoleCommand('theme.sync', 'System\Console\ThemeSync');
+ $this->registerConsoleCommand('winter.up', \System\Console\WinterUp::class);
+ $this->registerConsoleCommand('winter.down', \System\Console\WinterDown::class);
+ $this->registerConsoleCommand('winter.update', \System\Console\WinterUpdate::class);
+ $this->registerConsoleCommand('winter.util', \System\Console\WinterUtil::class);
+ $this->registerConsoleCommand('winter.mirror', \System\Console\WinterMirror::class);
+ $this->registerConsoleCommand('winter.fresh', \System\Console\WinterFresh::class);
+ $this->registerConsoleCommand('winter.env', \System\Console\WinterEnv::class);
+ $this->registerConsoleCommand('winter.install', \System\Console\WinterInstall::class);
+ $this->registerConsoleCommand('winter.passwd', \System\Console\WinterPasswd::class);
+ $this->registerConsoleCommand('winter.version', \System\Console\WinterVersion::class);
+ $this->registerConsoleCommand('winter.manifest', \System\Console\WinterManifest::class);
+ $this->registerConsoleCommand('winter.test', \System\Console\WinterTest::class);
+
+ $this->registerConsoleCommand('plugin.install', \System\Console\PluginInstall::class);
+ $this->registerConsoleCommand('plugin.remove', \System\Console\PluginRemove::class);
+ $this->registerConsoleCommand('plugin.disable', \System\Console\PluginDisable::class);
+ $this->registerConsoleCommand('plugin.enable', \System\Console\PluginEnable::class);
+ $this->registerConsoleCommand('plugin.refresh', \System\Console\PluginRefresh::class);
+ $this->registerConsoleCommand('plugin.rollback', \System\Console\PluginRollback::class);
+ $this->registerConsoleCommand('plugin.list', \System\Console\PluginList::class);
+
+ $this->registerConsoleCommand('theme.install', \System\Console\ThemeInstall::class);
+ $this->registerConsoleCommand('theme.remove', \System\Console\ThemeRemove::class);
+ $this->registerConsoleCommand('theme.list', \System\Console\ThemeList::class);
+ $this->registerConsoleCommand('theme.use', \System\Console\ThemeUse::class);
+ $this->registerConsoleCommand('theme.sync', \System\Console\ThemeSync::class);
+
+ $this->registerConsoleCommand('mix.install', \System\Console\MixInstall::class);
+ $this->registerConsoleCommand('mix.list', \System\Console\MixList::class);
+ $this->registerConsoleCommand('mix.compile', \System\Console\MixCompile::class);
+ $this->registerConsoleCommand('mix.watch', \System\Console\MixWatch::class);
}
/*
@@ -433,22 +439,22 @@ protected function registerBackendPermissions()
'system.manage_updates' => [
'label' => 'system::lang.permissions.manage_software_updates',
'tab' => 'system::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
],
'system.access_logs' => [
'label' => 'system::lang.permissions.access_logs',
'tab' => 'system::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
],
'system.manage_mail_settings' => [
'label' => 'system::lang.permissions.manage_mail_settings',
'tab' => 'system::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
],
'system.manage_mail_templates' => [
'label' => 'system::lang.permissions.manage_mail_templates',
'tab' => 'system::lang.permissions.name',
- 'roles' => UserRole::CODE_DEVELOPER,
+ 'roles' => [UserRole::CODE_DEVELOPER],
]
]);
$manager->registerPermissionOwnerAlias('Winter.System', 'October.System');
@@ -560,6 +566,11 @@ protected function registerAssetBundles()
$combiner->registerBundle('~/modules/system/assets/js/framework.js');
$combiner->registerBundle('~/modules/system/assets/js/framework.combined.js');
$combiner->registerBundle('~/modules/system/assets/less/framework.extras.less');
+ $combiner->registerBundle('~/modules/system/assets/less/snowboard.extras.less');
+ });
+
+ MixAssets::registerCallback(function ($mix) {
+ $mix->registerPackage('snowboard', '~/modules/system/assets/js/snowboard/winter.mix.js');
});
}
diff --git a/modules/system/assets/css/mailbrandsettings/mailbrandsettings.css b/modules/system/assets/css/mailbrandsettings/mailbrandsettings.css
deleted file mode 100644
index 042ea750f..000000000
--- a/modules/system/assets/css/mailbrandsettings/mailbrandsettings.css
+++ /dev/null
@@ -1,4 +0,0 @@
-.field-colorpicker {
- float: right;
- margin-top: -10px;
-}
diff --git a/modules/system/assets/css/snowboard.extras.css b/modules/system/assets/css/snowboard.extras.css
new file mode 100644
index 000000000..edd80c9ee
--- /dev/null
+++ b/modules/system/assets/css/snowboard.extras.css
@@ -0,0 +1,57 @@
+body.wn-loading,
+body.wn-loading *,
+body.oc-loading,
+body.oc-loading *{cursor:wait !important}
+.stripe-loading-indicator{height:5px;background:transparent;position:fixed;top:0;left:0;width:100%;overflow:hidden;z-index:2000}
+.stripe-loading-indicator .stripe,
+.stripe-loading-indicator .stripe-loaded{height:5px;display:block;background:#0090c0;position:absolute;-webkit-box-shadow:inset 0 1px 1px -1px #FFF,inset 0 -1px 1px -1px #FFF;box-shadow:inset 0 1px 1px -1px #FFF,inset 0 -1px 1px -1px #FFF}
+.stripe-loading-indicator .stripe{width:100%;-webkit-animation:wn-infinite-loader 60s linear;animation:wn-infinite-loader 60s linear}
+.stripe-loading-indicator .stripe-loaded{width:100%;transform:translate3d(-100%,0,0);opacity:0;filter:alpha(opacity=0)}
+.stripe-loading-indicator.loaded{opacity:0;filter:alpha(opacity=0);-webkit-transition:opacity 0.4s linear;transition:opacity 0.4s linear;-webkit-transition-delay:0.3s;transition-delay:0.3s}
+.stripe-loading-indicator.loaded .stripe{animation-play-state:paused}
+.stripe-loading-indicator.loaded .stripe-loaded{opacity:1;filter:alpha(opacity=100);transform:translate3d(0,0,0);-webkit-transition:transform 0.3s linear;transition:transform 0.3s linear}
+.stripe-loading-indicator.hide{display:none}
+body>div.flash-message{position:fixed;width:500px;left:50%;top:13px;margin-left:-250px;color:#fff;font-size:14px;padding:10px 30px 14px 15px;z-index:10300;word-wrap:break-word;text-shadow:0 -1px 0 rgba(0,0,0,0.15);text-align:center;-webkit-box-shadow:0 1px 6px rgba(0,0,0,0.12),0 1px 4px rgba(0,0,0,0.24);box-shadow:0 1px 6px rgba(0,0,0,0.12),0 1px 4px rgba(0,0,0,0.24);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;cursor:pointer}
+body>div.flash-message .flash-timer{position:absolute;bottom:0;left:0;width:100%;height:4px;background:#fff;opacity:0.5;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
+body>div.flash-message .flash-timer.timeout-active{-webkit-transition:width 6s linear;transition:width 6s linear}
+body>div.flash-message .flash-timer.timeout-in{width:100%}
+body>div.flash-message .flash-timer.timeout-out{width:0%}
+body>div.flash-message.show-active,
+body>div.flash-message.hide-active{-webkit-transition:all 0.5s,width 0s;transition:all 0.5s,width 0s}
+body>div.flash-message.show-in,
+body>div.flash-message.hide-out{opacity:0;filter:alpha(opacity=0);-webkit-transform:scale(0.9);-ms-transform:scale(0.9);transform:scale(0.9)}
+body>div.flash-message.show-out,
+body>div.flash-message.hide-in{opacity:1;filter:alpha(opacity=100);-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}
+body>div.flash-message.success{background:#8da85e}
+body>div.flash-message.error{background:#c30}
+body>div.flash-message.warning{background:#f0ad4e}
+body>div.flash-message.info{background:#5fb6f5}
+@media (max-width:768px){body>div.flash-message{left:10px;right:10px;top:10px;margin-left:0;width:auto}}
+[data-request][data-request-validate] [data-validate-for]:not(.visible),
+[data-request][data-request-validate] [data-validate-error]:not(.visible){display:none}
+a.wn-loading:after,
+button.wn-loading:after,
+span.wn-loading:after,
+a.oc-loading:after,
+button.oc-loading:after,
+span.oc-loading:after{content:'';display:inline-block;vertical-align:middle;margin-left:.4em;height:1em;width:1em;animation:wn-rotate-loader 0.8s infinite linear;border:.2em solid currentColor;border-right-color:transparent;border-radius:50%;opacity:0.5;filter:alpha(opacity=50)}
+@-moz-keyframes wn-rotate-loader{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(360deg)}}
+@-webkit-keyframes wn-rotate-loader{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}
+@-o-keyframes wn-rotate-loader{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(360deg)}}
+@-ms-keyframes wn-rotate-loader{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(360deg)}}
+@keyframes wn-rotate-loader{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
+@-moz-keyframes oc-rotate-loader{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(360deg)}}
+@-webkit-keyframes oc-rotate-loader{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}
+@-o-keyframes oc-rotate-loader{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(360deg)}}
+@-ms-keyframes oc-rotate-loader{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(360deg)}}
+@keyframes oc-rotate-loader{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
+@-moz-keyframes wn-infinite-loader{0%{transform:translateX(-100%)}10%{transform:translateX(-50%)}20%{transform:translateX(-25%)}30%{transform:translateX(-12.5%)}40%{transform:translateX(-6.25%)}50%{transform:translateX(-3.125%)}60%{transform:translateX(-1.5625%)}70%{transform:translateX(-0.78125%)}80%{transform:translateX(-0.390625%)}90%{transform:translateX(-0.1953125%)}100%{transform:translateX(-0.09765625%)}}
+@-webkit-keyframes wn-infinite-loader{0%{transform:translateX(-100%)}10%{transform:translateX(-50%)}20%{transform:translateX(-25%)}30%{transform:translateX(-12.5%)}40%{transform:translateX(-6.25%)}50%{transform:translateX(-3.125%)}60%{transform:translateX(-1.5625%)}70%{transform:translateX(-0.78125%)}80%{transform:translateX(-0.390625%)}90%{transform:translateX(-0.1953125%)}100%{transform:translateX(-0.09765625%)}}
+@-o-keyframes wn-infinite-loader{0%{transform:translateX(-100%)}10%{transform:translateX(-50%)}20%{transform:translateX(-25%)}30%{transform:translateX(-12.5%)}40%{transform:translateX(-6.25%)}50%{transform:translateX(-3.125%)}60%{transform:translateX(-1.5625%)}70%{transform:translateX(-0.78125%)}80%{transform:translateX(-0.390625%)}90%{transform:translateX(-0.1953125%)}100%{transform:translateX(-0.09765625%)}}
+@-ms-keyframes wn-infinite-loader{0%{transform:translateX(-100%)}10%{transform:translateX(-50%)}20%{transform:translateX(-25%)}30%{transform:translateX(-12.5%)}40%{transform:translateX(-6.25%)}50%{transform:translateX(-3.125%)}60%{transform:translateX(-1.5625%)}70%{transform:translateX(-0.78125%)}80%{transform:translateX(-0.390625%)}90%{transform:translateX(-0.1953125%)}100%{transform:translateX(-0.09765625%)}}
+@keyframes wn-infinite-loader{0%{transform:translateX(-100%)}10%{transform:translateX(-50%)}20%{transform:translateX(-25%)}30%{transform:translateX(-12.5%)}40%{transform:translateX(-6.25%)}50%{transform:translateX(-3.125%)}60%{transform:translateX(-1.5625%)}70%{transform:translateX(-0.78125%)}80%{transform:translateX(-0.390625%)}90%{transform:translateX(-0.1953125%)}100%{transform:translateX(-0.09765625%)}}
+@-moz-keyframes oc-infinite-loader{0%{transform:translateX(-100%)}10%{transform:translateX(-50%)}20%{transform:translateX(-25%)}30%{transform:translateX(-12.5%)}40%{transform:translateX(-6.25%)}50%{transform:translateX(-3.125%)}60%{transform:translateX(-1.5625%)}70%{transform:translateX(-0.78125%)}80%{transform:translateX(-0.390625%)}90%{transform:translateX(-0.1953125%)}100%{transform:translateX(-0.09765625%)}}
+@-webkit-keyframes oc-infinite-loader{0%{transform:translateX(-100%)}10%{transform:translateX(-50%)}20%{transform:translateX(-25%)}30%{transform:translateX(-12.5%)}40%{transform:translateX(-6.25%)}50%{transform:translateX(-3.125%)}60%{transform:translateX(-1.5625%)}70%{transform:translateX(-0.78125%)}80%{transform:translateX(-0.390625%)}90%{transform:translateX(-0.1953125%)}100%{transform:translateX(-0.09765625%)}}
+@-o-keyframes oc-infinite-loader{0%{transform:translateX(-100%)}10%{transform:translateX(-50%)}20%{transform:translateX(-25%)}30%{transform:translateX(-12.5%)}40%{transform:translateX(-6.25%)}50%{transform:translateX(-3.125%)}60%{transform:translateX(-1.5625%)}70%{transform:translateX(-0.78125%)}80%{transform:translateX(-0.390625%)}90%{transform:translateX(-0.1953125%)}100%{transform:translateX(-0.09765625%)}}
+@-ms-keyframes oc-infinite-loader{0%{transform:translateX(-100%)}10%{transform:translateX(-50%)}20%{transform:translateX(-25%)}30%{transform:translateX(-12.5%)}40%{transform:translateX(-6.25%)}50%{transform:translateX(-3.125%)}60%{transform:translateX(-1.5625%)}70%{transform:translateX(-0.78125%)}80%{transform:translateX(-0.390625%)}90%{transform:translateX(-0.1953125%)}100%{transform:translateX(-0.09765625%)}}
+@keyframes oc-infinite-loader{0%{transform:translateX(-100%)}10%{transform:translateX(-50%)}20%{transform:translateX(-25%)}30%{transform:translateX(-12.5%)}40%{transform:translateX(-6.25%)}50%{transform:translateX(-3.125%)}60%{transform:translateX(-1.5625%)}70%{transform:translateX(-0.78125%)}80%{transform:translateX(-0.390625%)}90%{transform:translateX(-0.1953125%)}100%{transform:translateX(-0.09765625%)}}
\ No newline at end of file
diff --git a/modules/system/assets/js/lang/lang.lv.js b/modules/system/assets/js/lang/lang.lv.js
index 816861e8f..fd153c332 100644
--- a/modules/system/assets/js/lang/lang.lv.js
+++ b/modules/system/assets/js/lang/lang.lv.js
@@ -6,7 +6,7 @@ if ($.oc === undefined) $.oc = $.wn
if ($.wn.langMessages === undefined) $.wn.langMessages = {}
$.wn.langMessages['lv'] = $.extend(
$.wn.langMessages['lv'] || {},
- {"markdowneditor":{"formatting":"Format\u0113jums","quote":"Cit\u0101ts","code":"Kods","header1":"Virsraksts 1","header2":"Virsraksts 2","header3":"Virsraksts 3","header4":"Virsraksts 4","header5":"Virsraksts 5","header6":"Virsraksts 6","bold":"Treknraksts","italic":"Kurs\u012bvraksts","unorderedlist":"Nesak\u0101rtots saraksts","orderedlist":"Sak\u0101rtots saraksts","video":"Video","image":"Att\u0113ls","link":"Saite","horizontalrule":"Ievietot horizont\u0101lu l\u012bniju","fullscreen":"Pilnekr\u0101na re\u017e\u012bms","preview":"Priek\u0161skat\u012bjums"},"mediamanager":{"insert_link":"Ievietot multivides saiti","insert_image":"Ievietot multivides att\u0113lu","insert_video":"Ievietot multivides video","insert_audio":"Ievietot multivides audio","invalid_file_empty_insert":"L\u016bdzu, izv\u0113lieties failu uz kuru ievietot saites.","invalid_file_single_insert":"L\u016bdzu, izv\u0113lieties vienu failu.","invalid_image_empty_insert":"L\u016bdzu, izv\u0113lieties ievietojamo(-os) att\u0113lu(-us).","invalid_video_empty_insert":"L\u016bdzu, izv\u0113lieties ievietojamo video failu.","invalid_audio_empty_insert":"L\u016bdzu, izv\u0113lieties ievietojamo audio failu."},"alert":{"confirm_button_text":"Labi","cancel_button_text":"Atcelt","widget_remove_confirm":"No\u0146emt \u0161o logr\u012bku?"},"datepicker":{"previousMonth":"Iepriek\u0161\u0113jais m\u0113nesis","nextMonth":"N\u0101kamais m\u0113nesis","months":["Janv\u0101ris","Febru\u0101ris","Marts","Apr\u012blis","Maijs","J\u016bnijs","J\u016blijs","Augusts","Septembris","Oktobris","Novembris","Decembris"],"weekdays":["Sv\u0113tdiena","Pirmdiena","Otrdiena","Tre\u0161diena","Ceturtdiena","Piektdiena","Sestdiena"],"weekdaysShort":["Sv","P","O","T","C","Pk","S"]},"colorpicker":{"last_color":"Use previously selected color","aria_palette":"Color selection area","aria_hue":"Hue selection slider","aria_opacity":"Opacity selection slider","choose":"Labi"},"filter":{"group":{"all":"visi"},"scopes":{"apply_button_text":"Piem\u0113rot","clear_button_text":"Not\u012br\u012bt"},"dates":{"all":"visi","filter_button_text":"Filtr\u0113t","reset_button_text":"Atiestat\u012bt","date_placeholder":"Datums","after_placeholder":"Pirms","before_placeholder":"P\u0113c"},"numbers":{"all":"visi","filter_button_text":"Filtr\u0113t","reset_button_text":"Atiestat\u012bt","min_placeholder":"Min","max_placeholder":"Max"}},"eventlog":{"show_stacktrace":"R\u0101d\u012bt atseko\u0161anas inform\u0101ciju","hide_stacktrace":"Sl\u0113pt atseko\u0161anas inform\u0101ciju","tabs":{"formatted":"Format\u0113ts","raw":"Neapstr\u0101d\u0101ts"},"editor":{"title":"Pirmkoda redaktors","description":"J\u016bsu oper\u0113t\u0101jsist\u0113mai j\u0101b\u016bt konfigur\u0113tai t\u0101, lai t\u0101 sp\u0113tu klaus\u012bties uz vienu no \u0161\u012bm URL sh\u0113m\u0101m.","openWith":"Atv\u0113rt ar","remember_choice":"Atcer\u0113ties izv\u0113li \u0161\u012bs sesijas ietvaros","open":"Atv\u0113rt","cancel":"Atcelt"}}}
+ {"markdowneditor":{"formatting":"Format\u0113jums","quote":"Cit\u0101ts","code":"Kods","header1":"Virsraksts 1","header2":"Virsraksts 2","header3":"Virsraksts 3","header4":"Virsraksts 4","header5":"Virsraksts 5","header6":"Virsraksts 6","bold":"Treknraksts","italic":"Kurs\u012bvraksts","unorderedlist":"Nesak\u0101rtots saraksts","orderedlist":"Sak\u0101rtots saraksts","video":"Video","image":"Att\u0113ls","link":"Saite","horizontalrule":"Ievietot horizont\u0101lu l\u012bniju","fullscreen":"Pilnekr\u0101na re\u017e\u012bms","preview":"Priek\u0161skat\u012bjums"},"mediamanager":{"insert_link":"Ievietot multivides saiti","insert_image":"Ievietot multivides att\u0113lu","insert_video":"Ievietot multivides video","insert_audio":"Ievietot multivides audio","invalid_file_empty_insert":"L\u016bdzu, izv\u0113lieties failu uz kuru ievietot saites.","invalid_file_single_insert":"L\u016bdzu, izv\u0113lieties vienu failu.","invalid_image_empty_insert":"L\u016bdzu, izv\u0113lieties ievietojamo(-os) att\u0113lu(-us).","invalid_video_empty_insert":"L\u016bdzu, izv\u0113lieties ievietojamo video failu.","invalid_audio_empty_insert":"L\u016bdzu, izv\u0113lieties ievietojamo audio failu."},"alert":{"confirm_button_text":"Labi","cancel_button_text":"Atcelt","widget_remove_confirm":"No\u0146emt \u0161o logr\u012bku?"},"datepicker":{"previousMonth":"Iepriek\u0161\u0113jais m\u0113nesis","nextMonth":"N\u0101kamais m\u0113nesis","months":["Janv\u0101ris","Febru\u0101ris","Marts","Apr\u012blis","Maijs","J\u016bnijs","J\u016blijs","Augusts","Septembris","Oktobris","Novembris","Decembris"],"weekdays":["Sv\u0113tdiena","Pirmdiena","Otrdiena","Tre\u0161diena","Ceturtdiena","Piektdiena","Sestdiena"],"weekdaysShort":["Sv","P","O","T","C","Pk","S"]},"colorpicker":{"last_color":"Lietot iepriek\u0161 izv\u0113l\u0113to kr\u0101su","aria_palette":"Kr\u0101sas izv\u0113les laukums","aria_hue":"Nokr\u0101sas izv\u0113les sl\u012bdnis","aria_opacity":"Caursp\u012bd\u012bguma izv\u0113les sl\u012bdnis"},"filter":{"group":{"all":"visi"},"scopes":{"apply_button_text":"Piem\u0113rot","clear_button_text":"Not\u012br\u012bt"},"dates":{"all":"visi","filter_button_text":"Filtr\u0113t","reset_button_text":"Atiestat\u012bt","date_placeholder":"Datums","after_placeholder":"Pirms","before_placeholder":"P\u0113c"},"numbers":{"all":"visi","filter_button_text":"Filtr\u0113t","reset_button_text":"Atiestat\u012bt","min_placeholder":"Min","max_placeholder":"Max"}},"eventlog":{"show_stacktrace":"R\u0101d\u012bt atseko\u0161anas inform\u0101ciju","hide_stacktrace":"Sl\u0113pt atseko\u0161anas inform\u0101ciju","tabs":{"formatted":"Format\u0113ts","raw":"Neapstr\u0101d\u0101ts"},"editor":{"title":"Pirmkoda redaktors","description":"J\u016bsu oper\u0113t\u0101jsist\u0113mai j\u0101b\u016bt konfigur\u0113tai t\u0101, lai t\u0101 sp\u0113tu klaus\u012bties uz vienu no \u0161\u012bm URL sh\u0113m\u0101m.","openWith":"Atv\u0113rt ar","remember_choice":"Atcer\u0113ties izv\u0113li \u0161\u012bs sesijas ietvaros","open":"Atv\u0113rt","cancel":"Atcelt"}}}
);
//! moment.js locale configuration v2.22.2
diff --git a/modules/system/assets/js/lang/lang.ru.js b/modules/system/assets/js/lang/lang.ru.js
index 74c872fec..d07910653 100644
--- a/modules/system/assets/js/lang/lang.ru.js
+++ b/modules/system/assets/js/lang/lang.ru.js
@@ -6,7 +6,7 @@ if ($.oc === undefined) $.oc = $.wn
if ($.wn.langMessages === undefined) $.wn.langMessages = {}
$.wn.langMessages['ru'] = $.extend(
$.wn.langMessages['ru'] || {},
- {"markdowneditor":{"formatting":"\u0424\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435","quote":"\u0426\u0438\u0442\u0430\u0442\u0430","code":"\u041a\u043e\u0434","header1":"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 1","header2":"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 2","header3":"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 3","header4":"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 4","header5":"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 5","header6":"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 6","bold":"\u0416\u0438\u0440\u043d\u044b\u0439 \u0448\u0440\u0438\u0444\u0442","italic":"\u041a\u0443\u0440\u0441\u0438\u0432","unorderedlist":"\u041d\u0435\u043d\u0443\u043c\u0435\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a","orderedlist":"\u041d\u0443\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a","video":"\u0412\u0438\u0434\u0435\u043e","image":"\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435","link":"\u0421\u0441\u044b\u043b\u043a\u0430","horizontalrule":"\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u0443\u044e \u0447\u0435\u0440\u0442\u0443","fullscreen":"\u041f\u043e\u043b\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d","preview":"\u041f\u0440\u0435\u0434\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440"},"mediamanager":{"insert_link":"\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0434\u0438\u0430-\u0441\u0441\u044b\u043b\u043a\u0443","insert_image":"\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0434\u0438\u0430-\u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435","insert_video":"\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0434\u0438\u0430-\u0432\u0438\u0434\u0435\u043e","insert_audio":"\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0434\u0438\u0430-\u0430\u0443\u0434\u0438\u043e","invalid_file_empty_insert":"\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0444\u0430\u0439\u043b \u0434\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438 \u0441\u0441\u044b\u043b\u043a\u0438.","invalid_file_single_insert":"\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u0438\u043d \u0444\u0430\u0439\u043b.","invalid_image_empty_insert":"\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438.","invalid_video_empty_insert":"\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0432\u0438\u0434\u0435\u043e \u0434\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438.","invalid_audio_empty_insert":"\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0430\u0443\u0434\u0438\u043e \u0434\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438."},"alert":{"confirm_button_text":"\u041e\u043a","cancel_button_text":"\u041e\u0442\u043c\u0435\u043d\u0430","widget_remove_confirm":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u044d\u0442\u043e\u0442 \u0432\u0438\u0434\u0436\u0435\u0442?"},"datepicker":{"previousMonth":"\u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0439 \u043c\u0435\u0441\u044f\u0446","nextMonth":"\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043c\u0435\u0441\u044f\u0446","months":["\u042f\u043d\u0432\u0430\u0440\u044c","\u0424\u0435\u0432\u0440\u0430\u043b\u044c","\u041c\u0430\u0440\u0442","\u0410\u043f\u0440\u0435\u043b\u044c","\u041c\u0430\u0439","\u0418\u044e\u043d\u044c","\u0418\u044e\u043b\u044c","\u0410\u0432\u0433\u0443\u0441\u0442","\u0421\u0435\u043d\u0442\u044f\u0431\u0440\u044c","\u041e\u043a\u0442\u044f\u0431\u0440\u044c","\u041d\u043e\u044f\u0431\u0440\u044c","\u0414\u0435\u043a\u0430\u0431\u0440\u044c"],"weekdays":["\u0412\u043e\u0441\u043a\u0440\u0435\u0441\u0435\u043d\u0438\u0435","\u041f\u043e\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u0438\u043a","\u0412\u0442\u043e\u0440\u043d\u0438\u043a","\u0421\u0440\u0435\u0434\u0430","\u0427\u0435\u0442\u0432\u0435\u0440\u0433","\u041f\u044f\u0442\u043d\u0438\u0446\u0430","\u0421\u0443\u0431\u0431\u043e\u0442\u0430"],"weekdaysShort":["\u0412\u0441","\u041f\u043d","\u0412\u0442","\u0421\u0440","\u0427\u0442","\u041f\u0442","\u0421\u0431"]},"colorpicker":{"last_color":"Use previously selected color","aria_palette":"Color selection area","aria_hue":"Hue selection slider","aria_opacity":"Opacity selection slider","choose":"\u041e\u041a"},"filter":{"group":{"all":"\u0432\u0441\u0435"},"scopes":{"apply_button_text":"\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c","clear_button_text":"\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c"},"dates":{"all":"\u0432\u0441\u0435","filter_button_text":"\u0424\u0438\u043b\u044c\u0442\u0440","reset_button_text":"\u0421\u0431\u0440\u043e\u0441\u0438\u0442\u044c","date_placeholder":"\u0414\u0430\u0442\u0430","after_placeholder":"\u041f\u043e\u0441\u043b\u0435","before_placeholder":"\u0414\u043e"},"numbers":{"all":"\u0432\u0441\u0435","filter_button_text":"\u0424\u0438\u043b\u044c\u0442\u0440","reset_button_text":"\u0421\u0431\u0440\u043e\u0441","min_placeholder":"Min","max_placeholder":"Max"}},"eventlog":{"show_stacktrace":"\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0442\u0440\u0430\u0441\u0441\u0438\u0440\u043e\u0432\u043a\u0443 \u0441\u0442\u0435\u043a\u0430","hide_stacktrace":"\u0421\u043a\u0440\u044b\u0442\u044c \u0442\u0440\u0430\u0441\u0441\u0438\u0440\u043e\u0432\u043a\u0443 \u0441\u0442\u0435\u043a\u0430","tabs":{"formatted":"\u0424\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439","raw":"\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439"},"editor":{"title":"\u0420\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430","description":"\u0412\u0430\u0448\u0430 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u043d\u0430 \u043f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u043d\u0438\u0435 \u043a \u043e\u0434\u043d\u043e\u0439 \u0438\u0437 \u044d\u0442\u0438\u0445 \u0441\u0445\u0435\u043c URL.","openWith":"\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e","remember_choice":"\u0417\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u044c \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0441\u0435\u0441\u0441\u0438\u0438","open":"\u041e\u0442\u043a\u0440\u044b\u0442\u044c","cancel":"\u041e\u0442\u043c\u0435\u043d\u0430"}}}
+ {"markdowneditor":{"formatting":"\u0424\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435","quote":"\u0426\u0438\u0442\u0430\u0442\u0430","code":"\u041a\u043e\u0434","header1":"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 1","header2":"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 2","header3":"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 3","header4":"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 4","header5":"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 5","header6":"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a 6","bold":"\u0416\u0438\u0440\u043d\u044b\u0439 \u0448\u0440\u0438\u0444\u0442","italic":"\u041a\u0443\u0440\u0441\u0438\u0432","unorderedlist":"\u041d\u0435\u043d\u0443\u043c\u0435\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a","orderedlist":"\u041d\u0443\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a","video":"\u0412\u0438\u0434\u0435\u043e","image":"\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435","link":"\u0421\u0441\u044b\u043b\u043a\u0430","horizontalrule":"\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u0443\u044e \u0447\u0435\u0440\u0442\u0443","fullscreen":"\u041f\u043e\u043b\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d","preview":"\u041f\u0440\u0435\u0434\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440"},"mediamanager":{"insert_link":"\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0434\u0438\u0430-\u0441\u0441\u044b\u043b\u043a\u0443","insert_image":"\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0434\u0438\u0430-\u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435","insert_video":"\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0434\u0438\u0430-\u0432\u0438\u0434\u0435\u043e","insert_audio":"\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0434\u0438\u0430-\u0430\u0443\u0434\u0438\u043e","invalid_file_empty_insert":"\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0444\u0430\u0439\u043b \u0434\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438 \u0441\u0441\u044b\u043b\u043a\u0438.","invalid_file_single_insert":"\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u0438\u043d \u0444\u0430\u0439\u043b.","invalid_image_empty_insert":"\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438.","invalid_video_empty_insert":"\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0432\u0438\u0434\u0435\u043e \u0434\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438.","invalid_audio_empty_insert":"\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0430\u0443\u0434\u0438\u043e \u0434\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438."},"alert":{"confirm_button_text":"\u041e\u043a","cancel_button_text":"\u041e\u0442\u043c\u0435\u043d\u0430","widget_remove_confirm":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u044d\u0442\u043e\u0442 \u0432\u0438\u0434\u0436\u0435\u0442?"},"datepicker":{"previousMonth":"\u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0439 \u043c\u0435\u0441\u044f\u0446","nextMonth":"\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043c\u0435\u0441\u044f\u0446","months":["\u042f\u043d\u0432\u0430\u0440\u044c","\u0424\u0435\u0432\u0440\u0430\u043b\u044c","\u041c\u0430\u0440\u0442","\u0410\u043f\u0440\u0435\u043b\u044c","\u041c\u0430\u0439","\u0418\u044e\u043d\u044c","\u0418\u044e\u043b\u044c","\u0410\u0432\u0433\u0443\u0441\u0442","\u0421\u0435\u043d\u0442\u044f\u0431\u0440\u044c","\u041e\u043a\u0442\u044f\u0431\u0440\u044c","\u041d\u043e\u044f\u0431\u0440\u044c","\u0414\u0435\u043a\u0430\u0431\u0440\u044c"],"weekdays":["\u0412\u043e\u0441\u043a\u0440\u0435\u0441\u0435\u043d\u0438\u0435","\u041f\u043e\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u0438\u043a","\u0412\u0442\u043e\u0440\u043d\u0438\u043a","\u0421\u0440\u0435\u0434\u0430","\u0427\u0435\u0442\u0432\u0435\u0440\u0433","\u041f\u044f\u0442\u043d\u0438\u0446\u0430","\u0421\u0443\u0431\u0431\u043e\u0442\u0430"],"weekdaysShort":["\u0412\u0441","\u041f\u043d","\u0412\u0442","\u0421\u0440","\u0427\u0442","\u041f\u0442","\u0421\u0431"]},"colorpicker":{"last_color":"\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u043d\u0435\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0446\u0432\u0435\u0442","aria_palette":"\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u0432\u044b\u0431\u043e\u0440\u0430 \u0446\u0432\u0435\u0442\u0430","aria_hue":"\u041f\u043e\u043b\u0437\u0443\u043d\u043e\u043a \u0432\u044b\u0431\u043e\u0440\u0430 \u043e\u0442\u0442\u0435\u043d\u043a\u0430","aria_opacity":"\u041f\u043e\u043b\u0437\u0443\u043d\u043e\u043a \u0432\u044b\u0431\u043e\u0440\u0430 \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u043e\u0441\u0442\u0438"},"filter":{"group":{"all":"\u0432\u0441\u0435"},"scopes":{"apply_button_text":"\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c","clear_button_text":"\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c"},"dates":{"all":"\u0432\u0441\u0435","filter_button_text":"\u0424\u0438\u043b\u044c\u0442\u0440","reset_button_text":"\u0421\u0431\u0440\u043e\u0441\u0438\u0442\u044c","date_placeholder":"\u0414\u0430\u0442\u0430","after_placeholder":"\u041f\u043e\u0441\u043b\u0435","before_placeholder":"\u0414\u043e"},"numbers":{"all":"\u0432\u0441\u0435","filter_button_text":"\u0424\u0438\u043b\u044c\u0442\u0440","reset_button_text":"\u0421\u0431\u0440\u043e\u0441","min_placeholder":"Min","max_placeholder":"Max"}},"eventlog":{"show_stacktrace":"\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0442\u0440\u0430\u0441\u0441\u0438\u0440\u043e\u0432\u043a\u0443 \u0441\u0442\u0435\u043a\u0430","hide_stacktrace":"\u0421\u043a\u0440\u044b\u0442\u044c \u0442\u0440\u0430\u0441\u0441\u0438\u0440\u043e\u0432\u043a\u0443 \u0441\u0442\u0435\u043a\u0430","tabs":{"formatted":"\u0424\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439","raw":"\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439"},"editor":{"title":"\u0420\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430","description":"\u0412\u0430\u0448\u0430 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u043d\u0430 \u043f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u043d\u0438\u0435 \u043a \u043e\u0434\u043d\u043e\u0439 \u0438\u0437 \u044d\u0442\u0438\u0445 \u0441\u0445\u0435\u043c URL.","openWith":"\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e","remember_choice":"\u0417\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u044c \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0441\u0435\u0441\u0441\u0438\u0438","open":"\u041e\u0442\u043a\u0440\u044b\u0442\u044c","cancel":"\u041e\u0442\u043c\u0435\u043d\u0430"}}}
);
//! moment.js locale configuration v2.22.2
diff --git a/modules/system/assets/js/snowboard/.eslintignore b/modules/system/assets/js/snowboard/.eslintignore
new file mode 100644
index 000000000..089411e42
--- /dev/null
+++ b/modules/system/assets/js/snowboard/.eslintignore
@@ -0,0 +1,2 @@
+**/node_modules/**
+build/*.js
diff --git a/modules/system/assets/js/snowboard/.eslintrc.json b/modules/system/assets/js/snowboard/.eslintrc.json
new file mode 100644
index 000000000..2924496b8
--- /dev/null
+++ b/modules/system/assets/js/snowboard/.eslintrc.json
@@ -0,0 +1,22 @@
+{
+ "env": {
+ "es6": true,
+ "browser": true
+ },
+ "globals": {
+ "Snowboard": "writable"
+ },
+ "extends": "airbnb-base",
+ "rules": {
+ "class-methods-use-this": ["off"],
+ "indent": ["error", 4, {
+ "SwitchCase": 1
+ }],
+ "max-len": ["off"],
+ "new-cap": ["error", { "properties": false }],
+ "no-alert": ["off"],
+ "no-param-reassign": ["error", {
+ "props": false
+ }]
+ }
+}
diff --git a/modules/system/assets/js/snowboard/.gitignore b/modules/system/assets/js/snowboard/.gitignore
new file mode 100644
index 000000000..4b6634079
--- /dev/null
+++ b/modules/system/assets/js/snowboard/.gitignore
@@ -0,0 +1,3 @@
+# Ignore packages
+node_modules
+package-lock.json
diff --git a/modules/system/assets/js/snowboard/abstracts/PluginBase.js b/modules/system/assets/js/snowboard/abstracts/PluginBase.js
new file mode 100644
index 000000000..c50f2556c
--- /dev/null
+++ b/modules/system/assets/js/snowboard/abstracts/PluginBase.js
@@ -0,0 +1,48 @@
+/**
+ * Plugin base abstract.
+ *
+ * This class provides the base functionality for all plugins.
+ *
+ * @copyright 2021 Winter.
+ * @author Ben Thomson
+ */
+export default class PluginBase {
+ /**
+ * Constructor.
+ *
+ * The constructor is provided the Snowboard framework instance.
+ *
+ * @param {Snowboard} snowboard
+ */
+ constructor(snowboard) {
+ this.snowboard = snowboard;
+ }
+
+ /**
+ * Defines the required plugins for this specific module to work.
+ *
+ * @returns {string[]} An array of plugins required for this module to work, as strings.
+ */
+ dependencies() {
+ return [];
+ }
+
+ /**
+ * Defines the listener methods for global events.
+ *
+ * @returns {Object}
+ */
+ listens() {
+ return {};
+ }
+
+ /**
+ * Destructor.
+ *
+ * Fired when this plugin is removed.
+ */
+ destructor() {
+ this.detach();
+ delete this.snowboard;
+ }
+}
diff --git a/modules/system/assets/js/snowboard/abstracts/Singleton.js b/modules/system/assets/js/snowboard/abstracts/Singleton.js
new file mode 100644
index 000000000..557e09c3d
--- /dev/null
+++ b/modules/system/assets/js/snowboard/abstracts/Singleton.js
@@ -0,0 +1,15 @@
+import PluginBase from './PluginBase';
+
+/**
+ * Singleton plugin abstract.
+ *
+ * This is a special definition class that the Snowboard framework will use to interpret the current plugin as a
+ * "singleton". This will ensure that only one instance of the plugin class is used across the board.
+ *
+ * Singletons are initialised on the "domReady" event by default.
+ *
+ * @copyright 2021 Winter.
+ * @author Ben Thomson
+ */
+export default class Singleton extends PluginBase {
+}
diff --git a/modules/system/assets/js/snowboard/ajax/Request.js b/modules/system/assets/js/snowboard/ajax/Request.js
new file mode 100644
index 000000000..16c90450e
--- /dev/null
+++ b/modules/system/assets/js/snowboard/ajax/Request.js
@@ -0,0 +1,789 @@
+/**
+ * Request plugin.
+ *
+ * This is the default AJAX handler which will run using the `fetch()` method that is default in modern browsers.
+ *
+ * @copyright 2021 Winter.
+ * @author Ben Thomson
+ */
+class Request extends Snowboard.PluginBase {
+ /**
+ * Constructor.
+ *
+ * @param {Snowboard} snowboard
+ * @param {HTMLElement|string} element
+ * @param {string} handler
+ * @param {Object} options
+ */
+ constructor(snowboard, element, handler, options) {
+ super(snowboard);
+
+ if (typeof element === 'string') {
+ const matchedElement = document.querySelector(element);
+ if (matchedElement === null) {
+ throw new Error(`No element was found with the given selector: ${element}`);
+ }
+ this.element = matchedElement;
+ } else {
+ this.element = element;
+ }
+ this.handler = handler;
+ this.options = options || {};
+ this.fetchOptions = {};
+ this.responseData = null;
+ this.responseError = null;
+ this.cancelled = false;
+
+ this.checkRequest();
+ if (!this.snowboard.globalEvent('ajaxSetup', this)) {
+ this.cancelled = true;
+ return;
+ }
+ if (this.element) {
+ const event = new Event('ajaxSetup', { cancelable: true });
+ event.request = this;
+ this.element.dispatchEvent(event);
+
+ if (event.defaultPrevented) {
+ this.cancelled = true;
+ return;
+ }
+ }
+
+ if (!this.doClientValidation()) {
+ this.cancelled = true;
+ return;
+ }
+
+ if (this.confirm) {
+ this.doConfirm().then((confirmed) => {
+ if (confirmed) {
+ this.doAjax().then(
+ (response) => {
+ if (response.cancelled) {
+ this.cancelled = true;
+ return;
+ }
+ this.responseData = response;
+ this.processUpdate(response).then(
+ () => {
+ if (response.X_WINTER_SUCCESS === false) {
+ this.processError(response);
+ } else {
+ this.processResponse(response);
+ }
+ },
+ );
+ },
+ (error) => {
+ this.responseError = error;
+ this.processError(error);
+ },
+ ).finally(() => {
+ if (this.cancelled === true) {
+ return;
+ }
+
+ if (this.options.complete && typeof this.options.complete === 'function') {
+ this.options.complete(this.responseData, this);
+ }
+ this.snowboard.globalEvent('ajaxDone', this.responseData, this);
+
+ if (this.element) {
+ const event = new Event('ajaxAlways');
+ event.request = this;
+ event.responseData = this.responseData;
+ event.responseError = this.responseError;
+ this.element.dispatchEvent(event);
+ }
+ });
+ }
+ });
+ } else {
+ this.doAjax().then(
+ (response) => {
+ if (response.cancelled) {
+ this.cancelled = true;
+ return;
+ }
+ this.responseData = response;
+ this.processUpdate(response).then(
+ () => {
+ if (response.X_WINTER_SUCCESS === false) {
+ this.processError(response);
+ } else {
+ this.processResponse(response);
+ }
+ },
+ );
+ },
+ (error) => {
+ this.responseError = error;
+ this.processError(error);
+ },
+ ).finally(() => {
+ if (this.cancelled === true) {
+ return;
+ }
+
+ if (this.options.complete && typeof this.options.complete === 'function') {
+ this.options.complete(this.responseData, this);
+ }
+ this.snowboard.globalEvent('ajaxDone', this.responseData, this);
+
+ if (this.element) {
+ const event = new Event('ajaxAlways');
+ event.request = this;
+ event.responseData = this.responseData;
+ event.responseError = this.responseError;
+ this.element.dispatchEvent(event);
+ }
+ });
+ }
+ }
+
+ /**
+ * Dependencies for this plugin.
+ *
+ * @returns {string[]}
+ */
+ dependencies() {
+ return ['cookie', 'jsonParser'];
+ }
+
+ /**
+ * Validates the element and handler given in the request.
+ */
+ checkRequest() {
+ if (this.element !== undefined && this.element instanceof Element === false) {
+ throw new Error('The element provided must be an Element instance');
+ }
+
+ if (this.handler === undefined) {
+ throw new Error('The AJAX handler name is not specified.');
+ }
+
+ if (!this.handler.match(/^(?:\w+:{2})?on*/)) {
+ throw new Error('Invalid AJAX handler name. The correct handler name format is: "onEvent".');
+ }
+ }
+
+ /**
+ * Creates a Fetch request.
+ *
+ * This method is made available for plugins to extend or override the default fetch() settings with their own.
+ *
+ * @returns {Promise}
+ */
+ getFetch() {
+ this.fetchOptions = (this.options.fetchOptions !== undefined && typeof this.options.fetchOptions === 'object')
+ ? this.options.fetchOptions
+ : {
+ method: 'POST',
+ headers: this.headers,
+ body: this.data,
+ redirect: 'follow',
+ mode: 'same-origin',
+ };
+
+ this.snowboard.globalEvent('ajaxFetchOptions', this.fetchOptions, this);
+
+ return fetch(this.url, this.fetchOptions);
+ }
+
+ /**
+ * Run client-side validation on the form, if available.
+ *
+ * @returns {boolean}
+ */
+ doClientValidation() {
+ if (this.options.browserValidate === true && this.form) {
+ if (this.form.checkValidity() === false) {
+ this.form.reportValidity();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Executes the AJAX query.
+ *
+ * Returns a Promise object for when the AJAX request is completed.
+ *
+ * @returns {Promise}
+ */
+ doAjax() {
+ // Allow plugins to cancel the AJAX request before sending
+ if (this.snowboard.globalEvent('ajaxBeforeSend', this) === false) {
+ return Promise.resolve({
+ cancelled: true,
+ });
+ }
+
+ const ajaxPromise = new Promise((resolve, reject) => {
+ this.getFetch().then(
+ (response) => {
+ if (!response.ok && response.status !== 406) {
+ if (response.headers.has('Content-Type') && response.headers.get('Content-Type').includes('/json')) {
+ response.json().then(
+ (responseData) => {
+ reject(this.renderError(
+ responseData.message,
+ responseData.exception,
+ responseData.file,
+ responseData.line,
+ responseData.trace,
+ ));
+ },
+ (error) => {
+ reject(this.renderError(`Unable to parse JSON response: ${error}`));
+ },
+ );
+ } else {
+ response.text().then(
+ (responseText) => {
+ reject(this.renderError(responseText));
+ },
+ (error) => {
+ reject(this.renderError(`Unable to process response: ${error}`));
+ },
+ );
+ }
+ return;
+ }
+
+ if (response.headers.has('Content-Type') && response.headers.get('Content-Type').includes('/json')) {
+ response.json().then(
+ (responseData) => {
+ resolve({
+ ...responseData,
+ X_WINTER_SUCCESS: response.status !== 406,
+ X_WINTER_RESPONSE_CODE: response.status,
+ });
+ },
+ (error) => {
+ reject(this.renderError(`Unable to parse JSON response: ${error}`));
+ },
+ );
+ } else {
+ response.text().then(
+ (responseData) => {
+ resolve(responseData);
+ },
+ (error) => {
+ reject(this.renderError(`Unable to process response: ${error}`));
+ },
+ );
+ }
+ },
+ (responseError) => {
+ reject(this.renderError(`Unable to retrieve a response from the server: ${responseError}`));
+ },
+ );
+ });
+
+ this.snowboard.globalEvent('ajaxStart', ajaxPromise, this);
+
+ if (this.element) {
+ const event = new Event('ajaxPromise');
+ event.promise = ajaxPromise;
+ this.element.dispatchEvent(event);
+ }
+
+ return ajaxPromise;
+ }
+
+ /**
+ * Prepares for updating the partials from the AJAX response.
+ *
+ * If any partials are returned from the AJAX response, this method will also action the partial updates.
+ *
+ * Returns a Promise object which tracks when the partial update is complete.
+ *
+ * @param {Object} response
+ * @returns {Promise}
+ */
+ processUpdate(response) {
+ return new Promise((resolve, reject) => {
+ if (typeof this.options.beforeUpdate === 'function') {
+ if (this.options.beforeUpdate.apply(this, [response]) === false) {
+ reject();
+ return;
+ }
+ }
+
+ // Extract partial information
+ const partials = {};
+ Object.entries(response).forEach((entry) => {
+ const [key, value] = entry;
+
+ if (key.substr(0, 8) !== 'X_WINTER') {
+ partials[key] = value;
+ }
+ });
+
+ if (Object.keys(partials).length === 0) {
+ resolve();
+ return;
+ }
+
+ const promises = this.snowboard.globalPromiseEvent('ajaxBeforeUpdate', response, this);
+ promises.then(
+ () => {
+ this.doUpdate(partials).then(
+ () => {
+ // Allow for HTML redraw
+ window.requestAnimationFrame(() => resolve());
+ },
+ () => {
+ reject();
+ },
+ );
+ },
+ () => {
+ reject();
+ },
+ );
+ });
+ }
+
+ /**
+ * Updates the partials with the given content.
+ *
+ * @param {Object} partials
+ * @returns {Promise}
+ */
+ doUpdate(partials) {
+ return new Promise((resolve) => {
+ const affected = [];
+
+ Object.entries(partials).forEach((entry) => {
+ const [partial, content] = entry;
+
+ let selector = (this.options.update && this.options.update[partial])
+ ? this.options.update[partial]
+ : partial;
+
+ let mode = 'replace';
+
+ if (selector.substr(0, 1) === '@') {
+ mode = 'append';
+ selector = selector.substr(1);
+ } else if (selector.substr(0, 1) === '^') {
+ mode = 'prepend';
+ selector = selector.substr(1);
+ }
+
+ const elements = document.querySelectorAll(selector);
+ if (elements.length > 0) {
+ elements.forEach((element) => {
+ switch (mode) {
+ case 'append':
+ element.innerHTML += content;
+ break;
+ case 'prepend':
+ element.innerHTML = content + element.innerHTML;
+ break;
+ case 'replace':
+ default:
+ element.innerHTML = content;
+ break;
+ }
+
+ affected.push(element);
+
+ // Fire update event for each element that is updated
+ this.snowboard.globalEvent('ajaxUpdate', element, content, this);
+ const event = new Event('ajaxUpdate');
+ event.content = content;
+ element.dispatchEvent(event);
+ });
+ }
+ });
+
+ this.snowboard.globalEvent('ajaxUpdateComplete', affected, this);
+
+ resolve();
+ });
+ }
+
+ /**
+ * Processes the response data.
+ *
+ * This fires off all necessary processing functions depending on the response, ie. if there's any flash
+ * messages to handle, or any redirects to be undertaken.
+ *
+ * @param {Object} response
+ * @returns {void}
+ */
+ processResponse(response) {
+ if (this.options.success && typeof this.options.success === 'function') {
+ if (!this.options.success(this.responseData, this)) {
+ return;
+ }
+ }
+
+ // Allow plugins to cancel any further response handling
+ if (this.snowboard.globalEvent('ajaxSuccess', this.responseData, this) === false) {
+ return;
+ }
+
+ // Allow the element to cancel any further response handling
+ if (this.element) {
+ const event = new Event('ajaxDone', { cancelable: true });
+ event.responseData = this.responseData;
+ event.request = this;
+ this.element.dispatchEvent(event);
+
+ if (event.defaultPrevented) {
+ return;
+ }
+ }
+
+ if (this.flash && response.X_WINTER_FLASH_MESSAGES) {
+ this.processFlashMessages(response.X_WINTER_FLASH_MESSAGES);
+ }
+
+ // Check for a redirect from the response, or use the redirect as specified in the options.
+ if (this.redirect || response.X_WINTER_REDIRECT) {
+ this.processRedirect(this.redirect || response.X_WINTER_REDIRECT);
+ return;
+ }
+
+ if (response.X_WINTER_ASSETS) {
+ this.processAssets(response.X_WINTER_ASSETS);
+ }
+ }
+
+ /**
+ * Processes an error response from the AJAX request.
+ *
+ * This fires off all necessary processing functions depending on the error response, ie. if there's any error or
+ * validation messages to handle.
+ *
+ * @param {Object|Error} error
+ */
+ processError(error) {
+ if (this.options.error && typeof this.options.error === 'function') {
+ if (!this.options.error(this.responseError, this)) {
+ return;
+ }
+ }
+
+ // Allow plugins to cancel any further error handling
+ if (this.snowboard.globalEvent('ajaxError', this.responseError, this) === false) {
+ return;
+ }
+
+ // Allow the element to cancel any further error handling
+ if (this.element) {
+ const event = new Event('ajaxFail', { cancelable: true });
+ event.responseError = this.responseError;
+ event.request = this;
+ this.element.dispatchEvent(event);
+
+ if (event.defaultPrevented) {
+ return;
+ }
+ }
+
+ if (error instanceof Error) {
+ this.processErrorMessage(error.message);
+ } else {
+ // Process validation errors
+ if (error.X_WINTER_ERROR_FIELDS) {
+ this.processValidationErrors(error.X_WINTER_ERROR_FIELDS);
+ }
+
+ if (error.X_WINTER_ERROR_MESSAGE) {
+ this.processErrorMessage(error.X_WINTER_ERROR_MESSAGE);
+ }
+ }
+ }
+
+ /**
+ * Processes a redirect response.
+ *
+ * By default, this processor will simply redirect the user in their browser.
+ *
+ * Plugins can augment this functionality from the `ajaxRedirect` event. You may also override this functionality on
+ * a per-request basis through the `handleRedirectResponse` callback option. If a `false` is returned from either, the
+ * redirect will be cancelled.
+ *
+ * @param {string} url
+ * @returns {void}
+ */
+ processRedirect(url) {
+ // Run a custom per-request redirect handler. If false is returned, don't run the redirect.
+ if (typeof this.options.handleRedirectResponse === 'function') {
+ if (this.options.handleRedirectResponse.apply(this, [url]) === false) {
+ return;
+ }
+ }
+
+ // Allow plugins to cancel the redirect
+ if (this.snowboard.globalEvent('ajaxRedirect', url, this) === false) {
+ return;
+ }
+
+ // Indicate that the AJAX request is finished if we're still on the current page
+ // so that the loading indicator for redirects that just change the hash value of
+ // the URL instead of leaving the page will properly stop.
+ // @see https://github.com/octobercms/october/issues/2780
+ window.addEventListener('popstate', () => {
+ if (this.element) {
+ const event = document.createEvent('CustomEvent');
+ event.eventName = 'ajaxRedirected';
+ this.element.dispatchEvent(event);
+ }
+ }, {
+ once: true,
+ });
+
+ window.location.assign(url);
+ }
+
+ /**
+ * Processes an error message.
+ *
+ * By default, this processor will simply alert the user through a simple `alert()` call.
+ *
+ * Plugins can augment this functionality from the `ajaxErrorMessage` event. You may also override this functionality
+ * on a per-request basis through the `handleErrorMessage` callback option. If a `false` is returned from either, the
+ * error message handling will be cancelled.
+ *
+ * @param {string} message
+ * @returns {void}
+ */
+ processErrorMessage(message) {
+ // Run a custom per-request handler for error messages. If false is returned, do not process the error messages
+ // any further.
+ if (typeof this.options.handleErrorMessage === 'function') {
+ if (this.options.handleErrorMessage.apply(this, [message]) === false) {
+ return;
+ }
+ }
+
+ // Allow plugins to cancel the error message being shown
+ if (this.snowboard.globalEvent('ajaxErrorMessage', message, this) === false) {
+ return;
+ }
+
+ // By default, show a browser error message
+ window.alert(message);
+ }
+
+ /**
+ * Processes flash messages from the response.
+ *
+ * By default, no flash message handling will occur.
+ *
+ * Plugins can augment this functionality from the `ajaxFlashMessages` event. You may also override this functionality
+ * on a per-request basis through the `handleFlashMessages` callback option. If a `false` is returned from either, the
+ * flash message handling will be cancelled.
+ *
+ * @param {Object} messages
+ * @returns
+ */
+ processFlashMessages(messages) {
+ // Run a custom per-request flash handler. If false is returned, don't show the flash message
+ if (typeof this.options.handleFlashMessages === 'function') {
+ if (this.options.handleFlashMessages.apply(this, [messages]) === false) {
+ return;
+ }
+ }
+
+ this.snowboard.globalEvent('ajaxFlashMessages', messages, this);
+ }
+
+ /**
+ * Processes validation errors for fields.
+ *
+ * By default, no validation error handling will occur.
+ *
+ * Plugins can augment this functionality from the `ajaxValidationErrors` event. You may also override this functionality
+ * on a per-request basis through the `handleValidationErrors` callback option. If a `false` is returned from either, the
+ * validation error handling will be cancelled.
+ *
+ * @param {Object} fields
+ * @returns
+ */
+ processValidationErrors(fields) {
+ if (typeof this.options.handleValidationErrors === 'function') {
+ if (this.options.handleValidationErrors.apply(this, [this.form, fields]) === false) {
+ return;
+ }
+ }
+
+ // Allow plugins to cancel the validation errors being handled
+ this.snowboard.globalEvent('ajaxValidationErrors', this.form, fields, this);
+ }
+
+ /**
+ * Confirms the request with the user before proceeding.
+ *
+ * This is an asynchronous method. By default, it will use the browser's `confirm()` method to query the user to
+ * confirm the action. This method will return a Promise with a boolean value depending on whether the user confirmed
+ * or not.
+ *
+ * Plugins can augment this functionality from the `ajaxConfirmMessage` event. You may also override this functionality
+ * on a per-request basis through the `handleConfirmMessage` callback option. If a `false` is returned from either,
+ * the confirmation is assumed to have been denied.
+ *
+ * @returns {Promise}
+ */
+ async doConfirm() {
+ // Allow for a custom handler for the confirmation, per request.
+ if (typeof this.options.handleConfirmMessage === 'function') {
+ if (this.options.handleConfirmMessage.apply(this, [this.confirm]) === false) {
+ return false;
+ }
+
+ return true;
+ }
+
+ // If no plugins have customised the confirmation, use a simple browser confirmation.
+ if (this.snowboard.listensToEvent('ajaxConfirmMessage').length === 0) {
+ return window.confirm(this.confirm);
+ }
+
+ // Run custom plugin confirmations
+ const promises = this.snowboard.globalPromiseEvent('ajaxConfirmMessage', this.confirm, this);
+
+ try {
+ const fulfilled = await promises;
+ if (fulfilled) {
+ return true;
+ }
+ } catch (e) {
+ return false;
+ }
+
+ return false;
+ }
+
+ get form() {
+ if (this.options.form) {
+ return this.options.form;
+ }
+ if (!this.element) {
+ return null;
+ }
+ if (this.element.tagName === 'FORM') {
+ return this.element;
+ }
+
+ return this.element.closest('form');
+ }
+
+ get context() {
+ return {
+ handler: this.handler,
+ options: this.options,
+ };
+ }
+
+ get headers() {
+ const headers = {
+ 'X-Requested-With': 'XMLHttpRequest', // Keeps compatibility with jQuery AJAX
+ 'X-WINTER-REQUEST-HANDLER': this.handler,
+ 'X-WINTER-REQUEST-PARTIALS': this.extractPartials(this.options.update || []),
+ };
+
+ if (this.flash) {
+ headers['X-WINTER-REQUEST-FLASH'] = 1;
+ }
+
+ if (this.xsrfToken) {
+ headers['X-XSRF-TOKEN'] = this.xsrfToken;
+ }
+
+ return headers;
+ }
+
+ get loading() {
+ return this.options.loading || false;
+ }
+
+ get url() {
+ return this.options.url || window.location.href;
+ }
+
+ get redirect() {
+ return (this.options.redirect && this.options.redirect.length) ? this.options.redirect : null;
+ }
+
+ get flash() {
+ return this.options.flash || false;
+ }
+
+ get files() {
+ if (this.options.files === true) {
+ if (FormData === undefined) {
+ this.snowboard.debug('This browser does not support file uploads');
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ get xsrfToken() {
+ return this.snowboard.cookie().get('XSRF-TOKEN');
+ }
+
+ get data() {
+ const data = (typeof this.options.data === 'object') ? this.options.data : {};
+
+ const formData = new FormData(this.form || undefined);
+ if (Object.keys(data).length > 0) {
+ Object.entries(data).forEach((entry) => {
+ const [key, value] = entry;
+ formData.append(key, value);
+ });
+ }
+
+ return formData;
+ }
+
+ get confirm() {
+ return this.options.confirm || false;
+ }
+
+ /**
+ * Extracts partials.
+ *
+ * @param {Object} update
+ * @returns {string}
+ */
+ extractPartials(update) {
+ return Object.keys(update).join('&');
+ }
+
+ /**
+ * Renders an error with useful debug information.
+ *
+ * This method is used internally when the AJAX request could not be completed or processed correctly due to an error.
+ *
+ * @param {string} message
+ * @param {string} exception
+ * @param {string} file
+ * @param {Number} line
+ * @param {string[]} trace
+ * @returns {Error}
+ */
+ renderError(message, exception, file, line, trace) {
+ const error = new Error(message);
+ error.exception = exception || null;
+ error.file = file || null;
+ error.line = line || null;
+ error.trace = trace || [];
+ return error;
+ }
+}
+
+Snowboard.addPlugin('request', Request);
diff --git a/modules/system/assets/js/snowboard/ajax/handlers/AttributeRequest.js b/modules/system/assets/js/snowboard/ajax/handlers/AttributeRequest.js
new file mode 100644
index 000000000..61effed78
--- /dev/null
+++ b/modules/system/assets/js/snowboard/ajax/handlers/AttributeRequest.js
@@ -0,0 +1,327 @@
+/**
+ * Enable Data Attributes API for AJAX requests.
+ *
+ * This is an extension of the base AJAX functionality that includes handling of HTML data attributes for processing
+ * AJAX requests. It is separated from the base AJAX functionality to allow developers to opt-out of data attribute
+ * requests if they do not intend to use them.
+ *
+ * @copyright 2021 Winter.
+ * @author Ben Thomson
+ */
+class AttributeRequest extends Snowboard.Singleton {
+ /**
+ * Listeners.
+ *
+ * @returns {Object}
+ */
+ listens() {
+ return {
+ ready: 'ready',
+ ajaxSetup: 'onAjaxSetup',
+ };
+ }
+
+ /**
+ * Ready event callback.
+ *
+ * Attaches handlers to the window to listen for all request interactions.
+ */
+ ready() {
+ this.attachHandlers();
+ this.disableDefaultFormValidation();
+ }
+
+ /**
+ * Dependencies.
+ *
+ * @returns {string[]}
+ */
+ dependencies() {
+ return ['request', 'jsonParser'];
+ }
+
+ /**
+ * Destructor.
+ *
+ * Detaches all handlers.
+ */
+ destructor() {
+ this.detachHandlers();
+
+ super.destructor();
+ }
+
+ /**
+ * Attaches the necessary handlers for all request interactions.
+ */
+ attachHandlers() {
+ window.addEventListener('change', (event) => this.changeHandler(event));
+ window.addEventListener('click', (event) => this.clickHandler(event));
+ window.addEventListener('keydown', (event) => this.keyDownHandler(event));
+ window.addEventListener('submit', (event) => this.submitHandler(event));
+ }
+
+ /**
+ * Disables default form validation for AJAX forms.
+ *
+ * A form that contains a `data-request` attribute to specify an AJAX call without including a `data-browser-validate`
+ * attribute means that the AJAX callback function will likely be handling the validation instead.
+ */
+ disableDefaultFormValidation() {
+ document.querySelectorAll('form[data-request]:not([data-browser-validate])').forEach((form) => {
+ form.setAttribute('novalidate', true);
+ });
+ }
+
+ /**
+ * Detaches the necessary handlers for all request interactions.
+ */
+ detachHandlers() {
+ window.removeEventListener('change', (event) => this.changeHandler(event));
+ window.removeEventListener('click', (event) => this.clickHandler(event));
+ window.removeEventListener('keydown', (event) => this.keyDownHandler(event));
+ window.removeEventListener('submit', (event) => this.submitHandler(event));
+ }
+
+ /**
+ * Handles changes to select, radio, checkbox and file inputs.
+ *
+ * @param {Event} event
+ */
+ changeHandler(event) {
+ // Check that we are changing a valid element
+ if (!event.target.matches(
+ 'select[data-request], input[type=radio][data-request], input[type=checkbox][data-request], input[type=file][data-request]',
+ )) {
+ return;
+ }
+
+ this.processRequestOnElement(event.target);
+ }
+
+ /**
+ * Handles clicks on hyperlinks and buttons.
+ *
+ * This event can bubble up the hierarchy to find a suitable request element.
+ *
+ * @param {Event} event
+ */
+ clickHandler(event) {
+ let currentElement = event.target;
+
+ while (currentElement.tagName !== 'HTML') {
+ if (!currentElement.matches(
+ 'a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]',
+ )) {
+ currentElement = currentElement.parentElement;
+ } else {
+ event.preventDefault();
+ this.processRequestOnElement(currentElement);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Handles key presses on inputs
+ *
+ * @param {Event} event
+ */
+ keyDownHandler(event) {
+ // Check that we are inputting into a valid element
+ if (!event.target.matches(
+ 'input',
+ )) {
+ return;
+ }
+
+ // Check that the input type is valid
+ const validTypes = [
+ 'checkbox',
+ 'color',
+ 'date',
+ 'datetime',
+ 'datetime-local',
+ 'email',
+ 'image',
+ 'month',
+ 'number',
+ 'password',
+ 'radio',
+ 'range',
+ 'search',
+ 'tel',
+ 'text',
+ 'time',
+ 'url',
+ 'week',
+ ];
+ if (validTypes.indexOf(event.target.getAttribute('type')) === -1) {
+ return;
+ }
+
+ if (event.key === 'Enter' && event.target.matches('*[data-request]')) {
+ this.processRequestOnElement(event.target);
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ } else if (event.target.matches('*[data-track-input]')) {
+ this.trackInput(event.target);
+ }
+ }
+
+ /**
+ * Handles form submissions.
+ *
+ * @param {Event} event
+ */
+ submitHandler(event) {
+ // Check that we are submitting a valid form
+ if (!event.target.matches(
+ 'form[data-request]',
+ )) {
+ return;
+ }
+
+ event.preventDefault();
+
+ this.processRequestOnElement(event.target);
+ }
+
+ /**
+ * Processes a request on a given element, using its data attributes.
+ *
+ * @param {HTMLElement} element
+ */
+ processRequestOnElement(element) {
+ const data = element.dataset;
+
+ const handler = String(data.request);
+ const options = {
+ confirm: ('requestConfirm' in data) ? String(data.requestConfirm) : null,
+ redirect: ('requestRedirect' in data) ? String(data.requestRedirect) : null,
+ loading: ('requestLoading' in data) ? String(data.requestLoading) : null,
+ flash: ('requestFlash' in data),
+ files: ('requestFiles' in data),
+ browserValidate: ('requestBrowserValidate' in data),
+ form: ('requestForm' in data) ? String(data.requestForm) : null,
+ url: ('requestUrl' in data) ? String(data.requestUrl) : null,
+ update: ('requestUpdate' in data) ? this.parseData(String(data.requestUpdate)) : [],
+ data: ('requestData' in data) ? this.parseData(String(data.requestData)) : [],
+ };
+
+ this.snowboard.request(element, handler, options);
+ }
+
+ /**
+ * Sets up an AJAX request via HTML attributes.
+ *
+ * @param {Request} request
+ */
+ onAjaxSetup(request) {
+ const fieldName = request.element.getAttribute('name');
+
+ const data = {
+ ...this.getParentRequestData(request.element),
+ ...request.options.data,
+ };
+
+ if (request.element && request.element.matches('input, textarea, select, button') && !request.form && fieldName && !request.options.data[fieldName]) {
+ data[fieldName] = request.element.value;
+ }
+
+ request.options.data = data;
+ }
+
+ /**
+ * Parses and collates all data from elements up the DOM hierarchy.
+ *
+ * @param {Element} target
+ * @returns {Object}
+ */
+ getParentRequestData(target) {
+ const elements = [];
+ let data = {};
+ let currentElement = target;
+
+ while (currentElement.parentElement && currentElement.parentElement.tagName !== 'HTML') {
+ elements.push(currentElement.parentElement);
+ currentElement = currentElement.parentElement;
+ }
+
+ elements.reverse();
+
+ elements.forEach((element) => {
+ const elementData = element.dataset;
+
+ if ('requestData' in elementData) {
+ data = {
+ ...data,
+ ...this.parseData(elementData.requestData),
+ };
+ }
+ });
+
+ return data;
+ }
+
+ /**
+ * Parses data in the Winter/October JSON format.
+ *
+ * @param {String} data
+ * @returns {Object}
+ */
+ parseData(data) {
+ let value;
+
+ if (data === undefined) {
+ value = '';
+ }
+ if (typeof value === 'object') {
+ return value;
+ }
+
+ try {
+ return this.snowboard.jsonparser().parse(`{${data}}`);
+ } catch (e) {
+ throw new Error(`Error parsing the data attribute on element: ${e.message}`);
+ }
+ }
+
+ trackInput(element) {
+ const { lastValue } = element.dataset;
+ const interval = element.dataset.trackInput || 300;
+
+ if (lastValue !== undefined && lastValue === element.value) {
+ return;
+ }
+
+ this.resetTrackInputTimer(element);
+
+ element.dataset.trackInput = window.setTimeout(() => {
+ if (element.dataset.request) {
+ this.processRequestOnElement(element);
+ return;
+ }
+
+ // Traverse up the hierarchy and find a form that sends an AJAX query
+ let currentElement = element;
+ while (currentElement.parentElement && currentElement.parentElement.tagName !== 'HTML') {
+ currentElement = currentElement.parentElement;
+
+ if (currentElement.tagName === 'FORM' && currentElement.dataset.request) {
+ this.processRequestOnElement(currentElement);
+ break;
+ }
+ }
+ }, interval);
+ }
+
+ resetTrackInputTimer(element) {
+ if (element.dataset.trackInput) {
+ window.clearTimeout(element.dataset.trackInput);
+ element.dataset.trackInput = null;
+ }
+ }
+}
+
+Snowboard.addPlugin('attributeRequest', AttributeRequest);
diff --git a/modules/system/assets/js/snowboard/build/snowboard.base.debug.js b/modules/system/assets/js/snowboard/build/snowboard.base.debug.js
new file mode 100644
index 000000000..5ab4e9074
--- /dev/null
+++ b/modules/system/assets/js/snowboard/build/snowboard.base.debug.js
@@ -0,0 +1,3 @@
+!function(){var t={2220:function(t,e,n){var r=n(2569),o=n(4354),i=n(3700),s=r.TypeError;t.exports=function(t){if(o(t))return t;throw s(i(t)+" is not a function")}},3467:function(t,e,n){var r=n(2569),o=n(4354),i=r.String,s=r.TypeError;t.exports=function(t){if("object"==typeof t||o(t))return t;throw s("Can't set "+i(t)+" as a prototype")}},2834:function(t,e,n){var r=n(2931),o=n(9062),i=n(378),s=r("unscopables"),c=Array.prototype;null==c[s]&&i.f(c,s,{configurable:!0,value:o(null)}),t.exports=function(t){c[s][t]=!0}},1421:function(t,e,n){var r=n(2569),o=n(1651),i=r.String,s=r.TypeError;t.exports=function(t){if(o(t))return t;throw s(i(t)+" is not an object")}},4041:function(t,e,n){var r=n(7830),o=n(7841),i=n(6095),s=function(t){return function(e,n,s){var c,a=r(e),u=i(a),f=o(s,u);if(t&&n!=n){for(;u>f;)if((c=a[f++])!=c)return!0}else for(;u>f;f++)if((t||f in a)&&a[f]===n)return t||f||0;return!t&&-1}};t.exports={includes:s(!0),indexOf:s(!1)}},8250:function(t,e,n){var r=n(1540),o=r({}.toString),i=r("".slice);t.exports=function(t){return i(o(t),8,-1)}},8778:function(t,e,n){var r=n(5320),o=n(6830),i=n(892),s=n(378);t.exports=function(t,e,n){for(var c=o(e),a=s.f,u=i.f,f=0;f0&&r[0]<4?1:+(r[0]+r[1])),!o&&s&&(!(r=s.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=s.match(/Chrome\/(\d+)/))&&(o=+r[1]),t.exports=o},4328:function(t){t.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},7641:function(t,e,n){var r=n(2569),o=n(892).f,i=n(7632),s=n(8946),c=n(2024),a=n(8778),u=n(8787);t.exports=function(t,e){var n,f,l,h,p,g=t.target,d=t.global,b=t.stat;if(n=d?r:b?r[g]||c(g,{}):(r[g]||{}).prototype)for(f in e){if(h=e[f],l=t.noTargetGet?(p=o(n,f))&&p.value:n[f],!u(d?f:g+(b?".":"#")+f,t.forced)&&void 0!==l){if(typeof h==typeof l)continue;a(h,l)}(t.sham||l&&l.sham)&&i(h,"sham",!0),s(n,f,h,t)}}},2112:function(t){t.exports=function(t){try{return!!t()}catch(t){return!0}}},9581:function(t,e,n){var r=n(2112);t.exports=!r((function(){var t=function(){}.bind();return"function"!=typeof t||t.hasOwnProperty("prototype")}))},7425:function(t,e,n){var r=n(9581),o=Function.prototype.call;t.exports=r?o.bind(o):function(){return o.apply(o,arguments)}},34:function(t,e,n){var r=n(1738),o=n(5320),i=Function.prototype,s=r&&Object.getOwnPropertyDescriptor,c=o(i,"name"),a=c&&"something"===function(){}.name,u=c&&(!r||r&&s(i,"name").configurable);t.exports={EXISTS:c,PROPER:a,CONFIGURABLE:u}},1540:function(t,e,n){var r=n(9581),o=Function.prototype,i=o.bind,s=o.call,c=r&&i.bind(s,s);t.exports=r?function(t){return t&&c(t)}:function(t){return t&&function(){return s.apply(t,arguments)}}},2430:function(t,e,n){var r=n(2569),o=n(4354),i=function(t){return o(t)?t:void 0};t.exports=function(t,e){return arguments.length<2?i(r[t]):r[t]&&r[t][e]}},5324:function(t,e,n){var r=n(2220);t.exports=function(t,e){var n=t[e];return null==n?void 0:r(n)}},2569:function(t,e,n){var r=function(t){return t&&t.Math==Math&&t};t.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||Function("return this")()},5320:function(t,e,n){var r=n(1540),o=n(6416),i=r({}.hasOwnProperty);t.exports=Object.hasOwn||function(t,e){return i(o(t),e)}},9012:function(t){t.exports={}},99:function(t,e,n){var r=n(2430);t.exports=r("document","documentElement")},8232:function(t,e,n){var r=n(1738),o=n(2112),i=n(7934);t.exports=!r&&!o((function(){return 7!=Object.defineProperty(i("div"),"a",{get:function(){return 7}}).a}))},6674:function(t,e,n){var r=n(2569),o=n(1540),i=n(2112),s=n(8250),c=r.Object,a=o("".split);t.exports=i((function(){return!c("z").propertyIsEnumerable(0)}))?function(t){return"String"==s(t)?a(t,""):c(t)}:c},5193:function(t,e,n){var r=n(1540),o=n(4354),i=n(7039),s=r(Function.toString);o(i.inspectSource)||(i.inspectSource=function(t){return s(t)}),t.exports=i.inspectSource},3500:function(t,e,n){var r,o,i,s=n(5965),c=n(2569),a=n(1540),u=n(1651),f=n(7632),l=n(5320),h=n(7039),p=n(9097),g=n(9012),d="Object already initialized",b=c.TypeError,v=c.WeakMap;if(s||h.state){var y=h.state||(h.state=new v),w=a(y.get),m=a(y.has),O=a(y.set);r=function(t,e){if(m(y,t))throw new b(d);return e.facade=t,O(y,t,e),e},o=function(t){return w(y,t)||{}},i=function(t){return m(y,t)}}else{var S=p("state");g[S]=!0,r=function(t,e){if(l(t,S))throw new b(d);return e.facade=t,f(t,S,e),e},o=function(t){return l(t,S)?t[S]:{}},i=function(t){return l(t,S)}}t.exports={set:r,get:o,has:i,enforce:function(t){return i(t)?o(t):r(t,{})},getterFor:function(t){return function(e){var n;if(!u(e)||(n=o(e)).type!==t)throw b("Incompatible receiver, "+t+" required");return n}}}},4354:function(t){t.exports=function(t){return"function"==typeof t}},8787:function(t,e,n){var r=n(2112),o=n(4354),i=/#|\.prototype\./,s=function(t,e){var n=a[c(t)];return n==f||n!=u&&(o(e)?r(e):!!e)},c=s.normalize=function(t){return String(t).replace(i,".").toLowerCase()},a=s.data={},u=s.NATIVE="N",f=s.POLYFILL="P";t.exports=s},1651:function(t,e,n){var r=n(4354);t.exports=function(t){return"object"==typeof t?null!==t:r(t)}},1274:function(t){t.exports=!1},8937:function(t,e,n){var r=n(2569),o=n(2430),i=n(4354),s=n(7652),c=n(7374),a=r.Object;t.exports=c?function(t){return"symbol"==typeof t}:function(t){var e=o("Symbol");return i(e)&&s(e.prototype,a(t))}},5756:function(t,e,n){"use strict";var r,o,i,s=n(2112),c=n(4354),a=n(9062),u=n(9299),f=n(8946),l=n(2931),h=n(1274),p=l("iterator"),g=!1;[].keys&&("next"in(i=[].keys())?(o=u(u(i)))!==Object.prototype&&(r=o):g=!0),null==r||s((function(){var t={};return r[p].call(t)!==t}))?r={}:h&&(r=a(r)),c(r[p])||f(r,p,(function(){return this})),t.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:g}},9259:function(t){t.exports={}},6095:function(t,e,n){var r=n(9309);t.exports=function(t){return r(t.length)}},5598:function(t,e,n){var r=n(9318),o=n(2112);t.exports=!!Object.getOwnPropertySymbols&&!o((function(){var t=Symbol();return!String(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&&r&&r<41}))},5965:function(t,e,n){var r=n(2569),o=n(4354),i=n(5193),s=r.WeakMap;t.exports=o(s)&&/native code/.test(i(s))},9062:function(t,e,n){var r,o=n(1421),i=n(3116),s=n(4328),c=n(9012),a=n(99),u=n(7934),f=n(9097),l=f("IE_PROTO"),h=function(){},p=function(t){return"' +
- '',
- {
- beforeParse: (window) => {
- // Mock XHR for tests below
- xhr = sinon.useFakeXMLHttpRequest()
- xhr.onCreate = (request) => {
- requests.push(request)
- }
- window.XMLHttpRequest = xhr
-
- // Allow window.location.assign() to be stubbed
- delete window.location
- window.location = {
- href: 'https://winter.example.org/',
- assign: sinon.stub()
- }
- }
- }
- )
- window = dom.window
-
- // Enable CORS on jQuery
- window.jqueryScript.onload = () => {
- window.jQuery.support.cors = true
- }
- })
-
- afterEach(() => {
- // Close window and restore XHR functionality to default
- window.XMLHttpRequest = sinon.xhr.XMLHttpRequest
- window.close()
- requests = []
- })
-
- it('can make a successful AJAX request', function (done) {
- window.frameworkScript.onload = () => {
- window.$.request('test::onTest', {
- success: function () {
- done()
- },
- error: function () {
- done(new Error('AJAX call failed'))
- }
- })
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a successful response from the server
- requests[1].respond(
- 200,
- {
- 'Content-Type': 'application/json'
- },
- JSON.stringify({
- 'successful': true
- })
- )
- }
- })
-
- it('can make an unsuccessful AJAX request', function (done) {
- window.frameworkScript.onload = () => {
- window.$.request('test::onTest', {
- success: function () {
- done(new Error('AJAX call succeeded'))
- },
- error: function () {
- done()
- }
- })
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a 404 Not Found response from the server
- requests[1].respond(
- 404,
- {
- 'Content-Type': 'text/html'
- },
- ''
- )
- }
- })
-
- it('can update a partial via an ID selector', function (done) {
- window.frameworkScript.onload = () => {
- window.$.request('test::onTest', {
- complete: function () {
- let partialContent = dom.window.document.getElementById('partialId').textContent
- try {
- assert(
- partialContent === 'Content passed through AJAX',
- 'Partial content incorrect - ' +
- 'expected "Content passed through AJAX", ' +
- 'found "' + partialContent + '"'
- )
- done()
- } catch (e) {
- done(e)
- }
- }
- })
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a response from the server that includes a partial change via ID
- requests[1].respond(
- 200,
- {
- 'Content-Type': 'application/json'
- },
- JSON.stringify({
- '#partialId': 'Content passed through AJAX'
- })
- )
- }
- })
-
- it('can update a partial via a class selector', function (done) {
- window.frameworkScript.onload = () => {
- window.$.request('test::onTest', {
- complete: function () {
- let partialContent = dom.window.document.getElementById('partialId').textContent
- try {
- assert(
- partialContent === 'Content passed through AJAX',
- 'Partial content incorrect - ' +
- 'expected "Content passed through AJAX", ' +
- 'found "' + partialContent + '"'
- )
- done()
- } catch (e) {
- done(e)
- }
- }
- })
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a response from the server that includes a partial change via a class
- requests[1].respond(
- 200,
- {
- 'Content-Type': 'application/json'
- },
- JSON.stringify({
- '.partialClass': 'Content passed through AJAX'
- })
- )
- }
- })
-
- it('can redirect after a successful AJAX request', function (done) {
- this.timeout(1000)
-
- // Detect a redirect
- window.location.assign.callsFake((url) => {
- try {
- assert(
- url === '/test/success',
- 'Non-matching redirect URL'
- )
- done()
- } catch (e) {
- done(e)
- }
- })
-
- window.frameworkScript.onload = () => {
- window.$.request('test::onTest', {
- redirect: '/test/success',
- })
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a successful response from the server
- requests[1].respond(
- 200,
- {
- 'Content-Type': 'application/json'
- },
- JSON.stringify({
- 'successful': true
- })
- )
- }
- })
-
- it('can send extra data with the AJAX request', function (done) {
- this.timeout(1000)
-
- window.frameworkScript.onload = () => {
- window.$.request('test::onTest', {
- data: {
- test1: 'First',
- test2: 'Second'
- },
- success: function () {
- done()
- }
- })
-
- try {
- assert(
- requests[1].requestBody === 'test1=First&test2=Second',
- 'Data incorrect or not included in request'
- )
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a successful response from the server
- requests[1].respond(
- 200,
- {
- 'Content-Type': 'application/json'
- },
- JSON.stringify({
- 'successful': true
- })
- )
- }
- })
-
- it('can call a beforeUpdate handler', function (done) {
- const beforeUpdate = function (data, status, jqXHR) {
- }
- const beforeUpdateSpy = sinon.spy(beforeUpdate)
-
- window.frameworkScript.onload = () => {
- window.$.request('test::onTest', {
- beforeUpdate: beforeUpdateSpy
- })
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a successful response from the server
- requests[1].respond(
- 200,
- {
- 'Content-Type': 'application/json'
- },
- JSON.stringify({
- 'successful': true
- })
- )
-
- try {
- assert(
- beforeUpdateSpy.withArgs(
- {
- 'successful': true
- },
- 'success'
- ).calledOnce
- )
- done()
- } catch (e) {
- done(e)
- }
- }
- })
- })
-
- describe('ajaxRequests through HTML attributes', function () {
- let dom,
- window,
- xhr,
- requests = []
-
- this.timeout(1000)
-
- beforeEach(() => {
- // Load framework.js in the fake DOM
- dom = fakeDom(
- '' +
- '' +
- '' +
- 'Initial content
' +
- '' +
- '',
- {
- beforeParse: (window) => {
- // Mock XHR for tests below
- xhr = sinon.useFakeXMLHttpRequest()
- xhr.onCreate = (request) => {
- requests.push(request)
- }
- window.XMLHttpRequest = xhr
-
- // Add a stub for the request handlers
- window.test = sinon.stub()
-
- // Add a spy for the beforeUpdate handler
- window.beforeUpdate = function (element, data, status) {
- }
- window.beforeUpdateSpy = sinon.spy(window.beforeUpdate)
-
- // Stub out window.alert
- window.alert = sinon.stub()
-
- // Allow window.location.assign() to be stubbed
- delete window.location
- window.location = {
- href: 'https://winter.example.org/',
- assign: sinon.stub()
- }
- }
- }
- )
- window = dom.window
-
- // Enable CORS on jQuery
- window.jqueryScript.onload = () => {
- window.jQuery.support.cors = true
- }
- })
-
- afterEach(() => {
- // Close window and restore XHR functionality to default
- window.XMLHttpRequest = sinon.xhr.XMLHttpRequest
- window.close()
- requests = []
- })
-
- it('can make a successful AJAX request', function (done) {
- window.frameworkScript.onload = () => {
- window.test.callsFake((response) => {
- assert(response === 'success', 'Response handler was not "success"')
- done()
- })
-
- window.$('a#standard').click()
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a successful response from the server
- requests[1].respond(
- 200,
- {
- 'Content-Type': 'application/json'
- },
- JSON.stringify({
- 'successful': true
- })
- )
- }
- })
-
- it('can make an unsuccessful AJAX request', function (done) {
- window.frameworkScript.onload = () => {
- window.test.callsFake((response) => {
- assert(response === 'failure', 'Response handler was not "failure"')
- done()
- })
-
- window.$('a#standard').click()
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a 404 Not Found response from the server
- requests[1].respond(
- 404,
- {
- 'Content-Type': 'text/html'
- },
- ''
- )
- }
- })
-
-
- it('can update a partial via an ID selector', function (done) {
- window.frameworkScript.onload = () => {
- window.test.callsFake(() => {
- let partialContent = dom.window.document.getElementById('partialId').textContent
- try {
- assert(
- partialContent === 'Content passed through AJAX',
- 'Partial content incorrect - ' +
- 'expected "Content passed through AJAX", ' +
- 'found "' + partialContent + '"'
- )
- done()
- } catch (e) {
- done(e)
- }
- })
-
- window.$('a#standard').click()
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a response from the server that includes a partial change via ID
- requests[1].respond(
- 200,
- {
- 'Content-Type': 'application/json'
- },
- JSON.stringify({
- '#partialId': 'Content passed through AJAX'
- })
- )
- }
- })
-
- it('can update a partial via a class selector', function (done) {
- window.frameworkScript.onload = () => {
- window.test.callsFake(() => {
- let partialContent = dom.window.document.getElementById('partialId').textContent
- try {
- assert(
- partialContent === 'Content passed through AJAX',
- 'Partial content incorrect - ' +
- 'expected "Content passed through AJAX", ' +
- 'found "' + partialContent + '"'
- )
- done()
- } catch (e) {
- done(e)
- }
- })
-
- window.$('a#standard').click()
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a response from the server that includes a partial change via a class
- requests[1].respond(
- 200,
- {
- 'Content-Type': 'application/json'
- },
- JSON.stringify({
- '.partialClass': 'Content passed through AJAX'
- })
- )
- }
- })
-
- it('can redirect after a successful AJAX request', function (done) {
- this.timeout(1000)
-
- // Detect a redirect
- window.location.assign.callsFake((url) => {
- try {
- assert(
- url === '/test/success',
- 'Non-matching redirect URL'
- )
- done()
- } catch (e) {
- done(e)
- }
- })
-
- window.frameworkScript.onload = () => {
- window.$('a#redirect').click()
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a successful response from the server
- requests[1].respond(
- 200,
- {
- 'Content-Type': 'application/json'
- },
- JSON.stringify({
- 'succesful': true
- })
- )
- }
- })
-
- it('can send extra data with the AJAX request', function (done) {
- this.timeout(1000)
-
- window.frameworkScript.onload = () => {
- window.test.callsFake((response) => {
- assert(response === 'success', 'Response handler was not "success"')
- done()
- })
-
- window.$('a#dataLink').click()
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a successful response from the server
- requests[1].respond(
- 200,
- {
- 'Content-Type': 'application/json'
- },
- JSON.stringify({
- 'succesful': true
- })
- )
- }
- })
-
- it('can call a beforeUpdate handler', function (done) {
- this.timeout(1000)
-
- window.frameworkScript.onload = () => {
- window.test.callsFake((response) => {
- assert(response === 'success', 'Response handler was not "success"')
- })
-
- window.$('a#dataLink').click()
-
- try {
- assert(
- requests[1].requestHeaders['X-WINTER-REQUEST-HANDLER'] === 'test::onTest',
- 'Incorrect Winter request handler'
- )
- } catch (e) {
- done(e)
- }
-
- // Mock a successful response from the server
- requests[1].respond(
- 200,
- {
- 'Content-Type': 'application/json'
- },
- JSON.stringify({
- 'successful': true
- })
- )
-
- try {
- assert(
- window.beforeUpdateSpy.withArgs(
- window.$('a#dataLink').get(),
- {
- 'successful': true
- },
- 'success'
- ).calledOnce,
- 'beforeUpdate handler never called, or incorrect arguments provided'
- )
- done()
- } catch (e) {
- done(e)
- }
- }
- })
- })
-})
diff --git a/tests/js/cases/system/ui.select.test.js b/tests/js/cases/system/ui.select.test.js
deleted file mode 100644
index 17f854789..000000000
--- a/tests/js/cases/system/ui.select.test.js
+++ /dev/null
@@ -1,111 +0,0 @@
-import { assert } from 'chai'
-import fakeDom from 'helpers/fakeDom'
-
-describe('modules/system/assets/ui/js/select.js', function () {
- describe('AJAX processResults function', function () {
- let dom,
- window,
- processResults,
- keyValResultFormat = {
- value1: 'text1',
- value2: 'text2'
- },
- select2ResultFormat = [
- {
- id: 'value1',
- text: 'text1',
- disabled: true
- },
- {
- id: 'value2',
- text: 'text2',
- selected: false
- }
- ]
-
- this.timeout(1000)
-
- beforeEach((done) => {
- // Load framework.js and select.js in the fake DOM
- dom = fakeDom(`
-
-
-
-
- `)
-
- window = dom.window
-
- window.selectScript.onload = () => {
- window.jQuery.fn.select2 = function(options) {
- processResults = options.ajax.processResults
- done()
- }
- }
- })
-
- afterEach(() => {
- window.close()
- })
-
- it('supports a key-value mapping on the "result" key', function () {
- let result = processResults({ result: keyValResultFormat })
- assert.deepEqual(result, { results: [
- {
- id: 'value1',
- text: 'text1'
- },
- {
- id: 'value2',
- text: 'text2'
- }
- ]})
- })
-
- it('supports a key-value mapping on the "results" key', function() {
- let result = processResults({ results: keyValResultFormat })
- assert.deepEqual(result, { results: [
- {
- id: 'value1',
- text: 'text1'
- },
- {
- id: 'value2',
- text: 'text2'
- }
- ]})
- })
-
- it('passes through other data provided with key-value mapping', function() {
- let result = processResults({ result: keyValResultFormat, other1: 1, other2: '2' })
- assert.include(result, { other1: 1, other2: '2'})
- })
-
- it('supports the Select2 result format on the "result" key', function() {
- let result = processResults({ result: select2ResultFormat })
- assert.deepEqual(result, { results: select2ResultFormat })
- })
-
- it('passes through the Select2 result format on the "results" key', function() {
- let result = processResults({ results: select2ResultFormat })
- assert.deepEqual(result, { results: select2ResultFormat })
- })
-
- it('passes through other data provided with Select2 results format', function() {
- let result = processResults({ results: select2ResultFormat, pagination: { more: true }, other: 'value' })
- assert.deepInclude(result, { pagination: { more: true }, other: 'value' })
- })
-
- it('passes through the Select2 format with a group as the first entry', function() {
- let data = [
- {
- text: 'Label',
- children: select2ResultFormat
- }
- ]
-
- let result = processResults({ results: data })
- assert.deepEqual(result, { results: data })
- })
- })
-})
diff --git a/tests/js/fixtures/framework/TestListener.js b/tests/js/fixtures/framework/TestListener.js
new file mode 100644
index 000000000..5bd4f6bff
--- /dev/null
+++ b/tests/js/fixtures/framework/TestListener.js
@@ -0,0 +1,18 @@
+/* globals window */
+
+((Snowboard) => {
+ class TestListener extends Snowboard.Singleton {
+ listens() {
+ return {
+ eventOne: 'eventOne',
+ eventTwo: 'notExists'
+ };
+ }
+
+ eventOne(arg) {
+ this.eventResult = 'Event called with arg ' + arg;
+ }
+ }
+
+ Snowboard.addPlugin('test', TestListener);
+})(window.Snowboard);
diff --git a/tests/js/fixtures/framework/TestPlugin.js b/tests/js/fixtures/framework/TestPlugin.js
new file mode 100644
index 000000000..568d6a6b9
--- /dev/null
+++ b/tests/js/fixtures/framework/TestPlugin.js
@@ -0,0 +1,11 @@
+/* globals window */
+
+((Snowboard) => {
+ class TestPlugin extends Snowboard.PluginBase {
+ testMethod() {
+ return 'Tested';
+ }
+ }
+
+ Snowboard.addPlugin('test', TestPlugin);
+})(window.Snowboard);
diff --git a/tests/js/fixtures/framework/TestPromiseListener.js b/tests/js/fixtures/framework/TestPromiseListener.js
new file mode 100644
index 000000000..1126ed93e
--- /dev/null
+++ b/tests/js/fixtures/framework/TestPromiseListener.js
@@ -0,0 +1,28 @@
+/* globals window */
+
+((Snowboard) => {
+ class TestPromiseListener extends Snowboard.Singleton {
+ listens() {
+ return {
+ promiseOne: 'promiseOne',
+ promiseTwo: 'promiseTwo'
+ };
+ }
+
+ promiseOne(arg) {
+ return new Promise((resolve) => {
+ window.setTimeout(() => {
+ this.eventResult = 'Event called with arg ' + arg;
+ resolve();
+ }, 500);
+ });
+ }
+
+ promiseTwo(arg) {
+ this.eventResult = 'Promise two called with arg ' + arg;
+ return true;
+ }
+ }
+
+ Snowboard.addPlugin('test', TestPromiseListener);
+})(window.Snowboard);
diff --git a/tests/js/fixtures/framework/TestSingleton.js b/tests/js/fixtures/framework/TestSingleton.js
new file mode 100644
index 000000000..45989248b
--- /dev/null
+++ b/tests/js/fixtures/framework/TestSingleton.js
@@ -0,0 +1,11 @@
+/* globals window */
+
+((Snowboard) => {
+ class TestSingleton extends Snowboard.Singleton {
+ testMethod() {
+ return 'Tested';
+ }
+ }
+
+ Snowboard.addPlugin('test', TestSingleton);
+})(window.Snowboard);
diff --git a/tests/js/helpers/FakeDom.js b/tests/js/helpers/FakeDom.js
new file mode 100644
index 000000000..42d530278
--- /dev/null
+++ b/tests/js/helpers/FakeDom.js
@@ -0,0 +1,156 @@
+/* globals __dirname, URL */
+
+import { JSDOM } from 'jsdom'
+import path from 'path'
+
+export default class FakeDom
+{
+ constructor(content, options)
+ {
+ if (options === undefined) {
+ options = {}
+ }
+
+ // Header settings
+ this.url = options.url || `file://${path.resolve(__dirname, '../../')}`
+ this.referer = options.referer
+ this.contentType = options.contentType || 'text/html'
+
+ // Content settings
+ this.head = options.head || 'Fake document'
+ this.bodyStart = options.bodyStart || ''
+ this.content = content || ''
+ this.bodyEnd = options.bodyEnd || ''
+ this.foot = options.foot || ''
+
+ // Callback settings
+ this.beforeParse = (typeof options.beforeParse === 'function')
+ ? options.beforeParse
+ : undefined
+
+ // Scripts
+ this.scripts = []
+ this.inline = []
+ }
+
+ static new(content, options)
+ {
+ return new FakeDom(content, options)
+ }
+
+ setContent(content)
+ {
+ this.content = content
+ return this
+ }
+
+ addScript(script, id)
+ {
+ if (Array.isArray(script)) {
+ script.forEach((item) => {
+ this.addScript(item)
+ })
+
+ return this
+ }
+
+ let url = new URL(script, this.url)
+ let base = new URL(this.url)
+
+ if (url.host === base.host) {
+ this.scripts.push({
+ url: `${url.pathname}`,
+ id: id || this.generateId(),
+ })
+ } else {
+ this.scripts.push({
+ url,
+ id: id || this.generateId(),
+ })
+ }
+
+ return this
+ }
+
+ addInlineScript(script, id)
+ {
+ this.inline.push({
+ script,
+ id: id || this.generateId(),
+ element: null,
+ })
+
+ return this
+ }
+
+ generateId()
+ {
+ let id = 'script-'
+ let chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-'
+ let charLength = chars.length
+
+ for (let i = 0; i < 10; i++) {
+ let currentChar = chars.substr(Math.floor(Math.random() * charLength), 1)
+ id = `${id}${currentChar}`
+ }
+
+ return id
+ }
+
+ render(content)
+ {
+ if (content) {
+ this.content = content;
+ }
+ return new Promise((resolve, reject) => {
+ try {
+ const dom = new JSDOM(
+ this._renderContent(),
+ {
+ url: this.url,
+ referrer: this.referer,
+ contentType: this.contentType,
+ includeNodeLocations: true,
+ runScripts: 'dangerously',
+ resources: 'usable',
+ pretendToBeVisual: true,
+ beforeParse: this.beforeParse,
+ }
+ )
+
+ dom.window.resolver = () => {
+ resolve(dom)
+ }
+ } catch (e) {
+ reject(e)
+ }
+ })
+ }
+
+ _renderContent()
+ {
+ // Create content list
+ const content = [
+ this.head,
+ this.bodyStart,
+ this.content,
+ ]
+
+ // Embed scripts
+ this.scripts.forEach((script) => {
+ content.push(``)
+ })
+ this.inline.forEach((script) => {
+ content.push(``)
+ })
+
+ // Add resolver
+ content.push(``)
+
+ // Add final content
+ content.push(this.bodyEnd)
+ content.push(this.foot)
+
+ return content.join('\n')
+ }
+}
diff --git a/tests/js/helpers/fakeDom.js b/tests/js/helpers/fakeDom.js
deleted file mode 100644
index 13757a91c..000000000
--- a/tests/js/helpers/fakeDom.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import { JSDOM } from 'jsdom'
-
-const defaults = {
- url: 'https://winter.example.org/',
- referer: null,
- contentType: 'text/html',
- head: 'Fake document',
- bodyStart: '',
- bodyEnd: '',
- foot: '',
- beforeParse: null
-}
-
-const fakeDom = (content, options) => {
- const settings = Object.assign({}, defaults, options)
-
- const dom = new JSDOM(
- settings.head +
- settings.bodyStart +
- (content + '') +
- settings.bodyEnd +
- settings.foot,
- {
- url: settings.url,
- referrer: settings.referer || undefined,
- contentType: settings.contenType,
- includeNodeLocations: true,
- runScripts: 'dangerously',
- resources: 'usable',
- pretendToBeVisual: true,
- beforeParse: (typeof settings.beforeParse === 'function')
- ? settings.beforeParse
- : undefined
- }
- )
-
- return dom
-}
-
-export default fakeDom
diff --git a/tests/js/jest.config.js b/tests/js/jest.config.js
new file mode 100644
index 000000000..1363597d6
--- /dev/null
+++ b/tests/js/jest.config.js
@@ -0,0 +1,194 @@
+/*
+ * For a detailed explanation regarding each configuration property, visit:
+ * https://jestjs.io/docs/configuration
+ */
+
+module.exports = {
+ // All imported modules in your tests should be mocked automatically
+ // automock: false,
+
+ // Stop running tests after `n` failures
+ // bail: 0,
+
+ // The directory where Jest should store its cached dependency information
+ // cacheDirectory: "/private/var/folders/81/m6w95r0j7ms_10c47hdbz4gw0000gn/T/jest_dx",
+
+ // Automatically clear mock calls, instances and results before every test
+ clearMocks: true,
+
+ // Indicates whether the coverage information should be collected while executing the test
+ // collectCoverage: false,
+
+ // An array of glob patterns indicating a set of files for which coverage information should be collected
+ // collectCoverageFrom: undefined,
+
+ // The directory where Jest should output its coverage files
+ // coverageDirectory: undefined,
+
+ // An array of regexp pattern strings used to skip coverage collection
+ // coveragePathIgnorePatterns: [
+ // "/node_modules/"
+ // ],
+
+ // Indicates which provider should be used to instrument code for coverage
+ // coverageProvider: "babel",
+
+ // A list of reporter names that Jest uses when writing coverage reports
+ // coverageReporters: [
+ // "json",
+ // "text",
+ // "lcov",
+ // "clover"
+ // ],
+
+ // An object that configures minimum threshold enforcement for coverage results
+ // coverageThreshold: undefined,
+
+ // A path to a custom dependency extractor
+ // dependencyExtractor: undefined,
+
+ // Make calling deprecated APIs throw helpful error messages
+ // errorOnDeprecated: false,
+
+ // Force coverage collection from ignored files using an array of glob patterns
+ // forceCoverageMatch: [],
+
+ // A path to a module which exports an async function that is triggered once before all test suites
+ // globalSetup: undefined,
+
+ // A path to a module which exports an async function that is triggered once after all test suites
+ // globalTeardown: undefined,
+
+ // A set of global variables that need to be available in all test environments
+ // globals: {},
+
+ // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
+ // maxWorkers: "50%",
+
+ // An array of directory names to be searched recursively up from the requiring module's location
+ // moduleDirectories: [
+ // "node_modules"
+ // ],
+
+ // An array of file extensions your modules use
+ // moduleFileExtensions: [
+ // "js",
+ // "jsx",
+ // "ts",
+ // "tsx",
+ // "json",
+ // "node"
+ // ],
+
+ // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
+ // moduleNameMapper: {},
+
+ // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
+ // modulePathIgnorePatterns: [],
+
+ // Activates notifications for test results
+ // notify: false,
+
+ // An enum that specifies notification mode. Requires { notify: true }
+ // notifyMode: "failure-change",
+
+ // A preset that is used as a base for Jest's configuration
+ // preset: undefined,
+
+ // Run tests from one or more projects
+ // projects: undefined,
+
+ // Use this configuration option to add custom reporters to Jest
+ // reporters: undefined,
+
+ // Automatically reset mock state before every test
+ // resetMocks: false,
+
+ // Reset the module registry before running each individual test
+ // resetModules: false,
+
+ // A path to a custom resolver
+ // resolver: undefined,
+
+ // Automatically restore mock state and implementation before every test
+ // restoreMocks: false,
+
+ // The root directory that Jest should scan for tests and modules within
+ // rootDir: undefined,
+
+ // A list of paths to directories that Jest should use to search for files in
+ // roots: [
+ // ""
+ // ],
+
+ // Allows you to use a custom runner instead of Jest's default test runner
+ // runner: "jest-runner",
+
+ // The paths to modules that run some code to configure or set up the testing environment before each test
+ // setupFiles: [],
+
+ // A list of paths to modules that run some code to configure or set up the testing framework before each test
+ // setupFilesAfterEnv: [],
+
+ // The number of seconds after which a test is considered as slow and reported as such in the results.
+ // slowTestThreshold: 5,
+
+ // A list of paths to snapshot serializer modules Jest should use for snapshot testing
+ // snapshotSerializers: [],
+
+ // The test environment that will be used for testing
+ // testEnvironment: "jest-environment-node",
+
+ // Options that will be passed to the testEnvironment
+ // testEnvironmentOptions: {},
+
+ // Adds a location field to test results
+ // testLocationInResults: false,
+
+ // The glob patterns Jest uses to detect test files
+ // testMatch: [
+ // "**/__tests__/**/*.[jt]s?(x)",
+ // "**/?(*.)+(spec|test).[tj]s?(x)"
+ // ],
+
+ // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
+ // testPathIgnorePatterns: [
+ // "/node_modules/"
+ // ],
+
+ // The regexp pattern or array of patterns that Jest uses to detect test files
+ // testRegex: [],
+
+ // This option allows the use of a custom results processor
+ // testResultsProcessor: undefined,
+
+ // This option allows use of a custom test runner
+ // testRunner: "jest-circus/runner",
+
+ // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
+ // testURL: "http://localhost",
+
+ // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
+ // timers: "real",
+
+ // A map from regular expressions to paths to transformers
+ // transform: undefined,
+
+ // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
+ // transformIgnorePatterns: [
+ // "/node_modules/",
+ // "\\.pnp\\.[^\\/]+$"
+ // ],
+
+ // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
+ // unmockedModulePathPatterns: undefined,
+
+ // Indicates whether each individual test should be reported during the run
+ // verbose: undefined,
+
+ // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
+ // watchPathIgnorePatterns: [],
+
+ // Whether to use watchman for file crawling
+ // watchman: true,
+};
diff --git a/tests/js/package.json b/tests/js/package.json
new file mode 100644
index 000000000..650f38698
--- /dev/null
+++ b/tests/js/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "@wintercms/tests",
+ "description": "Test cases for the Winter JavaScript framework",
+ "private": true,
+ "scripts": {
+ "test": "jest"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/wintercms/winter.git"
+ },
+ "contributors": [
+ {
+ "name": "Ben Thomson",
+ "email": "git@alfreido.com"
+ },
+ {
+ "name": "Winter CMS Maintainers",
+ "url": "https://wintercms.com/"
+ }
+ ],
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/wintercms/winter/issues"
+ },
+ "homepage": "https://wintercms.com/",
+ "devDependencies": {
+ "@babel/core": "^7.16.0",
+ "@babel/plugin-transform-runtime": "^7.16.4",
+ "@babel/preset-env": "^7.16.4",
+ "@babel/register": "^7.16.0",
+ "@babel/runtime": "^7.16.3",
+ "babel-plugin-module-resolver": "^4.1.0",
+ "jest": "^27.4.3",
+ "jsdom": "^18.1.1"
+ }
+}
diff --git a/themes/demo/layouts/default.htm b/themes/demo/layouts/default.htm
index 56dbaa99d..0d3e3ff1e 100644
--- a/themes/demo/layouts/default.htm
+++ b/themes/demo/layouts/default.htm
@@ -36,7 +36,7 @@
- {% framework extras %}
+ {% snowboard all %}
{% scripts %}
diff --git a/themes/demo/pages/ajax.htm b/themes/demo/pages/ajax.htm
index 9d82ab843..de7a4e9b1 100644
--- a/themes/demo/pages/ajax.htm
+++ b/themes/demo/pages/ajax.htm
@@ -5,9 +5,14 @@
Calculator