diff --git a/.github/workflows/code-quality-push.yaml b/.github/workflows/code-quality-push.yaml
index f34f7b3b8..8c036b312 100644
--- a/.github/workflows/code-quality-push.yaml
+++ b/.github/workflows/code-quality-push.yaml
@@ -3,7 +3,8 @@ name: Code Quality
on:
push:
branches:
- - master
+ - 1.0
+ - 1.1
- develop
jobs:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 9f0ab376b..348758a81 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -3,7 +3,8 @@ name: Tests
on:
push:
branches:
- - master
+ - 1.0
+ - 1.1
- develop
pull_request:
@@ -62,7 +63,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.phpVersion }}
- tools: composer
+ tools: composer:v1
extensions: ${{ env.extensions }}
- name: Setup dependency cache
diff --git a/README.md b/README.md
index 88168beb3..2e9d0c5ae 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
-
+
[October](https://octobercms.com) is a Content Management System (CMS) and web platform whose sole purpose is to make your development workflow simple again. It was born out of frustration with existing systems. We feel building websites has become a convoluted and confusing process that leaves developers unsatisfied. We want to turn you around to the simpler side and get back to basics.
October's mission is to show the world that web development is not rocket science.
-
+
[](https://packagist.org/packages/october/october)
## Installing October
@@ -73,7 +73,7 @@ Before sending or reviewing Pull Requests, be sure to review the [Contributing G
Please follow the following guides and code standards:
-* [PSR 4 Coding Standards](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md)
+* [PSR 4 Autoloader](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md)
* [PSR 2 Coding Style Guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
* [PSR 1 Coding Standards](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md)
diff --git a/composer.json b/composer.json
index 37732c84c..01f9bfefc 100644
--- a/composer.json
+++ b/composer.json
@@ -40,7 +40,8 @@
"wikimedia/composer-merge-plugin": "1.4.1"
},
"require-dev": {
- "phpunit/phpunit": "^8.0|^9.0",
+ "phpunit/phpunit": "^8.4|^9.3.3",
+ "mockery/mockery": "~1.3.3|^1.4.2",
"fzaninotto/faker": "~1.9",
"squizlabs/php_codesniffer": "3.*",
"php-parallel-lint/php-parallel-lint": "^1.0",
diff --git a/config/auth.php b/config/auth.php
index fe87b873e..8dc13b61c 100644
--- a/config/auth.php
+++ b/config/auth.php
@@ -19,7 +19,7 @@ return [
| Failed Authentication Attempt Limit
|--------------------------------------------------------------------------
|
- | Number of failed attemps allowed while trying to authenticate a user.
+ | Number of failed attempts allowed while trying to authenticate a user.
|
*/
'attemptLimit' => 5,
diff --git a/config/session.php b/config/session.php
index ab762f221..8e2f8fa57 100644
--- a/config/session.php
+++ b/config/session.php
@@ -169,17 +169,31 @@ return [
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
- | take place, and can be used to mitigate CSRF attacks. By default, we
- | do not enable this as other CSRF protection services are in place.
+ | take place and can be used to mitigate CSRF attacks.
|
- | In the strict mode, the cookie is not sent with any cross-site usage
- | even if the user follows a link to another website. Lax cookies are
- | only sent with a top-level get request.
+ | Cookies that match the domain of the current site, i.e. what's displayed
+ | in the browser's address bar, are referred to as first-party cookies.
+ | Similarly, cookies from domains other than the current site are referred
+ | to as third-party cookies.
|
- | Supported: "lax", "strict"
+ | Cookies without a SameSite attribute will be treated as `SameSite=Lax`,
+ | meaning the default behaviour will be to restrict cookies to first party
+ | contexts only.
+ |
+ | Cookies for cross-site usage must specify `same_site` as 'None' and `secure`
+ | as `true` to work correctly.
+ |
+ | Lax - Cookies are allowed to be sent with top-level navigations and will
+ | be sent along with GET request initiated by third party website.
+ | This is the default value in modern browsers.
+ |
+ | Strict - Cookies will only be sent in a first-party context and not be
+ | sent along with requests initiated by third party websites.
+ |
+ | Supported: "Lax", "Strict" and "None"
|
*/
- 'same_site' => null,
+ 'same_site' => 'Lax',
];
diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php
index 4c3825dd0..e21bda158 100644
--- a/modules/backend/ServiceProvider.php
+++ b/modules/backend/ServiceProvider.php
@@ -141,31 +141,40 @@ class ServiceProvider extends ModuleServiceProvider
$manager->registerPermissions('October.Backend', [
'backend.access_dashboard' => [
'label' => 'system::lang.permissions.view_the_dashboard',
- 'tab' => 'system::lang.permissions.name'
+ 'tab' => 'system::lang.permissions.name',
],
'backend.manage_default_dashboard' => [
'label' => 'system::lang.permissions.manage_default_dashboard',
'tab' => 'system::lang.permissions.name',
+ 'roles' => UserRole::CODE_DEVELOPER,
],
'backend.manage_users' => [
'label' => 'system::lang.permissions.manage_other_administrators',
- 'tab' => 'system::lang.permissions.name'
+ 'tab' => 'system::lang.permissions.name',
+ 'roles' => UserRole::CODE_DEVELOPER,
],
'backend.impersonate_users' => [
'label' => 'system::lang.permissions.impersonate_users',
'tab' => 'system::lang.permissions.name',
+ 'roles' => UserRole::CODE_DEVELOPER,
],
'backend.manage_preferences' => [
'label' => 'system::lang.permissions.manage_preferences',
- 'tab' => 'system::lang.permissions.name'
+ 'tab' => 'system::lang.permissions.name',
],
'backend.manage_editor' => [
'label' => 'system::lang.permissions.manage_editor',
- 'tab' => 'system::lang.permissions.name'
+ 'tab' => 'system::lang.permissions.name',
+ 'roles' => UserRole::CODE_DEVELOPER,
+ ],
+ 'backend.manage_own_editor' => [
+ 'label' => 'system::lang.permissions.manage_own_editor',
+ 'tab' => 'system::lang.permissions.name',
],
'backend.manage_branding' => [
'label' => 'system::lang.permissions.manage_branding',
- 'tab' => 'system::lang.permissions.name'
+ 'tab' => 'system::lang.permissions.name',
+ 'roles' => UserRole::CODE_DEVELOPER,
],
'media.manage_media' => [
'label' => 'backend::lang.permissions.manage_media',
diff --git a/modules/backend/assets/css/october.css b/modules/backend/assets/css/october.css
index e11e70041..5703da3d0 100644
--- a/modules/backend/assets/css/october.css
+++ b/modules/backend/assets/css/october.css
@@ -679,9 +679,10 @@ nav#layout-mainmenu .toolbar-item:before {left:-12px}
nav#layout-mainmenu .toolbar-item:after {right:-12px}
nav#layout-mainmenu .toolbar-item.scroll-active-before:before {color:#fff}
nav#layout-mainmenu .toolbar-item.scroll-active-after:after {color:#fff}
-nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-preview {margin:0 0 0 21px}
-nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-preview i {font-size:20px}
-nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-preview a {position:relative;padding:0 10px;top:-1px}
+nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-quick-action {margin:0}
+nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-quick-action:first-child {margin-left:21px}
+nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-quick-action i {font-size:20px}
+nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-quick-action a {position:relative;padding:0 10px;top:-1px}
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-account {margin-right:0}
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-account >a {padding:0 15px 0 10px;font-size:13px;position:relative}
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-account.highlight >a {z-index:600}
@@ -706,8 +707,8 @@ nav#layout-mainmenu ul li .mainmenu-accountmenu li:first-child a:active:after {c
nav#layout-mainmenu ul li .mainmenu-accountmenu li.divider {height:1px;width:100%;background-color:#e0e0e0}
nav#layout-mainmenu.navbar-mode-inline,
nav#layout-mainmenu.navbar-mode-inline_no_icons {height:60px}
-nav#layout-mainmenu.navbar-mode-inline ul.mainmenu-toolbar li.mainmenu-preview a,
-nav#layout-mainmenu.navbar-mode-inline_no_icons ul.mainmenu-toolbar li.mainmenu-preview a {height:60px;line-height:60px}
+nav#layout-mainmenu.navbar-mode-inline ul.mainmenu-toolbar li.mainmenu-quick-action a,
+nav#layout-mainmenu.navbar-mode-inline_no_icons ul.mainmenu-toolbar li.mainmenu-quick-action a {height:60px;line-height:60px}
nav#layout-mainmenu.navbar-mode-inline ul.mainmenu-toolbar li.mainmenu-account >a,
nav#layout-mainmenu.navbar-mode-inline_no_icons ul.mainmenu-toolbar li.mainmenu-account >a {height:60px;line-height:60px}
nav#layout-mainmenu.navbar-mode-inline ul li .mainmenu-accountmenu,
@@ -730,7 +731,7 @@ nav#layout-mainmenu.navbar-mode-inline ul.mainmenu-nav li:last-child,
nav#layout-mainmenu.navbar-mode-inline_no_icons ul.mainmenu-nav li:last-child {margin-right:0}
nav#layout-mainmenu.navbar-mode-inline_no_icons .nav-icon {display:none !important}
nav#layout-mainmenu.navbar-mode-tile {height:78px}
-nav#layout-mainmenu.navbar-mode-tile ul.mainmenu-toolbar li.mainmenu-preview a {height:78px;line-height:78px}
+nav#layout-mainmenu.navbar-mode-tile ul.mainmenu-toolbar li.mainmenu-quick-action a {height:78px;line-height:78px}
nav#layout-mainmenu.navbar-mode-tile ul.mainmenu-toolbar li.mainmenu-account >a {height:78px;line-height:78px}
nav#layout-mainmenu.navbar-mode-tile ul li .mainmenu-accountmenu {top:88px}
nav#layout-mainmenu.navbar-mode-tile ul.mainmenu-nav li a {position:relative;width:65px;height:65px}
@@ -749,14 +750,14 @@ nav#layout-mainmenu .menu-toggle .menu-toggle-title {margin-left:10px}
nav#layout-mainmenu .menu-toggle:hover .menu-toggle-icon {opacity:1}
body.mainmenu-open nav#layout-mainmenu .menu-toggle-icon {opacity:1}
nav#layout-mainmenu.navbar-mode-collapse {padding-left:0;height:45px}
-nav#layout-mainmenu.navbar-mode-collapse ul.mainmenu-toolbar li.mainmenu-preview a {height:45px;line-height:45px}
+nav#layout-mainmenu.navbar-mode-collapse ul.mainmenu-toolbar li.mainmenu-quick-action a {height:45px;line-height:45px}
nav#layout-mainmenu.navbar-mode-collapse ul.mainmenu-toolbar li.mainmenu-account >a {height:45px;line-height:45px}
nav#layout-mainmenu.navbar-mode-collapse ul li .mainmenu-accountmenu {top:55px}
nav#layout-mainmenu.navbar-mode-collapse ul.mainmenu-toolbar li.mainmenu-account >a {padding-right:0}
nav#layout-mainmenu.navbar-mode-collapse ul li .mainmenu-accountmenu:after {right:13px}
nav#layout-mainmenu.navbar-mode-collapse ul.nav {display:none}
nav#layout-mainmenu.navbar-mode-collapse .menu-toggle {display:inline-block;color:#fff !important}
-@media (max-width:769px) {nav#layout-mainmenu.navbar {padding-left:0;height:45px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-preview a {height:45px;line-height:45px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-account >a {height:45px;line-height:45px }nav#layout-mainmenu.navbar ul li .mainmenu-accountmenu {top:55px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-account >a {padding-right:0 }nav#layout-mainmenu.navbar ul li .mainmenu-accountmenu:after {right:13px }nav#layout-mainmenu.navbar ul.nav {display:none }nav#layout-mainmenu.navbar .menu-toggle {display:inline-block;color:#fff !important }}
+@media (max-width:769px) {nav#layout-mainmenu.navbar {padding-left:0;height:45px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-quick-action a {height:45px;line-height:45px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-account >a {height:45px;line-height:45px }nav#layout-mainmenu.navbar ul li .mainmenu-accountmenu {top:55px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-account >a {padding-right:0 }nav#layout-mainmenu.navbar ul li .mainmenu-accountmenu:after {right:13px }nav#layout-mainmenu.navbar ul.nav {display:none }nav#layout-mainmenu.navbar .menu-toggle {display:inline-block;color:#fff !important }}
.mainmenu-collapsed {position:absolute;height:100%;top:0;left:0;margin:0;background:#000}
.mainmenu-collapsed >div {display:block;height:100%}
.mainmenu-collapsed >div ul.mainmenu-nav li a {position:relative;width:65px;height:65px}
diff --git a/modules/backend/assets/less/layout/mainmenu.less b/modules/backend/assets/less/layout/mainmenu.less
index 5d3492c26..f4b8c37cb 100644
--- a/modules/backend/assets/less/layout/mainmenu.less
+++ b/modules/backend/assets/less/layout/mainmenu.less
@@ -47,7 +47,7 @@ body.mainmenu-open {
height: @height;
ul.mainmenu-toolbar {
- li.mainmenu-preview {
+ li.mainmenu-quick-action {
a {
height: @height;
line-height: @height;
@@ -191,8 +191,12 @@ nav#layout-mainmenu {
//
ul.mainmenu-toolbar {
- li.mainmenu-preview {
- margin: 0 0 0 21px;
+ li.mainmenu-quick-action {
+ margin: 0;
+
+ &:first-child {
+ margin-left: 21px;
+ }
i {
font-size: 20px;
diff --git a/modules/backend/behaviors/ImportExportController.php b/modules/backend/behaviors/ImportExportController.php
index b0278ccdc..0af1497ac 100644
--- a/modules/backend/behaviors/ImportExportController.php
+++ b/modules/backend/behaviors/ImportExportController.php
@@ -803,9 +803,9 @@ class ImportExportController extends ControllerBehavior
if (
$options['encoding'] !== null &&
- $reader->isActiveStreamFilter()
+ $reader->supportsStreamFilter()
) {
- $reader->appendStreamFilter(sprintf(
+ $reader->addStreamFilter(sprintf(
'%s%s:%s',
TranscodeFilter::FILTER_NAME,
strtolower($options['encoding']),
diff --git a/modules/backend/behaviors/ListController.php b/modules/backend/behaviors/ListController.php
index d885f1625..4bc6c4d8b 100644
--- a/modules/backend/behaviors/ListController.php
+++ b/modules/backend/behaviors/ListController.php
@@ -297,16 +297,6 @@ class ListController extends ControllerBehavior
return call_user_func_array([$this->controller, 'onDelete'], func_get_args());
}
- /*
- * Validate checked identifiers
- */
- $checkedIds = post('checked');
-
- if (!$checkedIds || !is_array($checkedIds) || !count($checkedIds)) {
- Flash::error(Lang::get('backend::lang.list.delete_selected_empty'));
- return $this->controller->listRefresh();
- }
-
/*
* Establish the list definition
*/
@@ -318,6 +308,20 @@ class ListController extends ControllerBehavior
$listConfig = $this->controller->listGetConfig($definition);
+ /*
+ * Validate checked identifiers
+ */
+ $checkedIds = post('checked');
+
+ if (!$checkedIds || !is_array($checkedIds) || !count($checkedIds)) {
+ Flash::error(Lang::get(
+ (!empty($listConfig->noRecordsDeletedMessage))
+ ? $listConfig->noRecordsDeletedMessage
+ : 'backend::lang.list.delete_selected_empty'
+ ));
+ return $this->controller->listRefresh();
+ }
+
/*
* Create the model
*/
@@ -344,10 +348,18 @@ class ListController extends ControllerBehavior
$record->delete();
}
- Flash::success(Lang::get('backend::lang.list.delete_selected_success'));
+ Flash::success(Lang::get(
+ (!empty($listConfig->deleteMessage))
+ ? $listConfig->deleteMessage
+ : 'backend::lang.list.delete_selected_success'
+ ));
}
else {
- Flash::error(Lang::get('backend::lang.list.delete_selected_empty'));
+ Flash::error(Lang::get(
+ (!empty($listConfig->noRecordsDeletedMessage))
+ ? $listConfig->noRecordsDeletedMessage
+ : 'backend::lang.list.delete_selected_empty'
+ ));
}
return $this->controller->listRefresh($definition);
diff --git a/modules/backend/behaviors/RelationController.php b/modules/backend/behaviors/RelationController.php
index 510242597..88ef54ff4 100644
--- a/modules/backend/behaviors/RelationController.php
+++ b/modules/backend/behaviors/RelationController.php
@@ -669,8 +669,9 @@ class RelationController extends ControllerBehavior
$config->defaultSort = $this->getConfig('view[defaultSort]');
$config->recordsPerPage = $this->getConfig('view[recordsPerPage]');
$config->showCheckboxes = $this->getConfig('view[showCheckboxes]', !$this->readOnly);
- $config->recordUrl = $this->getConfig('view[recordUrl]', null);
- $config->customViewPath = $this->getConfig('view[customViewPath]', null);
+ $config->recordUrl = $this->getConfig('view[recordUrl]');
+ $config->customViewPath = $this->getConfig('view[customViewPath]');
+ $config->noRecordsMessage = $this->getConfig('view[noRecordsMessage]');
$defaultOnClick = sprintf(
"$.oc.relationBehavior.clickViewListRecord(':%s', '%s', '%s')",
@@ -818,6 +819,7 @@ class RelationController extends ControllerBehavior
$config->showSorting = $this->getConfig('manage[showSorting]', !$isPivot);
$config->defaultSort = $this->getConfig('manage[defaultSort]');
$config->recordsPerPage = $this->getConfig('manage[recordsPerPage]');
+ $config->noRecordsMessage = $this->getConfig('manage[noRecordsMessage]');
if ($this->viewMode == 'single') {
$config->showCheckboxes = false;
diff --git a/modules/backend/behaviors/ReorderController.php b/modules/backend/behaviors/ReorderController.php
index 1795afca0..4b9681972 100644
--- a/modules/backend/behaviors/ReorderController.php
+++ b/modules/backend/behaviors/ReorderController.php
@@ -214,7 +214,10 @@ class ReorderController extends ControllerBehavior
$model = $this->controller->reorderGetModel();
$modelTraits = class_uses($model);
- if (isset($modelTraits[\October\Rain\Database\Traits\Sortable::class])) {
+ if (
+ isset($modelTraits[\October\Rain\Database\Traits\Sortable::class]) ||
+ $model->isClassExtendedWith(\October\Rain\Database\Behaviors\Sortable::class)
+ ) {
$this->sortMode = 'simple';
}
elseif (isset($modelTraits[\October\Rain\Database\Traits\NestedTree::class])) {
@@ -222,7 +225,7 @@ class ReorderController extends ControllerBehavior
$this->showTree = true;
}
else {
- throw new ApplicationException('The model must implement the NestedTree or Sortable traits.');
+ throw new ApplicationException('The model must implement the Sortable trait/behavior or the NestedTree trait.');
}
return $model;
diff --git a/modules/backend/classes/NavigationManager.php b/modules/backend/classes/NavigationManager.php
index ee57c1666..f88baf6c9 100644
--- a/modules/backend/classes/NavigationManager.php
+++ b/modules/backend/classes/NavigationManager.php
@@ -28,6 +28,11 @@ class NavigationManager
*/
protected $items;
+ /**
+ * @var QuickActionItem[] List of registered quick actions.
+ */
+ protected $quickActions;
+
protected $contextSidenavPartials = [];
protected $contextOwner;
@@ -54,6 +59,9 @@ class NavigationManager
*/
protected function loadItems()
{
+ $this->items = [];
+ $this->quickActions = [];
+
/*
* Load module items
*/
@@ -68,11 +76,18 @@ class NavigationManager
foreach ($plugins as $id => $plugin) {
$items = $plugin->registerNavigation();
- if (!is_array($items)) {
+ $quickActions = $plugin->registerQuickActions();
+
+ if (!is_array($items) && !is_array($quickActions)) {
continue;
}
- $this->registerMenuItems($id, $items);
+ if (is_array($items)) {
+ $this->registerMenuItems($id, $items);
+ }
+ if (is_array($quickActions)) {
+ $this->registerQuickActions($id, $quickActions);
+ }
}
/**
@@ -91,17 +106,21 @@ class NavigationManager
Event::fire('backend.menu.extendItems', [$this]);
/*
- * Sort menu items
+ * Sort menu items and quick actions
*/
uasort($this->items, static function ($a, $b) {
return $a->order - $b->order;
});
+ uasort($this->quickActions, static function ($a, $b) {
+ return $a->order - $b->order;
+ });
/*
- * Filter items user lacks permission for
+ * Filter items and quick actions that the user lacks permission for
*/
$user = BackendAuth::getUser();
$this->items = $this->filterItemPermissions($user, $this->items);
+ $this->quickActions = $this->filterItemPermissions($user, $this->quickActions);
foreach ($this->items as $item) {
if (!$item->sideMenu || !count($item->sideMenu)) {
@@ -183,10 +202,6 @@ class NavigationManager
*/
public function registerMenuItems($owner, array $definitions)
{
- if (!$this->items) {
- $this->items = [];
- }
-
$validator = Validator::make($definitions, [
'*.label' => 'required',
'*.icon' => 'required_without:*.iconSvg',
@@ -320,6 +335,21 @@ class NavigationManager
return true;
}
+ /**
+ * Remove multiple side menu items
+ *
+ * @param string $owner
+ * @param string $code
+ * @param array $sideCodes
+ * @return void
+ */
+ public function removeSideMenuItems($owner, $code, $sideCodes)
+ {
+ foreach ($sideCodes as $sideCode) {
+ $this->removeSideMenuItem($owner, $code, $sideCode);
+ }
+ }
+
/**
* Removes a single main menu item
* @param string $owner
@@ -346,10 +376,14 @@ class NavigationManager
*/
public function listMainMenuItems()
{
- if ($this->items === null) {
+ if ($this->items === null && $this->quickActions === null) {
$this->loadItems();
}
+ if ($this->items === null) {
+ return [];
+ }
+
foreach ($this->items as $item) {
if ($item->badge) {
$item->counter = (string) $item->badge;
@@ -429,6 +463,137 @@ class NavigationManager
return $items;
}
+ /**
+ * Registers quick actions in the main navigation.
+ *
+ * Quick actions are single purpose links displayed to the left of the user menu in the
+ * backend main navigation.
+ *
+ * The argument is an array of the quick action items. The array keys represent the
+ * quick action item codes, specific for the plugin/module. Each element in the
+ * array should be an associative array with the following keys:
+ * - label - specifies the action label localization string key, used as a tooltip, required.
+ * - icon - an icon name from the Font Awesome icon collection, required if iconSvg is unspecified.
+ * - iconSvg - a custom SVG icon to use for the icon, required if icon is unspecified.
+ * - url - the back-end relative URL the quick action item should point to, required.
+ * - permissions - an array of permissions the back-end user should have, optional.
+ * The item will be displayed if the user has any of the specified permissions.
+ * - order - a position of the item in the menu, optional.
+ *
+ * @param string $owner Specifies the quick action items owner plugin or module in the format Author.Plugin.
+ * @param array $definitions An array of the quick action item definitions.
+ * @return void
+ * @throws SystemException If the validation of the quick action configuration fails
+ */
+ public function registerQuickActions($owner, array $definitions)
+ {
+ $validator = Validator::make($definitions, [
+ '*.label' => 'required',
+ '*.icon' => 'required_without:*.iconSvg',
+ '*.url' => 'required'
+ ]);
+
+ if ($validator->fails()) {
+ $errorMessage = 'Invalid quick action item detected in ' . $owner . '. Contact the plugin author to fix (' . $validator->errors()->first() . ')';
+ if (Config::get('app.debug', false)) {
+ throw new SystemException($errorMessage);
+ }
+
+ Log::error($errorMessage);
+ }
+
+ $this->addQuickActionItems($owner, $definitions);
+ }
+
+ /**
+ * Dynamically add an array of quick action items
+ *
+ * @param string $owner
+ * @param array $definitions
+ * @return void
+ */
+ public function addQuickActionItems($owner, array $definitions)
+ {
+ foreach ($definitions as $code => $definition) {
+ $this->addQuickActionItem($owner, $code, $definition);
+ }
+ }
+
+ /**
+ * Dynamically add a single quick action item
+ *
+ * @param string $owner
+ * @param string $code
+ * @param array $definition
+ * @return void
+ */
+ public function addQuickActionItem($owner, $code, array $definition)
+ {
+ $itemKey = $this->makeItemKey($owner, $code);
+
+ if (isset($this->quickActions[$itemKey])) {
+ $definition = array_merge((array) $this->quickActions[$itemKey], $definition);
+ }
+
+ $item = array_merge($definition, [
+ 'code' => $code,
+ 'owner' => $owner
+ ]);
+
+ $this->quickActions[$itemKey] = QuickActionItem::createFromArray($item);
+ }
+
+ /**
+ * Gets the instance of a specified quick action item.
+ *
+ * @param string $owner
+ * @param string $code
+ * @return QuickActionItem
+ * @throws SystemException
+ */
+ public function getQuickActionItem(string $owner, string $code)
+ {
+ $itemKey = $this->makeItemKey($owner, $code);
+
+ if (!array_key_exists($itemKey, $this->quickActions)) {
+ throw new SystemException('No quick action item found with key ' . $itemKey);
+ }
+
+ return $this->quickActions[$itemKey];
+ }
+
+ /**
+ * Removes a single quick action item
+ *
+ * @param $owner
+ * @param $code
+ * @return void
+ */
+ public function removeQuickActionItem($owner, $code)
+ {
+ $itemKey = $this->makeItemKey($owner, $code);
+ unset($this->quickActions[$itemKey]);
+ }
+
+ /**
+ * Returns a list of quick action items.
+ *
+ * @return array
+ * @throws SystemException
+ */
+ public function listQuickActionItems()
+ {
+ if ($this->items === null && $this->quickActions === null) {
+ $this->loadItems();
+ }
+
+ if ($this->quickActions === null) {
+ return [];
+ }
+
+ return $this->quickActions;
+ }
+
/**
* Sets the navigation context.
* The function sets the navigation owner, main menu item code and the side menu item code.
diff --git a/modules/backend/classes/QuickActionItem.php b/modules/backend/classes/QuickActionItem.php
new file mode 100644
index 000000000..bf30cadc1
--- /dev/null
+++ b/modules/backend/classes/QuickActionItem.php
@@ -0,0 +1,105 @@
+attributes[$attribute] = $value;
+ }
+
+ public function removeAttribute($attribute)
+ {
+ unset($this->attributes[$attribute]);
+ }
+
+ /**
+ * @param string $permission
+ * @param array $definition
+ */
+ public function addPermission(string $permission, array $definition)
+ {
+ $this->permissions[$permission] = $definition;
+ }
+
+ /**
+ * @param string $permission
+ * @return void
+ */
+ public function removePermission(string $permission)
+ {
+ unset($this->permissions[$permission]);
+ }
+
+ /**
+ * @param array $data
+ * @return static
+ */
+ public static function createFromArray(array $data)
+ {
+ $instance = new static();
+ $instance->code = $data['code'];
+ $instance->owner = $data['owner'];
+ $instance->label = $data['label'];
+ $instance->url = $data['url'];
+ $instance->icon = $data['icon'] ?? null;
+ $instance->iconSvg = $data['iconSvg'] ?? null;
+ $instance->attributes = $data['attributes'] ?? $instance->attributes;
+ $instance->permissions = $data['permissions'] ?? $instance->permissions;
+ $instance->order = $data['order'] ?? $instance->order;
+ return $instance;
+ }
+}
diff --git a/modules/backend/controllers/Preferences.php b/modules/backend/controllers/Preferences.php
index 9a79f88b5..1a9e25828 100644
--- a/modules/backend/controllers/Preferences.php
+++ b/modules/backend/controllers/Preferences.php
@@ -57,7 +57,7 @@ class Preferences extends Controller
*/
public function formExtendFields($form)
{
- if (!$this->user->hasAccess('backend.manage_editor')) {
+ if (!$this->user->hasAccess('backend.manage_own_editor')) {
$form->removeTab('backend::lang.backend_preferences.code_editor');
}
}
diff --git a/modules/backend/controllers/users/config_filter.yaml b/modules/backend/controllers/users/config_filter.yaml
index 08ebb3f56..d9660cc48 100644
--- a/modules/backend/controllers/users/config_filter.yaml
+++ b/modules/backend/controllers/users/config_filter.yaml
@@ -8,8 +8,8 @@ scopes:
label: backend::lang.user.superuser
type: switch
conditions:
- - is_superuser = false
- - is_superuser = true
+ - is_superuser = 0
+ - is_superuser = 1
login_date:
label: backend::lang.user.last_login
diff --git a/modules/backend/database/seeds/DatabaseSeeder.php b/modules/backend/database/seeds/DatabaseSeeder.php
index 78c561441..50701273e 100644
--- a/modules/backend/database/seeds/DatabaseSeeder.php
+++ b/modules/backend/database/seeds/DatabaseSeeder.php
@@ -1,5 +1,6 @@
call('Backend\Database\Seeds\SeedSetupAdmin');
+ $adminPassword = Str::random(22);
+
+ Eloquent::unguarded(function () use ($adminPassword) {
+ // Generate a random password for the seeded admin account
+ $adminSeeder = new \Backend\Database\Seeds\SeedSetupAdmin;
+ $adminSeeder->setDefaults([
+ 'password' => $adminPassword
+ ]);
+ $this->call($adminSeeder);
});
+
+ return 'The following password has been automatically generated for the "admin" account: '
+ . "${adminPassword}>";
}
}
diff --git a/modules/backend/facades/Backend.php b/modules/backend/facades/Backend.php
index 4ca59f641..1a1ae24bd 100644
--- a/modules/backend/facades/Backend.php
+++ b/modules/backend/facades/Backend.php
@@ -2,12 +2,24 @@
use October\Rain\Support\Facade;
+/**
+ * @method static string uri()
+ * @method static string url(string $path = null, array $parameters = [], bool $secure = null)
+ * @method static string baseUrl(string $path = null)
+ * @method static string skinAsset(string $path = null)
+ * @method static \Illuminate\Http\RedirectResponse redirect(string $path, int $status = 302, array $headers = [], bool $secure = null)
+ * @method static \Illuminate\Http\RedirectResponse redirectGuest(string $path, int $status = 302, array $headers = [], bool $secure = null)
+ * @method static \Illuminate\Http\RedirectResponse redirectIntended(string $path, int $status = 302, array $headers = [], bool $secure = null)
+ * @method static string date($dateTime, array $options = [])
+ * @method static string dateTime($dateTime, array $options = [])
+ *
+ * @see \Backend\Helpers\Backend
+ */
class Backend extends Facade
{
/**
* Get the registered name of the component.
*
- * @see \Backend\Helpers\Backend
* @return string
*/
protected static function getFacadeAccessor()
diff --git a/modules/backend/facades/BackendAuth.php b/modules/backend/facades/BackendAuth.php
index 99dde86f3..6c3cfc078 100644
--- a/modules/backend/facades/BackendAuth.php
+++ b/modules/backend/facades/BackendAuth.php
@@ -2,14 +2,22 @@
use October\Rain\Support\Facade;
+/**
+ * @method static void registerCallback(callable $callback)
+ * @method static void registerPermissions(string $owner, array $definitions)
+ * @method static void removePermission(string $owner, string $code)
+ * @method static array listPermissions()
+ * @method static array listTabbedPermissions()
+ * @method static array listPermissionsForRole(string $role, bool $includeOrphans = true)
+ * @method static boolean hasPermissionsForRole(string $role)
+ *
+ * @see \Backend\Classes\AuthManager
+ */
class BackendAuth extends Facade
{
/**
* Get the registered name of the component.
*
- * Resolves to:
- * - Backend\Classes\AuthManager
- *
* @return string
*/
protected static function getFacadeAccessor()
diff --git a/modules/backend/facades/BackendMenu.php b/modules/backend/facades/BackendMenu.php
index eb93dff9a..9b305c188 100644
--- a/modules/backend/facades/BackendMenu.php
+++ b/modules/backend/facades/BackendMenu.php
@@ -2,14 +2,36 @@
use October\Rain\Support\Facade;
+/**
+ * @method static void registerCallback(callable $callback)
+ * @method static void registerMenuItems(string $owner, array $definitions)
+ * @method static void addMainMenuItems(string $owner, array $definitions)
+ * @method static void addMainMenuItem(string $owner, $code, array $definition)
+ * @method static \Backend\Classes\MainMenuItem getMainMenuItem(string $owner, string $code)
+ * @method static void removeMainMenuItem(string $owner, string $code)
+ * @method static void addSideMenuItems(string $owner, string $code, array $definitions)
+ * @method static bool addSideMenuItem(string $owner, string $code, string $sideCode, array $definition)
+ * @method static bool removeSideMenuItem(string $owner, string $code, string $sideCode)
+ * @method static \Backend\Classes\MainMenuItem[] listMainMenuItems()
+ * @method static \Backend\Classes\SideMenuItem[] listSideMenuItems(string|null $owner = null, string|null $code = null)
+ * @method static void setContext(string $owner, string $mainMenuItemCode, string|null $sideMenuItemCode = null)
+ * @method static void setContextOwner(string $owner)
+ * @method static void setContextMainMenu(string $mainMenuItemCode)
+ * @method static object getContext()
+ * @method static void setContextSideMenu(string $sideMenuItemCode)
+ * @method static bool isMainMenuItemActive(\Backend\Classes\MainMenuItem $item)
+ * @method static \Backend\Classes\MainMenuItem|null getActiveMainMenuItem()
+ * @method static bool isSideMenuItemActive(\Backend\Classes\SideMenuItem $item)
+ * @method static void registerContextSidenavPartial(string $owner, string $mainMenuItemCode, string $partial)
+ * @method static mixed getContextSidenavPartial(string $owner, string $mainMenuItemCode)
+ *
+ * @see \Backend\Classes\NavigationManager
+ */
class BackendMenu extends Facade
{
/**
* Get the registered name of the component.
*
- * Resolves to:
- * - Backend\Classes\NavigationManager
- *
* @return string
*/
protected static function getFacadeAccessor()
diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php
index 95ac67c90..159f20c3e 100644
--- a/modules/backend/formwidgets/Repeater.php
+++ b/modules/backend/formwidgets/Repeater.php
@@ -248,16 +248,6 @@ class Repeater extends FormWidgetBase
}
}
- if (!$this->childAddItemCalled && $currentValue === null) {
- $this->formWidgets = [];
- return;
- }
-
- if ($this->childAddItemCalled && !isset($currentValue[$this->childIndexCalled])) {
- // If no value is available but a child repeater has added an item, add a "stub" repeater item
- $this->makeItemFormWidget($this->childIndexCalled);
- }
-
// Ensure that the minimum number of items are preinitialized
// ONLY DONE WHEN NOT IN GROUP MODE
if (!$this->useGroups && $this->minItems > 0) {
@@ -273,6 +263,16 @@ class Repeater extends FormWidgetBase
}
}
+ if (!$this->childAddItemCalled && $currentValue === null) {
+ $this->formWidgets = [];
+ return;
+ }
+
+ if ($this->childAddItemCalled && !isset($currentValue[$this->childIndexCalled])) {
+ // If no value is available but a child repeater has added an item, add a "stub" repeater item
+ $this->makeItemFormWidget($this->childIndexCalled);
+ }
+
if (!is_array($currentValue)) {
return;
}
diff --git a/modules/backend/formwidgets/RichEditor.php b/modules/backend/formwidgets/RichEditor.php
index 0949ced02..d8d26785e 100644
--- a/modules/backend/formwidgets/RichEditor.php
+++ b/modules/backend/formwidgets/RichEditor.php
@@ -186,6 +186,19 @@ class RichEditor extends FormWidgetBase
{
$result = [];
+ /**
+ * @event backend.richeditor.listTypes
+ * Register additional "page link types" to the RichEditor FormWidget
+ *
+ * Example usage:
+ *
+ * Event::listen('backend.richeditor.listTypes', function () {
+ * return [
+ * 'my-identifier' => 'author.plugin::lang.richeditor.link_types.my_identifier',
+ * ];
+ * });
+ *
+ */
$apiResult = Event::fire('backend.richeditor.listTypes');
if (is_array($apiResult)) {
foreach ($apiResult as $typeList) {
@@ -205,6 +218,28 @@ class RichEditor extends FormWidgetBase
protected function getPageLinks($type)
{
$result = [];
+
+ /**
+ * @event backend.richeditor.getTypeInfo
+ * Register additional "page link types" to the RichEditor FormWidget
+ *
+ * Example usage:
+ *
+ * Event::listen('backend.richeditor.getTypeInfo', function ($type) {
+ * if ($type === 'my-identifier') {
+ * return [
+ * 'https://example.com/page1' => 'Page 1',
+ * 'https://example.com/parent-page' => [
+ * 'title' => 'Parent Page',
+ * 'links' => [
+ * 'https://example.com/child-page' => 'Child Page',
+ * ],
+ * ],
+ * ];
+ * }
+ * });
+ *
+ */
$apiResult = Event::fire('backend.richeditor.getTypeInfo', [$type]);
if (is_array($apiResult)) {
foreach ($apiResult as $typeInfo) {
diff --git a/modules/backend/lang/de/lang.php b/modules/backend/lang/de/lang.php
index 5118bb7f5..e765f39f6 100644
--- a/modules/backend/lang/de/lang.php
+++ b/modules/backend/lang/de/lang.php
@@ -2,12 +2,15 @@
return [
'auth' => [
- 'title' => 'Admin-Bereich'
+ 'title' => 'Admin-Bereich',
+ 'invalid_login' => 'Die Angaben stimmen nicht mit unseren Aufzeichnungen überein. Überprüfen Sie diese und versuchen Sie es noch einmal.',
],
'field' => [
'invalid_type' => 'Ungültiger Feldtyp :type.',
'options_method_invalid_model' => 'Das Attribut ":field" löst sich nicht zu einen gültigen Model auf. Probiere die options Methode der Model-Klasse :model explicit zu definieren.',
- 'options_method_not_exists' => 'Die Model-Klasse :model muss eine Methode :method() mit Rückgabe der Werte von ":field" besitzen.',
+ 'options_method_not_exists' => 'Die Modell-Klasse :model muss eine Methode :method() mit Rückgabe der Werte von ":field" besitzen.',
+ 'options_static_method_invalid_value' => "Die statische Methode ':method()' in der Klasse :class hat kein valides Optionsarray zurückgegeben.",
+ 'colors_method_not_exists' => "Die Modellklasse :model muss eine Methode :method() definieren, welche html color (HEX) codes für das ':field' Formularfeld zurückgibt.",
],
'widget' => [
'not_registered' => "Ein Widget namens ':name' wurde nicht registriert",
@@ -15,6 +18,11 @@ return [
],
'page' => [
'untitled' => "Unbenannt",
+ '404' => [
+ 'label' => 'Seite nicht gefunden',
+ 'help' => "Die von Ihnen angeforderte Seite konnte nicht gefunden werden.",
+ 'back_link' => 'Zurück zur vorigen Seite',
+ ],
'access_denied' => [
'label' => "Zugriff verweigert",
'help' => "Sie haben nicht die erforderlichen Berechtigungen, um diese Seite zu sehen.",
@@ -28,14 +36,23 @@ return [
],
'partial' => [
'not_found_name' => "Das Partial ':name' wurde nicht gefunden.",
+ 'invalid_name' => 'Ungültiger Partial: :name.',
+ ],
+ 'ajax_handler' => [
+ 'invalid_name' => 'Ungültiger AJAX handler: :name.',
+ 'not_found' => "AJAX handler ':name' wurde nicht gefunden.",
],
'account' => [
+ 'impersonate_confirm' => 'Sind Sie sicher, dass Sie sich als dieser Benutzer anmelden wollen? Sie können zu Ihrem ursprünglichen Zustand zurückkehren, indem Sie sich abmelden.',
+ 'impersonate_success' => 'Sie sind jetzt als dieser Benutzer angemeldet',
+ 'signed_in_as' => 'Angemeldet als :full_name',
'sign_out' => 'Abmelden',
'login' => 'Anmelden',
'reset' => 'Zurücksetzen',
'restore' => 'Wiederherstellen',
'login_placeholder' => 'Benutzername',
'password_placeholder' => 'Passwort',
+ 'remember_me' => 'Angemeldet bleiben',
'forgot_password' => "Passwort vergessen?",
'enter_email' => "Bitte E-Mail-Adresse eingeben",
'enter_login' => "Bitte Benutzernamen eingeben",
@@ -112,6 +129,8 @@ return [
'last_name' => 'Nachname',
'full_name' => 'Kompletter Name',
'email' => 'E-Mail',
+ 'role_field' => 'Rolle',
+ 'role_comment' => 'Rollen definieren Benutzerberechtigungen, die auf Benutzerebene auf der Registerkarte Berechtigungen überschrieben werden können.',
'groups' => 'Gruppen',
'groups_comment' => 'Geben Sie hier die Gruppenzugehörigkeit an',
'avatar' => 'Avatar',
@@ -148,9 +167,25 @@ return [
'return' => 'Zurück zur Gruppen-Übersicht',
'users_count' => 'Benutzer',
],
+ 'role' => [
+ 'name' => 'Rolle',
+ 'name_field' => 'Name',
+ 'name_comment' => 'Der Name wird in der Rollenliste auf dem Administratorformular angezeigt.',
+ 'description_field' => 'Beschreibung',
+ 'code_field' => 'Code',
+ 'code_comment' => 'Geben Sie einen eindeutigen Code an, wenn Sie mit der API auf das Rollenobjekt zugreifen möchten.',
+ 'menu_label' => 'Rollen verwalten',
+ 'list_title' => 'Rollen verwalten',
+ 'new' => 'Neue Rolle',
+ 'delete_confirm' => 'Diese Administratorrolle löschen?',
+ 'return' => 'Zurück zur Rollenliste',
+ 'users_count' => 'Benutzer',
+ ],
'preferences' => [
'not_authenticated' => 'Zum Speichern oder Anzeigen dieser Einstellungen liegt kein Nutzerkonto vor'
- ]
+ ],
+ 'trashed_hint_title' => 'Dieses Konto wurde gelöscht.',
+ 'trashed_hint_desc' => 'Dieses Konto wurde gelöscht und kann nicht mehr angemeldet werden. Um es wiederherzustellen, klicken Sie auf das Symbol "Benutzer wiederherstellen" unten rechts',
],
'list' => [
'default_title' => 'Auflisten',
@@ -195,6 +230,11 @@ return [
'remove_confirm' => 'Sind Sie sicher?',
'remove_file' => 'Datei entfernen',
],
+ 'repeater' => [
+ 'add_new_item' => 'Neues Element hinzufügen',
+ 'min_items_failed' => ':name erfordert ein Minimum an :min Elementen, aber es wurden nur :items bereitgestellt',
+ 'max_items_failed' => ':name lässt nur bis zu :max Elemente zu, :items wurden bereitgestellt',
+ ],
'form' => [
'create_title' => "Neuer :name",
'update_title' => "Bearbeite :name",
@@ -312,6 +352,8 @@ return [
'permissions' => 'Verzeichnis :name oder ein Unterverzeichnis kann nicht von PHP beschrieben werden. Bitte setzen Sie die korrekten Rechte für den Webserver in diesem Verzeichnis.',
'extension' => 'Die PHP Erweiterung :name ist nicht installiert. Bitte installieren Sie diese Library und aktivieren Sie die Erweiterung.',
'plugin_missing' => 'Das Plugin :name hat eine Abhängigkeit die nicht installiert ist. Bitte installieren Sie alle benötigten Plugins.',
+ 'debug' => 'Der Debug-Modus ist aktiviert. Dies wird für Produktionsinstallationen nicht empfohlen.',
+ 'decompileBackendAssets' => 'Assets im Backend sind derzeit dekompiliert. Dies wird für Produktionsinstallationen nicht empfohlen.',
],
'editor' => [
'menu_label' => 'Editor Einstellungen',
@@ -367,6 +409,8 @@ return [
'minimal' => 'Minimal',
'full' => 'Vollständig',
],
+ 'paragraph_formats' => 'Absatzformatierungen',
+ 'paragraph_formats_comment' => 'Die Optionen, welche in der Dropdown-Liste für Absatzformatierungen angezeigt werden.',
],
'tooltips' => [
'preview_website' => 'Vorschau der Webseite'
@@ -386,6 +430,8 @@ return [
'brand' => 'Brand',
'logo' => 'Logo',
'logo_description' => 'Lade ein eigenes Logo hoch, das im Backend verwendet werden soll.',
+ 'favicon' => 'Favicon',
+ 'favicon_description' => 'Laden Sie ein benutzerdefiniertes Favicon zur Verwendung im Back-End hoch',
'app_name' => 'App-Name',
'app_name_description' => 'Dieser Name wird als Titel des Backends angezeigt.',
'app_tagline' => 'App-Tagline',
@@ -399,6 +445,7 @@ return [
'navigation' => 'Navigation',
'menu_mode' => 'Menustyles',
'menu_mode_inline' => 'Inline',
+ 'menu_mode_inline_no_icons' => 'Inline (ohne Icons)',
'menu_mode_tile' => 'Tiles',
'menu_mode_collapsed' => 'Collapsed'
],
@@ -503,6 +550,7 @@ return [
],
'permissions' => [
'manage_media' => 'Medien verwalten',
+ 'allow_unsafe_markdown' => 'Unsicheres Markdown verwenden (kann Javascript enthalten)',
],
'mediafinder' => [
'label' => 'Media Finder',
@@ -534,7 +582,12 @@ return [
'multiple_selected' => 'Mehrere Dateien ausgewählt.',
'uploading_file_num' => 'Lade :number Datei(en)...',
'uploading_complete' => 'Upload vollständig',
+ 'uploading_error' => 'Upload fehlgeschlagen',
+ 'type_blocked' => 'Der verwendete Dateityp ist aus Sicherheitsgründen gesperrt.',
'order_by' => 'Sortieren nach',
+ 'direction' => 'Direction',
+ 'direction_asc' => 'Aufsteigend',
+ 'direction_desc' => 'Absteigend',
'folder' => 'Ordner',
'no_files_found' => 'Keine entsprechenden Dateien gefunden.',
'delete_empty' => 'Bitte Wählen Sie Dateien zum Löschen aus.',
@@ -557,11 +610,11 @@ return [
'restore' => 'Alle Änderungen rückgängig machen',
'resize' => 'Größe anpassen...',
'selection_mode_normal' => 'Normal',
- 'selection_mode_fixed_ratio' => 'Fixes Verhältnis',
- 'selection_mode_fixed_size' => 'Fixe Größe',
+ 'selection_mode_fixed_ratio' => 'Festes Verhältnis',
+ 'selection_mode_fixed_size' => 'Feste Größe',
'height' => 'Höhe',
'width' => 'Breite',
- 'selection_mode' => 'Selection mode',
+ 'selection_mode' => 'Auswahlmodus',
'resize_image' => 'Bildgröße anpassen',
'image_size' => 'Dimensionen:',
'selected_size' => 'Ausgewählt:'
diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php
index 368664c63..aca0ab6cd 100644
--- a/modules/backend/lang/en/lang.php
+++ b/modules/backend/lang/en/lang.php
@@ -9,6 +9,7 @@ return [
'invalid_type' => 'Invalid field type used :type.',
'options_method_invalid_model' => "The attribute ':field' does not resolve to a valid model. Try specifying the options method for model class :model explicitly.",
'options_method_not_exists' => "The model class :model must define a method :method() returning options for the ':field' form field.",
+ 'options_static_method_invalid_value' => "The static method ':method()' on :class did not return a valid options array.",
'colors_method_not_exists' => "The model class :model must define a method :method() returning html color HEX codes for the ':field' form field.",
],
'widget' => [
@@ -372,6 +373,7 @@ return [
'editor' => [
'menu_label' => 'Editor settings',
'menu_description' => 'Customize the global editor preferences, such as font size and color scheme.',
+ 'preview' => 'Preview',
'font_size' => 'Font size',
'tab_size' => 'Tab size',
'use_hard_tabs' => 'Indent using tabs',
diff --git a/modules/backend/lang/it/lang.php b/modules/backend/lang/it/lang.php
index 022bdbb33..e0dab8b92 100644
--- a/modules/backend/lang/it/lang.php
+++ b/modules/backend/lang/it/lang.php
@@ -228,7 +228,7 @@ return [
'preview_no_record_message' => 'Nessun record selezionato.',
'select' => 'Seleziona',
'select_all' => 'seleziona tutto',
- 'select_none' => 'non selezionare niente',
+ 'select_none' => 'deseleziona tutto',
'select_placeholder' => 'seleziona',
'insert_row' => 'Inserisci riga',
'insert_row_below' => 'Inserisci riga sotto',
diff --git a/modules/backend/lang/nl/lang.php b/modules/backend/lang/nl/lang.php
index d214a087c..4c8375b32 100644
--- a/modules/backend/lang/nl/lang.php
+++ b/modules/backend/lang/nl/lang.php
@@ -9,6 +9,7 @@ return [
'invalid_type' => 'Ongeldig type veld: :type.',
'options_method_invalid_model' => "Het attribuut ':field' levert geen geldig model op. Probeer de opties methode expliciet te specifieren voor modelklasse :model.",
'options_method_not_exists' => 'De modelklasse :model moet de methode :method() definiëren met daarin opties voor het veld ":field".',
+ 'options_static_method_invalid_value' => "De statische methode ':method()' in :class leverde geen geldige array met opties op.",
'colors_method_not_exists' => 'De modelklasse :model moet de methode :method() definiëren met daarin html HEX kleurcodes voor het veld ":field".',
],
'widget' => [
@@ -372,6 +373,7 @@ return [
'editor' => [
'menu_label' => 'Editor instellingen',
'menu_description' => 'Beheer editor instellingen, zoals lettergrootte en kleurschema.',
+ 'preview' => 'Voorbeeldweergave',
'font_size' => 'Lettergrootte',
'tab_size' => 'Tab grootte',
'use_hard_tabs' => 'Inspringen met tabs',
@@ -406,6 +408,7 @@ return [
'label' => 'Label',
'class_name' => 'Class naam',
'markup_tags' => 'Opmaak HTML-tags',
+ 'markup_tag' => 'Opmaak HTML-tag',
'allowed_empty_tags' => 'Toegestane lege HTML-tags',
'allowed_empty_tags_comment' => 'Een lijst van HTML-tags die niet worden verwijderd als ze leeg zijn.',
'allowed_tags' => 'Toegestane HTML-tags',
@@ -416,6 +419,7 @@ return [
'remove_tags_comment' => 'Een lijst van HTML-tags die samen met hun inhoud worden verwijderd.',
'line_breaker_tags' => 'Line breaker tags',
'line_breaker_tags_comment' => 'Een lijst van HTML-tags waartussen een line breaker element wordt geplaatst.',
+ 'toolbar_options' => 'Toolbar opties',
'toolbar_buttons' => 'Toolbar knoppen',
'toolbar_buttons_comment' => 'De toolbar knoppen die standaard getoond worden door de Rich Editor.',
'toolbar_buttons_preset' => 'Voeg preset toe voor toolbar knoppen:',
@@ -424,9 +428,11 @@ return [
'minimal' => 'Minimaal',
'full' => 'Volledig',
],
+ 'paragraph_formats' => 'Paragraaf formaten',
+ 'paragraph_formats_comment' => 'De opties die in de "Paragraaf formaat" lijst zullen verschijnen.',
],
'tooltips' => [
- 'preview_website' => 'Voorvertoning website',
+ 'preview_website' => 'Voorbeeldweergave website',
],
'mysettings' => [
'menu_label' => 'Mijn instellingen',
diff --git a/modules/backend/lang/sl/lang.php b/modules/backend/lang/sl/lang.php
index d8ca85772..4cb459a2b 100644
--- a/modules/backend/lang/sl/lang.php
+++ b/modules/backend/lang/sl/lang.php
@@ -6,10 +6,11 @@ return [
'invalid_login' => 'Podatki, ki ste jih vnesli, se ne ujemajo z našimi zapisi. Prosimo, ponovno preverite podatke in poskusite znova.',
],
'field' => [
- 'invalid_type' => 'Uporabljen je neveljaven tip polja :type.',
- 'options_method_invalid_model' => "Atribut ':field' ne ustreza veljavnemu modelu. Poskusite natančno določiti možnosti metode za model :model.",
- 'options_method_not_exists' => "Model :model mora vsebovati metodo :method(), ki vrača možnosti za polje ':field' na obrazcu.",
- 'colors_method_not_exists' => "Model :model mora vsebovati metodo :method(), ki vrača HTML barvne kode v HEX formatu za polje ':field' na obrazcu.",
+ 'invalid_type' => 'Uporabljen je neveljaven tip polja :type.',
+ 'options_method_invalid_model' => "Atribut ':field' ne ustreza veljavnemu modelu. Poskusite natančno določiti možnosti metode za model :model.",
+ 'options_method_not_exists' => "Model :model mora vsebovati metodo :method(), ki vrača možnosti za polje ':field' na obrazcu.",
+ 'options_static_method_invalid_value' => "Statična metoda ':method()' v razredu :class ni vrnila veljavnih možnosti.",
+ 'colors_method_not_exists' => "Model :model mora vsebovati metodo :method(), ki vrača HTML barvne kode v HEX formatu za polje ':field' na obrazcu.",
],
'widget' => [
'not_registered' => "Ime vtičnika ':name' ni bilo registrirano.",
@@ -48,6 +49,10 @@ return [
'impersonate_working' => 'Impersoniram...',
'impersonating' => 'Impersonacija uporabnika :full_name',
'stop_impersonating' => 'Prekliči impersonacijo',
+ 'unsuspend' => 'Odsuspendiraj',
+ 'unsuspend_confirm' => 'Ali ste prepričani, da želite odsuspendirati tega uporabnika?',
+ 'unsuspend_success' => 'Uporabnik je odsuspendiran.',
+ 'unsuspend_working' => 'Odsuspendiram...',
'signed_in_as' => 'Prijavljeni ste kot :full_name',
'sign_out' => 'Odjava',
'login' => 'Prijava',
@@ -402,6 +407,7 @@ return [
'label' => 'Opis',
'class_name' => 'Oznaka razreda',
'markup_tags' => 'Označevalne oznake',
+ 'markup_tag' => 'Označevalna oznaka',
'allowed_empty_tags' => 'Dovoljene prazne oznake',
'allowed_empty_tags_comment' => 'Seznam oznak, ki niso odstranjene, če v njih ni vsebine.',
'allowed_tags' => 'Dovoljene oznake',
@@ -412,6 +418,7 @@ return [
'remove_tags_comment' => 'Seznam oznak, ki so odstranjene skupaj z njihovo vsebino.',
'line_breaker_tags' => 'Oznake prekinitve vrstic',
'line_breaker_tags_comment' => 'Seznam oznak, ki se uporabljajo za postavitev elementa prekinitve med vrstice.',
+ 'toolbar_options' => 'Nastavitve orodne vrstice',
'toolbar_buttons' => 'Gumbi orodne vrstice',
'toolbar_buttons_comment' => 'Gumbi orodne vrstice, ki se privzeto prikažejo v urejevalniku. [fullscreen, bold, italic, underline, strikeThrough, subscript, superscript, fontFamily, fontSize, |, color, emoticons, inlineStyle, paragraphStyle, |, paragraphFormat, align, formatOL, formatUL, outdent, indent, quote, insertHR, -, insertLink, insertImage, insertVideo, insertAudio, insertFile, insertTable, undo, redo, clearFormatting, selectAll, html]',
'toolbar_buttons_preset' => 'Vstavite prednastavljeno konfiguracijo gumbov orodne vrstice:',
@@ -420,6 +427,8 @@ return [
'minimal' => 'Minimalno',
'full' => 'Polno',
],
+ 'paragraph_formats' => 'Formati odstavkov',
+ 'paragraph_formats_comment' => 'Možnosti, ki se prikažejo v spustnem seznamu Format odstavka.',
],
'tooltips' => [
'preview_website' => 'Ogled spletne strani',
@@ -470,7 +479,7 @@ return [
],
'access_log' => [
'hint' => 'Ta dnevnik beleži seznam uspešnih prijav administratorjev. Zapisi se hranijo :days dni.',
- 'menu_label' => 'Dnevnik dostopa',
+ 'menu_label' => 'Dnevnik dostopov',
'menu_description' => 'Prikaz seznama uspešnih prijav administratorjev.',
'id' => 'ID',
'created_at' => 'Datum in čas',
@@ -558,11 +567,12 @@ return [
'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' => 'Nalaganje in upravljanje z media vsebinami - slike, video posnetki, zvočni posnetki, dokumenti',
+ 'manage_media' => 'Nalaganje in upravljanje z media vsebinami - slike, video posnetki, zvočni posnetki, dokumenti',
+ 'allow_unsafe_markdown' => 'Dovoli uporabo nevarnih označb (lahko vključi Javascript)',
],
'mediafinder' => [
'label' => 'Media brskalnik',
diff --git a/modules/backend/lang/zh-tw/lang.php b/modules/backend/lang/zh-tw/lang.php
index 35ab3510b..9655ef6e6 100644
--- a/modules/backend/lang/zh-tw/lang.php
+++ b/modules/backend/lang/zh-tw/lang.php
@@ -52,6 +52,7 @@ return [
'widget_width' => '寬度',
'full_width' => '全部寬度',
'add_widget' => '新增元件',
+ 'manage_widgets' => '管理元件',
'widget_inspector_title' => '元件設定',
'widget_inspector_description' => '設定報表元件',
'widget_columns_label' => '寬度 :columns',
@@ -62,6 +63,12 @@ return [
'widget_new_row_description' => '把元件放到新列',
'widget_title_label' => '元件標題',
'widget_title_error' => '需要元件標題',
+ 'reset_layout' => '重置版面',
+ 'reset_layout_confirm' => '確定重置為預設版面?',
+ 'reset_layout_success' => '版面已重置。',
+ 'make_default' => '設定為預設',
+ 'make_default_confirm' => '確定將此版面設定為預設?',
+ 'make_default_success' => '已設定此版面為預設。',
'status' => [
'widget_title_default' => '系統狀態',
'update_available' => '{0} 更新可用!|{1} 更新可用!|[2,Inf] 更新可用!'
@@ -170,7 +177,7 @@ return [
'field_off' => '關',
'field_on' => '開',
'add' => '增加',
- 'apply' => '應用',
+ 'apply' => '確定',
'cancel' => '取消',
'close' => '關閉',
'confirm' => '確認',
@@ -297,7 +304,9 @@ return [
'email' => 'Email'
],
'filter' => [
- 'all' => '全部'
+ 'all' => '全部',
+ 'date_all' => '全部區間',
+ 'number_all' => '全部數目',
],
'permissions' => [
'manage_media' => 'Upload and manage media contents - images, videos, sounds, documents'
@@ -316,10 +325,10 @@ return [
'display' => '顯示',
'filter_everything' => '所有',
'filter_images' => '圖片',
- 'filter_video' => '視頻',
- 'filter_audio' => '音頻',
+ 'filter_video' => '影片',
+ 'filter_audio' => '音訊',
'filter_documents' => '文檔',
- 'library' => '庫',
+ 'library' => '媒體庫',
'size' => '大小',
'title' => '標題',
'last_modified' => '最近修改',
@@ -332,7 +341,7 @@ return [
'multiple_selected' => '多選.',
'uploading_file_num' => '上傳 :number 檔案...',
'uploading_complete' => '上傳完畢',
- 'order_by' => '排序',
+ 'order_by' => '排列方式',
'folder' => '檔案夾',
'no_files_found' => '沒找到您請求的檔案.',
'delete_empty' => '請選擇刪除項.',
@@ -362,6 +371,9 @@ return [
'selection_mode' => '選擇模式',
'resize_image' => '調整圖片',
'image_size' => '圖片大小:',
- 'selected_size' => '選中:'
+ 'selected_size' => '選中:',
+ 'direction' => '順序',
+ 'direction_asc' => '升冪',
+ 'direction_desc' => '降冪',
]
];
diff --git a/modules/backend/layouts/_custom_styles.htm b/modules/backend/layouts/_custom_styles.htm
index 8eec0413b..cb4e3ad82 100644
--- a/modules/backend/layouts/_custom_styles.htm
+++ b/modules/backend/layouts/_custom_styles.htm
@@ -9,6 +9,6 @@
diff --git a/modules/backend/layouts/_mainmenu.htm b/modules/backend/layouts/_mainmenu.htm
index 0cf31ac6d..3327218e4 100644
--- a/modules/backend/layouts/_mainmenu.htm
+++ b/modules/backend/layouts/_mainmenu.htm
@@ -49,15 +49,24 @@