diff --git a/core/database/migrations/2026_08_29_000000_create_site_module_roles_table.php b/core/database/migrations/2026_08_29_000000_create_site_module_roles_table.php
new file mode 100644
index 0000000000..15a40f3916
--- /dev/null
+++ b/core/database/migrations/2026_08_29_000000_create_site_module_roles_table.php
@@ -0,0 +1,79 @@
+comment('Module role access control - defines which user roles may run specific modules');
+ $table->increments('id');
+ $table->unsignedInteger('module')->default(0);
+ $table->unsignedInteger('role')->default(0);
+ $table->unique(['module', 'role'], 'site_module_roles_module_role');
+ });
+ }
+
+ $this->applyDefaultRestrictions();
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('site_module_roles');
+ }
+
+ /**
+ * Restrict bundled modules on sites that already have them installed.
+ *
+ * Only modules with no restriction at all are touched, so an administrator
+ * who has opened a module up keeps that decision.
+ */
+ protected function applyDefaultRestrictions(): void
+ {
+ if (!Schema::hasTable('site_modules') || !Schema::hasTable('user_roles')) {
+ return;
+ }
+
+ foreach (ModuleAccess::DEFAULT_RESTRICTIONS as $name => $roleIds) {
+ $moduleId = (int) DB::table('site_modules')->where('name', $name)->value('id');
+ if ($moduleId <= 0) {
+ continue;
+ }
+
+ if (DB::table('site_module_roles')->where('module', $moduleId)->exists()) {
+ continue;
+ }
+
+ foreach (ModuleAccess::normalizeRoleIds($roleIds) as $roleId) {
+ if (!DB::table('user_roles')->where('id', $roleId)->exists()) {
+ continue;
+ }
+
+ DB::table('site_module_roles')->insert([
+ 'module' => $moduleId,
+ 'role' => $roleId,
+ ]);
+ }
+ }
+ }
+}
diff --git a/core/lang/az/global.php b/core/lang/az/global.php
index a3fb7d4349..f1f71bc677 100644
--- a/core/lang/az/global.php
+++ b/core/lang/az/global.php
@@ -80,6 +80,10 @@
$_lang["tmplvars_type"] = 'Daxil Etmə Növü';
$_lang["tmplvars_widget"] = 'Widget';
$_lang["tmplvars_widget_prop"] = 'Widget Xüsusiyyətləri';
+$_lang["role_modules_tab"] = 'Modullar';
+$_lang["role_modules_msg"] = 'Bu rolun işə salmasına icazə verilən modullar. Heç kimin məhdudlaşdırmadığı modul bütün rollar üçün əlçatan qalır — buradan işarəni götürmək onu məhdudlaşdırır.';
+$_lang["role_modules_admin_msg"] = 'Administrator rolu bütün modulları işə sala bilər.';
+$_lang["role_modules_none"] = 'Hələ heç bir modul quraşdırılmayıb.';
$_lang["role_no_tv"] = 'Bu Rol üçün hələ dəyişənlər təyin olunmayıb.';
$_lang["role_notassigned_tv"] = 'Təyin edilə bilən dəyişənlər.';
$_lang["role_tv_msg"] = 'Bu Rola təyin olunmuş dəyişənlər aşağıda göstərilib.';
@@ -203,6 +207,9 @@
$_lang["module_desc"] = 'Təsvir';
$_lang["module_disabled"] = 'Modul deaktiv edilib';
$_lang["module_edit_click_title"] = 'Bu Modulu redaktə etmək üçün buraya klikləyin';
+$_lang["module_exec_no_privileges"] = 'Bu modulu işə salmaq üçün kifayət qədər icazəniz yoxdur.';
+$_lang["module_exec_not_found"] = '%s id-li modul tapılmadı.';
+$_lang["module_exec_disabled"] = 'Bu modul deaktivdir və işə salına bilməz.';
$_lang["module_group_access_msg"] = 'Bu Modulu İdarəetmə Panelində icra edə biləcək İstifadəçi Qruplarını seçin.';
$_lang["module_management"] = 'Modulları idarə et';
$_lang["module_management_msg"] = 'İcra etmək və ya dəyişdirmək istədiyiniz Modulu seçin. Modulu işə salmaq üçün şəbəkədəki ikona klikləyin. Modulu redaktə etmək üçün onun adına klikləyin.';
diff --git a/core/lang/be/global.php b/core/lang/be/global.php
index 168a85087a..c944295d8a 100644
--- a/core/lang/be/global.php
+++ b/core/lang/be/global.php
@@ -68,6 +68,10 @@
$_lang["tmplvars_type"] = 'Тып уводу';
$_lang["tmplvars_widget"] = 'Віджэт';
$_lang["tmplvars_widget_prop"] = 'Уласцівасці віджэта';
+$_lang["role_modules_tab"] = 'Модулі';
+$_lang["role_modules_msg"] = 'Модулі, якія дазволена запускаць гэтай ролі. Модуль, які ніхто не абмежаваў, застаецца даступным усім ролям — зніміце адзнаку, каб абмежаваць яго.';
+$_lang["role_modules_admin_msg"] = 'Роля адміністратара можа запускаць любыя модулі.';
+$_lang["role_modules_none"] = 'Модулі яшчэ не ўсталяваны.';
$_lang["role_no_tv"] = 'Гэтай ролі яшчэ не было прызначана зменных.';
$_lang["role_notassigned_tv"] = 'Гэтыя зменныя даступныя для прызначэння.';
$_lang["role_tv_msg"] = 'Зменныя, прызначаныя гэтай ролі, пералічаны ніжэй.';
@@ -191,6 +195,9 @@
$_lang["module_desc"] = 'Апісанне';
$_lang["module_disabled"] = 'Модуль адключаны';
$_lang["module_edit_click_title"] = 'Націсніце тут, каб змяніць гэты модуль';
+$_lang["module_exec_no_privileges"] = 'У вас недастаткова правоў для запуску гэтага модуля.';
+$_lang["module_exec_not_found"] = 'Модуль з id %s не знойдзены.';
+$_lang["module_exec_disabled"] = 'Гэты модуль адключаны і не можа быць запушчаны.';
$_lang["module_group_access_msg"] = 'Выберыце групы карыстальнікаў, якім дазволена выконваць гэты модуль з Менеджара кантэнту.';
$_lang["module_management"] = 'Кіраванне модулямі';
$_lang["module_management_msg"] = 'Выберыце модуль, які вы хочаце выканаць або змяніць. Каб запусціць модуль, націсніце на значок у сетцы. Каб змяніць модуль, націсніце на назву модуля.';
diff --git a/core/lang/bg/global.php b/core/lang/bg/global.php
index 12849c4650..78073ab066 100644
--- a/core/lang/bg/global.php
+++ b/core/lang/bg/global.php
@@ -504,6 +504,9 @@
$_lang["module_desc"] = 'Описание';
$_lang["module_disabled"] = 'Модулът е спрян';
$_lang["module_edit_click_title"] = 'Щракнете тук, за да редактирате този модул';
+$_lang["module_exec_no_privileges"] = 'Нямате достатъчно права да изпълните този модул.';
+$_lang["module_exec_not_found"] = 'Не е намерен модул с id %s.';
+$_lang["module_exec_disabled"] = 'Този модул е изключен и не може да бъде изпълнен.';
$_lang["module_group_access_msg"] = 'Изберете потребителските групи, имащи права да стартират този модул от Мениджъра.';
$_lang["module_management"] = 'Управление на Модули';
$_lang["module_management_msg"] = 'Тук можете да изберете модула, който желаете да стартирате или модифицирате. За да стартирате модул, щракнете върху иконата в таблицата. За да модифицирате модул, кликнете върху името на модула.';
@@ -1249,6 +1252,10 @@
$_lang["check_none"] = "Select none";
$_lang["check_toggle"] = "Toggle selection";
+$_lang["role_modules_tab"] = 'Модули';
+$_lang["role_modules_msg"] = 'Модули, които тази роля може да изпълнява. Модул, който никой не е ограничил, остава достъпен за всички роли — премахването на отметката тук го ограничава.';
+$_lang["role_modules_admin_msg"] = 'Ролята администратор може да изпълнява всички модули.';
+$_lang["role_modules_none"] = 'Все още няма инсталирани модули.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/cs/global.php b/core/lang/cs/global.php
index c0432c5849..1cf881b082 100644
--- a/core/lang/cs/global.php
+++ b/core/lang/cs/global.php
@@ -504,6 +504,9 @@
$_lang["module_desc"] = 'Popis';
$_lang["module_disabled"] = 'Modul je zakázaný';
$_lang["module_edit_click_title"] = 'Pro úpravu tohoto modulu klikněte zde';
+$_lang["module_exec_no_privileges"] = 'Nemáte dostatečná oprávnění ke spuštění tohoto modulu.';
+$_lang["module_exec_not_found"] = 'Nebyl nalezen žádný modul s id %s.';
+$_lang["module_exec_disabled"] = 'Tento modul je zakázán a nelze jej spustit.';
$_lang["module_group_access_msg"] = 'Zvolte skupiny uživatelů, které mají oprávnění spustit tento modul ve Správci obsahu.';
$_lang["module_management"] = 'Správa modulů';
$_lang["module_management_msg"] = 'Zde můžete zvolit modul, který byste chtěli spustit nebo upravit. Ke spuštění modulu klikněte na ikonu kostky. Pro úpravu modulu klikněte na název modulu.';
@@ -1253,6 +1256,10 @@
$_lang["check_none"] = "Select none";
$_lang["check_toggle"] = "Toggle selection";
+$_lang["role_modules_tab"] = 'Moduly';
+$_lang["role_modules_msg"] = 'Moduly, které tato role smí spouštět. Modul, který nikdo neomezil, zůstává dostupný všem rolím — zrušením zaškrtnutí jej omezíte.';
+$_lang["role_modules_admin_msg"] = 'Role administrátora může spustit každý modul.';
+$_lang["role_modules_none"] = 'Zatím nejsou nainstalovány žádné moduly.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/da/global.php b/core/lang/da/global.php
index 09aade39da..29d2602412 100644
--- a/core/lang/da/global.php
+++ b/core/lang/da/global.php
@@ -505,6 +505,9 @@
$_lang["module_desc"] = 'Beskrivelse';
$_lang["module_disabled"] = 'Modulet deaktiveret';
$_lang["module_edit_click_title"] = 'Klik her for at redigere dette modul';
+$_lang["module_exec_no_privileges"] = 'Du har ikke tilstrækkelige rettigheder til at køre dette modul.';
+$_lang["module_exec_not_found"] = 'Der blev ikke fundet noget modul med id %s.';
+$_lang["module_exec_disabled"] = 'Dette modul er deaktiveret og kan ikke køres.';
$_lang["module_group_access_msg"] = 'Vælg den eller de brugergrupper som har rettigheder til at eksekvere dette modul indefra CMS\'et.';
$_lang["module_management"] = 'Administrer moduler';
$_lang["module_management_msg"] = 'Vælg det modul du vil eksekvere eller redigere. For at køre modulet skal man klikke på ikonet i oversigten. For at redigere modulet skal man klikke på modulets navn.';
@@ -1252,6 +1255,10 @@
$_lang["check_none"] = "Select none";
$_lang["check_toggle"] = "Toggle selection";
+$_lang["role_modules_tab"] = 'Moduler';
+$_lang["role_modules_msg"] = 'Moduler, som denne rolle må køre. Et modul, som ingen har begrænset, er tilgængeligt for alle roller — det er fjernelsen af fluebenet her, der begrænser det.';
+$_lang["role_modules_admin_msg"] = 'Administratorrollen kan køre alle moduler.';
+$_lang["role_modules_none"] = 'Der er endnu ikke installeret nogen moduler.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/de/global.php b/core/lang/de/global.php
index ea3e4f1f78..c5f6954dcd 100644
--- a/core/lang/de/global.php
+++ b/core/lang/de/global.php
@@ -541,6 +541,9 @@
$_lang["module_desc"] = 'Beschreibung';
$_lang["module_disabled"] = 'Modul deaktivieren';
$_lang["module_edit_click_title"] = 'Klicken Sie hier, um das Modul zu bearbeiten';
+$_lang["module_exec_no_privileges"] = 'Sie haben nicht genügend Rechte, um dieses Modul auszuführen.';
+$_lang["module_exec_not_found"] = 'Kein Modul mit der ID %s gefunden.';
+$_lang["module_exec_disabled"] = 'Dieses Modul ist deaktiviert und kann nicht ausgeführt werden.';
$_lang["module_group_access_msg"] = 'Wählen Sie die Benutzergruppen aus, die dieses Modul innerhalb des MODX-Managers ausführen dürfen.';
$_lang["module_management"] = 'Modul-Verwaltung';
$_lang["module_management_msg"] = 'In dieser Übersicht können Sie Module ausführen und bearbeiten. Um ein Modul zu starten, klicken Sie auf das Symbol des Moduls, für die Bearbeitung auf den Namen.';
@@ -1477,6 +1480,10 @@
$_lang['manager_menu_position_left'] = 'links';
$_lang['invalid_event_response'] = 'Der Event "%s" verursachte eine invalide Ausgabe.';
+$_lang["role_modules_tab"] = 'Module';
+$_lang["role_modules_msg"] = 'Module, die diese Rolle ausführen darf. Ein Modul, das niemand eingeschränkt hat, bleibt für alle Rollen verfügbar — das Häkchen hier zu entfernen schränkt es ein.';
+$_lang["role_modules_admin_msg"] = 'Die Administratorrolle kann jedes Modul ausführen.';
+$_lang["role_modules_none"] = 'Es sind noch keine Module installiert.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/en/global.php b/core/lang/en/global.php
index 700353862d..0e18021d1d 100644
--- a/core/lang/en/global.php
+++ b/core/lang/en/global.php
@@ -560,6 +560,9 @@
$_lang["module_desc"] = 'Description';
$_lang["module_disabled"] = 'Module disabled';
$_lang["module_edit_click_title"] = 'Click here to edit this Module';
+$_lang["module_exec_no_privileges"] = 'You do not have sufficient privileges to execute this Module.';
+$_lang["module_exec_not_found"] = 'No Module found for id %s.';
+$_lang["module_exec_disabled"] = 'This Module is disabled and cannot be executed.';
$_lang["module_group_access_msg"] = 'Select the User Groups that are allowed to execute this Module from within the Content Manager.';
$_lang["module_management"] = 'Manage Modules';
$_lang["module_management_msg"] = 'Choose the Module you would like to execute or modify. To run the Module click on the icon in the grid. To modify the Module click on the name of the Module.';
@@ -1543,6 +1546,10 @@
$_lang["lang_key_desc"] = 'Key language from array $_lang';
$_lang["key_desc"] = 'Key for checked permission';
+$_lang["role_modules_tab"] = 'Modules';
+$_lang["role_modules_msg"] = 'Modules this role is allowed to run. A module nobody has restricted stays available to every role - unticking it here is what restricts it.';
+$_lang["role_modules_admin_msg"] = 'The administrator role can run every module.';
+$_lang["role_modules_none"] = 'No modules have been installed yet.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/es/global.php b/core/lang/es/global.php
index 7405452e1e..73ef810cb6 100644
--- a/core/lang/es/global.php
+++ b/core/lang/es/global.php
@@ -543,6 +543,9 @@
$_lang["module_desc"] = 'Descripción';
$_lang["module_disabled"] = 'Modulo deshabilitado';
$_lang["module_edit_click_title"] = 'Haz clic aquí para editar este módulo';
+$_lang["module_exec_no_privileges"] = 'No tiene privilegios suficientes para ejecutar este módulo.';
+$_lang["module_exec_not_found"] = 'No se encontró ningún módulo con el id %s.';
+$_lang["module_exec_disabled"] = 'Este módulo está desactivado y no se puede ejecutar.';
$_lang["module_group_access_msg"] = 'Selecciona los grupos de usuarios que tienen permitido ejecutar este módulo desde el Administrador de Contenido.';
$_lang["module_management"] = 'Administrar Módulos';
$_lang["module_management_msg"] = 'Elige el módulo que quisieras ejecutar o modificar. Para correr el módulo haz clic en el icono en la cuadrícula. Para modificarlo ´haz clic en el nombre del módulo.';
@@ -1486,6 +1489,10 @@
$_lang['enable_mootools'] = 'Habilitar Mootools en el administrador';
$_lang['enable_mootools_message'] = 'Cargue Mootools.js en el administrador para compatibilidad con versiones anteriores';
+$_lang["role_modules_tab"] = 'Módulos';
+$_lang["role_modules_msg"] = 'Módulos que este rol puede ejecutar. Un módulo que nadie ha restringido sigue disponible para todos los roles: desmarcarlo aquí es lo que lo restringe.';
+$_lang["role_modules_admin_msg"] = 'El rol de administrador puede ejecutar cualquier módulo.';
+$_lang["role_modules_none"] = 'Todavía no hay módulos instalados.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/fa/global.php b/core/lang/fa/global.php
index b79cc23ca2..957fc4a234 100644
--- a/core/lang/fa/global.php
+++ b/core/lang/fa/global.php
@@ -505,6 +505,9 @@
$_lang["module_desc"] = 'توضیحات';
$_lang["module_disabled"] = 'تعلیق ماژول';
$_lang["module_edit_click_title"] = 'برای ویرایش ماژول اینجا کلیک کنید';
+$_lang["module_exec_no_privileges"] = 'شما دسترسی کافی برای اجرای این ماژول را ندارید.';
+$_lang["module_exec_not_found"] = 'ماژولی با شناسه %s یافت نشد.';
+$_lang["module_exec_disabled"] = 'این ماژول غیرفعال است و نمیتواند اجرا شود.';
$_lang["module_group_access_msg"] = 'گروههای کاربری که مجاز به اجرای این ماژول در بخش مدیریت محتوا هستند را انتخاب کنید.';
$_lang["module_management"] = 'مدیریت ماژولها';
$_lang["module_management_msg"] = 'در اینجا شما میتوانید ماژول مورد نظر خود را برای اجرا یا تنظیم انتخاب کنید. برای اجرای ماژول روی نماد یا آیکون در داخل همان قسمت کلیک کنید. برای تنظیم ماژول روی اسم ان کلیک کنید.';
@@ -1250,6 +1253,10 @@
$_lang["check_none"] = "Select none";
$_lang["check_toggle"] = "Toggle selection";
+$_lang["role_modules_tab"] = 'ماژولها';
+$_lang["role_modules_msg"] = 'ماژولهایی که این نقش اجازه اجرای آنها را دارد. ماژولی که کسی آن را محدود نکرده باشد برای همه نقشها در دسترس است؛ برداشتن تیک در اینجا آن را محدود میکند.';
+$_lang["role_modules_admin_msg"] = 'نقش مدیر میتواند همه ماژولها را اجرا کند.';
+$_lang["role_modules_none"] = 'هنوز هیچ ماژولی نصب نشده است.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/fi/global.php b/core/lang/fi/global.php
index 9fe6e6fea3..74cc25487b 100644
--- a/core/lang/fi/global.php
+++ b/core/lang/fi/global.php
@@ -504,6 +504,9 @@
$_lang["module_desc"] = 'Kuvaus';
$_lang["module_disabled"] = 'Ota moduuli pois käytöstä';
$_lang["module_edit_click_title"] = 'Muokkaa moduulia napsauttamalla tästä';
+$_lang["module_exec_no_privileges"] = 'Sinulla ei ole riittäviä oikeuksia tämän moduulin suorittamiseen.';
+$_lang["module_exec_not_found"] = 'Moduulia tunnuksella %s ei löytynyt.';
+$_lang["module_exec_disabled"] = 'Tämä moduuli on poistettu käytöstä eikä sitä voi suorittaa.';
$_lang["module_group_access_msg"] = 'Valitse käyttäjäryhmät, joilla on oikeus suorittaa moduuli ylläpidosta käsin.';
$_lang["module_management"] = 'Moduulit';
$_lang["module_management_msg"] = 'Valitse suoritettava tai muokattava moduuli. Suorita, muokkaa, tee kopio tai poista moduuli napsauttamalla moduulin kuvaketta. Muokkaa moduulia napsauttamalla sen nimeä.';
@@ -1248,6 +1251,10 @@
$_lang["check_none"] = "Select none";
$_lang["check_toggle"] = "Toggle selection";
+$_lang["role_modules_tab"] = 'Moduulit';
+$_lang["role_modules_msg"] = 'Moduulit, joita tämä rooli saa suorittaa. Moduuli, jota kukaan ei ole rajoittanut, on kaikkien roolien käytettävissä — valinnan poistaminen tässä rajoittaa sen.';
+$_lang["role_modules_admin_msg"] = 'Ylläpitäjän rooli voi suorittaa kaikki moduulit.';
+$_lang["role_modules_none"] = 'Moduuleja ei ole vielä asennettu.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/fr/global.php b/core/lang/fr/global.php
index 7aadd9e2aa..dcade25fdb 100644
--- a/core/lang/fr/global.php
+++ b/core/lang/fr/global.php
@@ -507,6 +507,9 @@
$_lang["module_desc"] = 'Description';
$_lang["module_disabled"] = 'Le Module est désactivé';
$_lang["module_edit_click_title"] = 'Cliquez ici pour modifier le Module';
+$_lang["module_exec_no_privileges"] = 'Vous n\'avez pas les privilèges suffisants pour exécuter ce module.';
+$_lang["module_exec_not_found"] = 'Aucun module trouvé pour l\'id %s.';
+$_lang["module_exec_disabled"] = 'Ce module est désactivé et ne peut pas être exécuté.';
$_lang["module_group_access_msg"] = 'Sélectionnez les Groupes d\'Utilisateurs ayant le droit de lancer ce Module depuis le Gestionnaire de Contenu.';
$_lang["module_management"] = 'Gestion des Modules';
$_lang["module_management_msg"] = 'Vous pouvez choisir ici le Module à lancer ou à modifier. Pour lancer le Module, cliquer sur l\'icône dans la grille. Pour modifier le Module, cliquez sur le nom du Module.';
@@ -1219,6 +1222,10 @@
$_lang["check_none"] = "Select none";
$_lang["check_toggle"] = "Toggle selection";
+$_lang["role_modules_tab"] = 'Modules';
+$_lang["role_modules_msg"] = 'Modules que ce rôle est autorisé à exécuter. Un module que personne n\'a restreint reste accessible à tous les rôles : le décocher ici est ce qui le restreint.';
+$_lang["role_modules_admin_msg"] = 'Le rôle administrateur peut exécuter tous les modules.';
+$_lang["role_modules_none"] = 'Aucun module n\'est encore installé.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/he/global.php b/core/lang/he/global.php
index e70634e842..79850cdf34 100644
--- a/core/lang/he/global.php
+++ b/core/lang/he/global.php
@@ -505,6 +505,9 @@
$_lang["module_desc"] = 'תיאור';
$_lang["module_disabled"] = 'מודול לא פעיל';
$_lang["module_edit_click_title"] = 'Click here to edit this module';
+$_lang["module_exec_no_privileges"] = 'אין לך הרשאות מספיקות להריץ מודול זה.';
+$_lang["module_exec_not_found"] = 'לא נמצא מודול עם המזהה %s.';
+$_lang["module_exec_disabled"] = 'מודול זה מושבת ולא ניתן להריץ אותו.';
$_lang["module_group_access_msg"] = 'Select the user groups that are allowed to execute this module from within the Content Manager.';
$_lang["module_management"] = 'ניהול מודולים';
$_lang["module_management_msg"] = 'Choose the module you would like to execute or modify. To run the module click on the icon in the grid. To modify the module click on the name of the module.';
@@ -1250,6 +1253,10 @@
$_lang["check_none"] = "Select none";
$_lang["check_toggle"] = "Toggle selection";
+$_lang["role_modules_tab"] = 'מודולים';
+$_lang["role_modules_msg"] = 'המודולים שתפקיד זה רשאי להריץ. מודול שאיש לא הגביל נשאר זמין לכל התפקידים — הסרת הסימון כאן היא שמגבילה אותו.';
+$_lang["role_modules_admin_msg"] = 'תפקיד המנהל יכול להריץ כל מודול.';
+$_lang["role_modules_none"] = 'עדיין לא הותקנו מודולים.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/it/global.php b/core/lang/it/global.php
index d36be00f12..f203252742 100644
--- a/core/lang/it/global.php
+++ b/core/lang/it/global.php
@@ -543,6 +543,9 @@
$_lang["module_desc"] = 'Descrizione';
$_lang["module_disabled"] = 'Modulo disabilitato';
$_lang["module_edit_click_title"] = 'Cliccate qui per modificare il Modulo';
+$_lang["module_exec_no_privileges"] = 'Non hai privilegi sufficienti per eseguire questo modulo.';
+$_lang["module_exec_not_found"] = 'Nessun modulo trovato per l\'id %s.';
+$_lang["module_exec_disabled"] = 'Questo modulo è disattivato e non può essere eseguito.';
$_lang["module_group_access_msg"] = 'Selezionate i gruppi autorizzati ad eseguire il Modulo.';
$_lang["module_management"] = 'Gestione Moduli';
$_lang["module_management_msg"] = 'Qui potete scegliere il Modulo che desiderate eseguire o modificare. Per eseguire un Modulo, fate clic sull\'icona del Modulo a sinistra sulla griglia. Per modificare un Modulo, fate clic sul suo nome.';
@@ -1496,6 +1499,10 @@
$_lang['enable_mootools'] = 'Abilita Mootools nel manager';
$_lang['enable_mootools_message'] = 'Carica nel manager la libreria Mootools.js per la retrocompatibilità';
+$_lang["role_modules_tab"] = 'Moduli';
+$_lang["role_modules_msg"] = 'Moduli che questo ruolo può eseguire. Un modulo che nessuno ha limitato resta disponibile a tutti i ruoli: togliere la spunta qui è ciò che lo limita.';
+$_lang["role_modules_admin_msg"] = 'Il ruolo amministratore può eseguire qualsiasi modulo.';
+$_lang["role_modules_none"] = 'Nessun modulo è ancora installato.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/ja/global.php b/core/lang/ja/global.php
index 924f7f4a1c..321b215bab 100644
--- a/core/lang/ja/global.php
+++ b/core/lang/ja/global.php
@@ -566,6 +566,9 @@
$_lang["module_desc"] = '説明';
$_lang["module_disabled"] = 'モジュールを停止';
$_lang["module_edit_click_title"] = 'このモジュールを編集';
+$_lang["module_exec_no_privileges"] = 'このモジュールを実行する権限がありません。';
+$_lang["module_exec_not_found"] = 'id %s のモジュールが見つかりません。';
+$_lang["module_exec_disabled"] = 'このモジュールは無効になっているため実行できません。';
$_lang["module_group_access_msg"] = '管理画面内でこのモジュールを実行できるユーザーグループを選択してください。';
$_lang["module_management"] = 'モジュール管理';
$_lang["module_management_msg"] = 'アイコンをクリックして操作を選択してください。モジュールは管理画面を拡張する形で機能を追加できます。追加する機能自体はプラグインやスニペットで作り、これをモジュール側のフォームでコントロールする実装を推奨します。';
@@ -1532,6 +1535,10 @@
$_lang["lang_key_desc"] = '配列$_langから言語のキーを設定';
$_lang["key_desc"] = 'チェックしたパーミッションに対するキー';
+$_lang["role_modules_tab"] = 'モジュール';
+$_lang["role_modules_msg"] = 'このロールが実行できるモジュールです。誰も制限していないモジュールはすべてのロールで利用できます。ここでチェックを外すと制限されます。';
+$_lang["role_modules_admin_msg"] = '管理者ロールはすべてのモジュールを実行できます。';
+$_lang["role_modules_none"] = 'モジュールはまだインストールされていません。';
$_lang["role_no_tv"] = 'このロールにはテンプレート変数がまだ割り当てられていません。';
$_lang["role_notassigned_tv"] = '割り当て可能なテンプレート変数です。';
$_lang["role_tv_msg"] = '以下のテンプレート変数にこのロールが割り当てられました。';
diff --git a/core/lang/nl/global.php b/core/lang/nl/global.php
index 3e719ae7d5..da9b163930 100644
--- a/core/lang/nl/global.php
+++ b/core/lang/nl/global.php
@@ -532,6 +532,9 @@
$_lang["module_desc"] = 'Beschrijving';
$_lang["module_disabled"] = 'Module uitgeschakeld';
$_lang["module_edit_click_title"] = 'Klik hier om de Module te bewerken';
+$_lang["module_exec_no_privileges"] = 'U hebt onvoldoende rechten om deze module uit te voeren.';
+$_lang["module_exec_not_found"] = 'Geen module gevonden met id %s.';
+$_lang["module_exec_disabled"] = 'Deze module is uitgeschakeld en kan niet worden uitgevoerd.';
$_lang["module_group_access_msg"] = 'Selecteer de Gebruikersgroepen die deze Module binnen het Content Management Systeem mogen uitvoeren.';
$_lang["module_management"] = 'Modulebeheer';
$_lang["module_management_msg"] = 'Hier kunt u Modules toevoegen, bewerken en uitvoeren.
Klik op het icoon van de Module om de Module uit te voeren. Klik op de naam van de Module om de Module te bewerken.';
@@ -1446,6 +1449,10 @@
$_lang["viewopts_fontsize"] = 'Fontgrootte';
$_lang["viewopts_cb_alltabs"] = 'Alle tabs';
+$_lang["role_modules_tab"] = 'Modules';
+$_lang["role_modules_msg"] = 'Modules die deze rol mag uitvoeren. Een module die niemand heeft beperkt blijft voor elke rol beschikbaar — het vinkje hier weghalen is wat hem beperkt.';
+$_lang["role_modules_admin_msg"] = 'De beheerdersrol kan elke module uitvoeren.';
+$_lang["role_modules_none"] = 'Er zijn nog geen modules geïnstalleerd.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/nn/global.php b/core/lang/nn/global.php
index f3e70d3a1f..a49c92d3f9 100644
--- a/core/lang/nn/global.php
+++ b/core/lang/nn/global.php
@@ -504,6 +504,9 @@
$_lang["module_desc"] = 'Beskrivelse';
$_lang["module_disabled"] = 'Modulen er slått av';
$_lang["module_edit_click_title"] = 'Klikk her for å redigere denne modulen';
+$_lang["module_exec_no_privileges"] = 'Du har ikkje tilstrekkelege rettar til å køyre denne modulen.';
+$_lang["module_exec_not_found"] = 'Fann ingen modul med id %s.';
+$_lang["module_exec_disabled"] = 'Denne modulen er deaktivert og kan ikkje køyrast.';
$_lang["module_group_access_msg"] = 'Velg brukergruppene som har tilgang til å kjøre denne modulen fra inneholdshåndtereren.';
$_lang["module_management"] = 'Håndtere moduler';
$_lang["module_management_msg"] = 'Her kan du velge modulen du vil kjøre eller endre. Klikk på ikonet i tabellen for å kjøre modulen. Klikk på modulnavnet for å redigere modulen.';
@@ -1216,6 +1219,10 @@
$_lang["check_none"] = "Select none";
$_lang["check_toggle"] = "Toggle selection";
+$_lang["role_modules_tab"] = 'Modular';
+$_lang["role_modules_msg"] = 'Modular som denne rolla har lov til å køyre. Ein modul som ingen har avgrensa, er tilgjengeleg for alle roller — å fjerne haken her er det som avgrensar han.';
+$_lang["role_modules_admin_msg"] = 'Administratorrolla kan køyre alle modular.';
+$_lang["role_modules_none"] = 'Ingen modular er installerte enno.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/pl/global.php b/core/lang/pl/global.php
index fe8d9d5fb5..36aa7c8709 100644
--- a/core/lang/pl/global.php
+++ b/core/lang/pl/global.php
@@ -544,6 +544,9 @@
$_lang["module_desc"] = 'Opis';
$_lang["module_disabled"] = 'Moduł wyłączony';
$_lang["module_edit_click_title"] = 'Kliknij tutaj aby edytować moduł';
+$_lang["module_exec_no_privileges"] = 'Nie masz wystarczających uprawnień, aby uruchomić ten moduł.';
+$_lang["module_exec_not_found"] = 'Nie znaleziono modułu o id %s.';
+$_lang["module_exec_disabled"] = 'Ten moduł jest wyłączony i nie może zostać uruchomiony.';
$_lang["module_group_access_msg"] = 'Wybierz grupy użytkowników, które mogą uruchamiać ten moduł z panelu administracji.';
$_lang["module_management"] = 'Zarządzenie modułami';
$_lang["module_management_msg"] = 'Tutaj możesz wybrać moduł, który chcesz wykonać lub zmodyfikować. Aby uruchomić moduł kliknij ikonę na siatce. Aby zmodyfikować moduł kliknij w jego nazwę.';
@@ -1482,6 +1485,10 @@
$_lang['manager_menu_position_left'] = 'Lewa';
$_lang['invalid_event_response'] = 'Zdarzenie %s ma nieprawidłowy wynik';
+$_lang["role_modules_tab"] = 'Moduły';
+$_lang["role_modules_msg"] = 'Moduły, które ta rola może uruchamiać. Moduł, którego nikt nie ograniczył, pozostaje dostępny dla wszystkich ról — odznaczenie go tutaj wprowadza ograniczenie.';
+$_lang["role_modules_admin_msg"] = 'Rola administratora może uruchomić każdy moduł.';
+$_lang["role_modules_none"] = 'Nie zainstalowano jeszcze żadnych modułów.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/pt/global.php b/core/lang/pt/global.php
index 4e6321a038..b3b277ec34 100644
--- a/core/lang/pt/global.php
+++ b/core/lang/pt/global.php
@@ -504,6 +504,9 @@
$_lang["module_desc"] = 'Descrição';
$_lang["module_disabled"] = 'Módulo desactivado';
$_lang["module_edit_click_title"] = 'Clique aqui para editar este Módulo';
+$_lang["module_exec_no_privileges"] = 'Não tem privilégios suficientes para executar este módulo.';
+$_lang["module_exec_not_found"] = 'Nenhum módulo encontrado para o id %s.';
+$_lang["module_exec_disabled"] = 'Este módulo está desativado e não pode ser executado.';
$_lang["module_group_access_msg"] = 'Seleccione os grupos de utilizadores que estão autorizados a executar este Módulo a partir do Gestor de Conteúdos.';
$_lang["module_management"] = 'Gerir Módulos';
$_lang["module_management_msg"] = 'Aqui pode escolher qual o módulo que deseja executar ou modificar. Para aceder às opções do módulo, clique no ícone ao lado do nome.';
@@ -1249,6 +1252,10 @@
$_lang["check_none"] = "Select none";
$_lang["check_toggle"] = "Toggle selection";
+$_lang["role_modules_tab"] = 'Módulos';
+$_lang["role_modules_msg"] = 'Módulos que este perfil pode executar. Um módulo que ninguém restringiu continua disponível para todos os perfis — desmarcá-lo aqui é o que o restringe.';
+$_lang["role_modules_admin_msg"] = 'O perfil de administrador pode executar qualquer módulo.';
+$_lang["role_modules_none"] = 'Ainda não há módulos instalados.';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/lang/ru/global.php b/core/lang/ru/global.php
index 3b25b96197..e1e8209722 100644
--- a/core/lang/ru/global.php
+++ b/core/lang/ru/global.php
@@ -559,6 +559,9 @@
$_lang["module_desc"] = 'Описание';
$_lang["module_disabled"] = 'Модуль отключен';
$_lang["module_edit_click_title"] = 'Нажмите для редактирования модуля';
+$_lang["module_exec_no_privileges"] = 'У вас недостаточно прав для запуска этого модуля.';
+$_lang["module_exec_not_found"] = 'Модуль с id %s не найден.';
+$_lang["module_exec_disabled"] = 'Этот модуль отключён и не может быть запущен.';
$_lang["module_group_access_msg"] = 'Выберите группы пользователей, которые могут запускать этот модуль.';
$_lang["module_management"] = 'Модули';
$_lang["module_management_msg"] = 'Модуль - это приложение, основанное на архитектуре Evolution CMS и расширяющее возможности системы управления. Модуль может группировать набор элементов (сниппеты, чанки, данные), реализуя принцип инкапсуляции через разделение интерфейса и реализации.
Выберите модуль, который вы хотите запустить или изменить, или создайте новый модуль. Для запуска модуля нажмите на значок рядом с его именем, для редактирования модуля нажмите на его название.'; @@ -1537,6 +1540,10 @@ $_lang["lang_key_desc"] = 'Ключ языка из массива $_lang'; $_lang["key_desc"] = 'Ключь для проверки доступа'; +$_lang["role_modules_tab"] = 'Модули'; +$_lang["role_modules_msg"] = 'Модули, которые разрешено запускать этой роли. Модуль без ограничений доступен всем ролям — снимите галочку, чтобы ограничить его.'; +$_lang["role_modules_admin_msg"] = 'Роль администратора может запускать любые модули.'; +$_lang["role_modules_none"] = 'Модули ещё не установлены.'; $_lang["role_no_tv"] = 'Этой роли еще не присвоены параметры.'; $_lang["role_notassigned_tv"] = 'Эти параметры доступны для выбора.'; $_lang["role_tv_msg"] = 'Ниже выводятся параметры, назначенные этой роли.'; diff --git a/core/lang/sv/global.php b/core/lang/sv/global.php index 146da59e03..234cf609e0 100644 --- a/core/lang/sv/global.php +++ b/core/lang/sv/global.php @@ -505,6 +505,9 @@ $_lang["module_desc"] = 'Beskrivning'; $_lang["module_disabled"] = 'Modulen avstängd'; $_lang["module_edit_click_title"] = 'Klicka här för att redigera denna modul'; +$_lang["module_exec_no_privileges"] = 'Du har inte tillräcklig behörighet för att köra den här modulen.'; +$_lang["module_exec_not_found"] = 'Ingen modul hittades med id %s.'; +$_lang["module_exec_disabled"] = 'Den här modulen är inaktiverad och kan inte köras.'; $_lang["module_group_access_msg"] = 'Välj de användargrupper som har tillstånd att köra den här modulen innifrån innehållshanteraren.'; $_lang["module_management"] = 'Hantera moduler'; $_lang["module_management_msg"] = 'Välj den modul du vill köra eller ändra. Klicka på ikonen i tabellen för att köra modulen. Klicka på modulnamnet för att redigera modulen.'; @@ -1249,6 +1252,10 @@ $_lang["check_none"] = "Välj ingen"; $_lang["check_toggle"] = "Växla val"; +$_lang["role_modules_tab"] = 'Moduler'; +$_lang["role_modules_msg"] = 'Moduler som den här rollen får köra. En modul som ingen har begränsat är tillgänglig för alla roller — att avmarkera den här är det som begränsar den.'; +$_lang["role_modules_admin_msg"] = 'Administratörsrollen kan köra alla moduler.'; +$_lang["role_modules_none"] = 'Inga moduler är installerade ännu.'; $_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.'; $_lang["role_notassigned_tv"] = 'These Variables are available for assigning.'; $_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.'; diff --git a/core/lang/uk/global.php b/core/lang/uk/global.php index 90776756ad..60226b392d 100644 --- a/core/lang/uk/global.php +++ b/core/lang/uk/global.php @@ -564,6 +564,9 @@ $_lang["module_desc"] = 'Опис'; $_lang["module_disabled"] = 'Модуль відключений'; $_lang["module_edit_click_title"] = 'Натисніть для редагування модуля'; +$_lang["module_exec_no_privileges"] = 'У вас недостатньо прав для запуску цього модуля.'; +$_lang["module_exec_not_found"] = 'Модуль з id %s не знайдено.'; +$_lang["module_exec_disabled"] = 'Цей модуль вимкнено, і його не можна запустити.'; $_lang["module_group_access_msg"] = 'Оберіть групи користувачів, які можуть запускати цей модуль.'; $_lang["module_management"] = 'Керування модулями'; $_lang["module_management_msg"] = 'Модуль - це додаток, засноване на архітектурі EVO і розширює можливості системи управління. Модуль може групувати набір елементів (сніппети, чанки, дані), реалізуючи принцип інкапсуляції через поділ інтерфейсу і реалізації.
Оберіть модуль, який ви хочете запустити або змінити, або створіть новий модуль. Для запуску модуля натисніть на значок поруч з його ім\'ям, для редагування модуля натисніть на його назву.';
@@ -1537,6 +1540,10 @@
$_lang['invalid_event_response'] = 'Подія %s повернула невірний результат';
$_lang['chunk_processor'] = 'Клас обробки чанків';
+$_lang["role_modules_tab"] = 'Модулі';
+$_lang["role_modules_msg"] = 'Модулі, які дозволено запускати цій ролі. Модуль без обмежень доступний усім ролям — зніміть позначку, щоб обмежити його.';
+$_lang["role_modules_admin_msg"] = 'Роль адміністратора може запускати будь-які модулі.';
+$_lang["role_modules_none"] = 'Модулі ще не встановлено.';
$_lang["role_no_tv"] = 'Цій ролі ще не призначено жодної змінної.';
$_lang["role_notassigned_tv"] = 'Ці змінні доступні для призначення.';
$_lang["role_tv_msg"] = 'Змінні, призначені цій ролі, перераховані нижче.';
diff --git a/core/lang/zh/global.php b/core/lang/zh/global.php
index aeede52a16..cf4fa7c36d 100644
--- a/core/lang/zh/global.php
+++ b/core/lang/zh/global.php
@@ -505,6 +505,9 @@
$_lang["module_desc"] = '描述';
$_lang["module_disabled"] = '禁用模块';
$_lang["module_edit_click_title"] = '点这里运行模块';
+$_lang["module_exec_no_privileges"] = '您没有足够的权限运行此模块。';
+$_lang["module_exec_not_found"] = '未找到 id 为 %s 的模块。';
+$_lang["module_exec_disabled"] = '此模块已禁用,无法运行。';
$_lang["module_group_access_msg"] = '选择可以在后台执行模块的用户组.';
$_lang["module_management"] = '管理模块';
$_lang["module_management_msg"] = '在这里你可以选择你希望运行或编辑的模块,要运行模块的话,就在他的图标上点击,如果你要修改编辑的话,就点击他的名字。';
@@ -1250,6 +1253,10 @@
$_lang["check_none"] = "Select none";
$_lang["check_toggle"] = "Toggle selection";
+$_lang["role_modules_tab"] = '模块';
+$_lang["role_modules_msg"] = '此角色可以运行的模块。没有被任何人限制的模块对所有角色都可用——在此取消勾选即会对其加以限制。';
+$_lang["role_modules_admin_msg"] = '管理员角色可以运行所有模块。';
+$_lang["role_modules_none"] = '尚未安装任何模块。';
$_lang["role_no_tv"] = 'No Variables have been assigned to this Role yet.';
$_lang["role_notassigned_tv"] = 'These Variables are available for assigning.';
$_lang["role_tv_msg"] = 'The Variables assigned to this Role are listed below.';
diff --git a/core/src/Console/SiteUpdateCommand.php b/core/src/Console/SiteUpdateCommand.php
index 32d1b5f978..7957adde49 100644
--- a/core/src/Console/SiteUpdateCommand.php
+++ b/core/src/Console/SiteUpdateCommand.php
@@ -2,6 +2,7 @@
use EvolutionCMS\Models\Category;
use EvolutionCMS\Models\SiteModule;
+use EvolutionCMS\Models\SiteModuleRole;
use EvolutionCMS\Services\ComposerVersionSynchronizer;
use Illuminate\Console\Command;
@@ -402,7 +403,7 @@ protected function updateBundledExtrasModule(): void
return;
}
- SiteModule::query()->create([
+ $created = SiteModule::query()->create([
'name' => 'Extras',
'description' => $description,
'modulecode' => $moduleCode,
@@ -411,6 +412,9 @@ protected function updateBundledExtrasModule(): void
'enable_sharedparams' => (int) ($params['shareparams'] ?? 0),
'category' => $categoryId,
]);
+
+ // a re-created Extras module keeps the shipped default: admins only
+ SiteModuleRole::applyDefaultsFor((int) $created->getKey(), 'Extras');
}
/**
diff --git a/core/src/Controllers/Frame.php b/core/src/Controllers/Frame.php
index f3926f6beb..73f7d0eaed 100644
--- a/core/src/Controllers/Frame.php
+++ b/core/src/Controllers/Frame.php
@@ -3,6 +3,7 @@
use EvolutionCMS\Interfaces\ManagerTheme;
use EvolutionCMS\Interfaces\ManagerThemeInterface;
use EvolutionCMS\Models\SiteModule;
+use EvolutionCMS\Support\ModuleAccess;
class Frame extends AbstractController implements ManagerTheme\PageControllerInterface
{
@@ -685,18 +686,17 @@ protected function menuNewModule()
protected function menuRunModules()
{
if ($this->managerTheme->getCore()->hasPermission('exec_module')) {
- if ($_SESSION['mgrRole'] != 1 && $this->managerTheme->getCore()->getConfig('use_udperms') === true) {
- $modules = SiteModule::select('site_modules.id', 'site_modules.name', 'site_modules.icon', 'member_groups.member')
- ->withoutProtected()
- ->lockedView()
- ->where('site_modules.disabled', 0)
- ->orderBy('site_modules.name')->get()->toArray();
-
- } else {
- $modules = SiteModule::where('disabled', '!=', 1)->orderBy('name')->get()->toArray();
- }
+ // withoutProtected() applies both ACL axes and is a no-op for the
+ // admin role, so the menu lists exactly what the user may run
+ $modules = SiteModule::select('site_modules.id', 'site_modules.name', 'site_modules.icon')
+ ->withoutProtected()
+ ->lockedView()
+ ->where('site_modules.disabled', '!=', 1)
+ ->orderBy('site_modules.name')->get()->toArray();
+ $entries = 0;
if (count($modules) > 0) {
foreach ($modules as $row) {
+ $entries++;
$this->sitemenu['module' . $row['id']] = [
'module' . $row['id'],
'modules',
@@ -712,11 +712,17 @@ protected function menuRunModules()
];
}
}
+ $mgrRole = (int) get_by_key($_SESSION, 'mgrRole', 0);
foreach ($this->managerTheme->getCore()->modulesFromFile as $module) {
if (!empty($module['properties']['hidden'])) {
continue;
}
+ if (!ModuleAccess::canRunFileModule($mgrRole, $module)) {
+ continue;
+ }
+
+ $entries++;
$this->sitemenu['module' . $module['id']] = [
'module' . $module['id'],
'modules',
@@ -731,6 +737,13 @@ protected function menuRunModules()
''
];
}
+
+ // Nothing this user may run: drop the parent node menuModules()
+ // added before the list was known, instead of rendering an empty
+ // Modules menu. Free - the listing above has already happened.
+ if ($entries === 0) {
+ unset($this->sitemenu['modules']);
+ }
}
return $this;
diff --git a/core/src/Controllers/Modules.php b/core/src/Controllers/Modules.php
index ed7165f059..22814f0c51 100644
--- a/core/src/Controllers/Modules.php
+++ b/core/src/Controllers/Modules.php
@@ -99,7 +99,13 @@ protected function getModules() : Collection
*/
protected function getCategories(): Collection
{
- return Category::with('modules')
+ // the eager load is constrained too, otherwise a category the user can
+ // see would list the modules inside it that they may not run
+ $roleId = (int) get_by_key($_SESSION, 'mgrRole', 0);
+
+ return Category::with(['modules' => function ($builder) use ($roleId) {
+ return $builder->allowedForRole($roleId);
+ }])
->whereHas('modules', function (Builder $builder) {
return $builder->withoutProtected()->lockedView();
})
diff --git a/core/src/Controllers/Resources/Modules.php b/core/src/Controllers/Resources/Modules.php
index bc2882af26..cb386d1c4c 100644
--- a/core/src/Controllers/Resources/Modules.php
+++ b/core/src/Controllers/Resources/Modules.php
@@ -67,17 +67,25 @@ public function getParameters(array $params = []) : array
protected function parameterOutCategory() : Collection
{
- return Models\SiteModule::where('category', '=', 0)
- ->orderBy('name', 'ASC')
+ return Models\SiteModule::query()
+ ->where('site_modules.category', '=', 0)
+ ->withoutProtected()
+ ->orderBy('site_modules.name', 'ASC')
->lockedView()
->get();
}
protected function parameterCategories() : Collection
{
- return Models\Category::with('modules')
- ->whereHas('modules', function (Eloquent\Builder $builder) {
- return $builder->lockedView();
+ // the eager load is constrained too, otherwise a category the user can
+ // see would list the modules inside it that they may not run
+ $roleId = (int) get_by_key($_SESSION, 'mgrRole', 0);
+
+ return Models\Category::with(['modules' => function ($builder) use ($roleId) {
+ return $builder->allowedForRole($roleId);
+ }])
+ ->whereHas('modules', function (Eloquent\Builder $builder) use ($roleId) {
+ return $builder->lockedView()->allowedForRole($roleId);
})->orderBy('rank', 'ASC')
->get();
}
diff --git a/core/src/Controllers/UserRoles/UserRole.php b/core/src/Controllers/UserRoles/UserRole.php
index 8938c87a0a..b258df1709 100644
--- a/core/src/Controllers/UserRoles/UserRole.php
+++ b/core/src/Controllers/UserRoles/UserRole.php
@@ -2,6 +2,7 @@
use EvolutionCMS\Controllers\AbstractController;
use EvolutionCMS\Models;
+use EvolutionCMS\Support\ModuleAccess;
use EvolutionCMS\Interfaces\ManagerTheme;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent;
@@ -37,6 +38,7 @@ public function process(): bool
if(isset($_GET['action']) && $_GET['action'] == 'delete' ){
$id = $this->getElementId();
Models\RolePermissions::query()->where('role_id', $id)->delete();
+ Models\SiteModuleRole::query()->where('role', $id)->delete();
Models\UserRoleVar::where('roleid', $id)->delete();
Models\UserRole::query()->where('id', $id)->delete();
header('Location: index.php?a=86&tab=0');
@@ -80,6 +82,11 @@ public function updateOrCreate()
Models\RolePermissions::create(['role_id' => $role->getKey(), 'permission' => $key]);
}
+ $this->saveModuleAccess(
+ (int) $role->getKey(),
+ is_array($_POST['modules'] ?? null) ? array_keys($_POST['modules']) : []
+ );
+
if ($_POST['tvsDirty'] == 1) {
// Preserve rankings of already assigned TVs
$exists = Models\UserRoleVar::where('roleid', $role->id)->get()->toArray();
@@ -114,6 +121,101 @@ public function updateOrCreate()
header('Location: index.php?a=35&id=' . $role->getKey() . '&r=9');
}
+ /**
+ * Store which modules this role may run.
+ *
+ * A module with no rows in site_module_roles is unrestricted, so allowing
+ * such a module means writing nothing at all. Denying one has to make it
+ * restricted first, by listing every other role. A module always keeps at
+ * least the admin role, otherwise dropping the last row would silently
+ * turn it back into an unrestricted module.
+ *
+ * @param int $roleId
+ * @param array $allowedModuleIds Module ids ticked on the role form.
+ * @return void
+ */
+ protected function saveModuleAccess(int $roleId, array $allowedModuleIds): void
+ {
+ // the admin role bypasses the ACL, and the form renders read-only for it
+ if ($roleId <= 0 || $roleId === ModuleAccess::ADMIN_ROLE) {
+ return;
+ }
+
+ if (!$this->managerTheme->getCore()->hasPermission('manage_module_permissions')) {
+ return;
+ }
+
+ $allowed = ModuleAccess::normalizeRoleIds($allowedModuleIds);
+ $restricted = Models\SiteModuleRole::query()->get()->groupBy('module');
+ $allRoleIds = Models\UserRole::query()->pluck('id')->all();
+
+ foreach (Models\SiteModule::query()->pluck('id') as $moduleId) {
+ $moduleId = (int) $moduleId;
+ $isRestricted = isset($restricted[$moduleId]);
+
+ if (in_array($moduleId, $allowed, true)) {
+ if ($isRestricted) {
+ Models\SiteModuleRole::query()->firstOrCreate([
+ 'module' => $moduleId,
+ 'role' => $roleId,
+ ]);
+ }
+
+ continue;
+ }
+
+ if (!$isRestricted) {
+ foreach (ModuleAccess::normalizeRoleIds($allRoleIds) as $otherRoleId) {
+ if ($otherRoleId === $roleId) {
+ continue;
+ }
+
+ Models\SiteModuleRole::query()->firstOrCreate([
+ 'module' => $moduleId,
+ 'role' => $otherRoleId,
+ ]);
+ }
+
+ continue;
+ }
+
+ Models\SiteModuleRole::query()
+ ->where('module', $moduleId)
+ ->where('role', $roleId)
+ ->delete();
+
+ $remaining = Models\SiteModuleRole::query()->where('module', $moduleId)->count();
+ if ($remaining === 0) {
+ Models\SiteModuleRole::query()->firstOrCreate([
+ 'module' => $moduleId,
+ 'role' => ModuleAccess::ADMIN_ROLE,
+ ]);
+ }
+ }
+ }
+
+ /**
+ * Modules the role may run, keyed by module id.
+ *
+ * Unrestricted modules read as allowed: unticking one is what restricts it.
+ *
+ * @param int $roleId
+ * @return array {{ $isAdminRole
+ ? ManagerTheme::getLexicon('role_modules_admin_msg', 'The administrator role can run every module.')
+ : ManagerTheme::getLexicon('role_modules_msg', 'Modules this role is allowed to run.') }}
Please use the EVO Content Manager instead of accessing this file directly.");
@@ -11,35 +12,60 @@
if (isset($_GET['id'])) {
if (is_numeric($_GET['id'])) {
$id = (int)$_GET['id'];
- } else {
+ } elseif (is_string($_GET['id'])) {
+ // the key of a module registered from a file
$id = $_GET['id'];
+ } else {
+ // ?id[]=... and friends: not an id at all
+ $modx->webAlertAndQuit($_lang["error_no_id"]);
}
} else {
$modx->webAlertAndQuit($_lang["error_no_id"]);
}
-// check if user has access permission, except admins
-if ($_SESSION['mgrRole'] != 1 && is_numeric($id)) {
- $moduleAccess = SiteModule::query()
- ->withoutProtected()
- ->where('site_modules.id', $id)
- ->first();
-
- if (empty($moduleAccess)) {
- $modx->webAlertAndQuit("You do not sufficient privileges to execute this module.", "index.php?a=76&tab=5");
- }
-}
+$mgrRole = (int)get_by_key($_SESSION, 'mgrRole', 0);
+
if (is_numeric($id)) {
+ // check if user has access permission, except admins
+ if ($mgrRole !== ModuleAccess::ADMIN_ROLE) {
+ $moduleAccess = SiteModule::query()
+ ->select('site_modules.id')
+ ->withoutProtected()
+ ->where('site_modules.id', $id)
+ ->first();
+
+ if (empty($moduleAccess)) {
+ $modx->webAlertAndQuit($_lang["module_exec_no_privileges"], "index.php?a=76&tab=5");
+ }
+ }
+
// get module data
$content = \EvolutionCMS\Models\SiteModule::find($id);
if (is_null($content)) {
- $modx->webAlertAndQuit("No record found for id {$id}.", "index.php?a=76&tab=5");
+ $modx->webAlertAndQuit(sprintf($_lang["module_exec_not_found"], e($id)), "index.php?a=76&tab=5");
}
$content = $content->toArray();
if ($content['disabled']) {
- $modx->webAlertAndQuit("This module is disabled and cannot be executed.", "index.php?a=76&tab=5");
+ $modx->webAlertAndQuit($_lang["module_exec_disabled"], "index.php?a=76&tab=5");
}
} else {
+ // file based modules: the id is a registry key, never a path - reject
+ // anything that is not registered instead of reading an arbitrary file
+ if (!is_string($id) || !isset($modx->modulesFromFile[$id])) {
+ $modx->webAlertAndQuit(sprintf($_lang["module_exec_not_found"], e($id)), "index.php?a=76&tab=5");
+ }
+
$content = $modx->modulesFromFile[$id];
+
+ // file based modules carry their role restriction in the registration
+ // params; the group ACL cannot apply because they have no database row
+ if (!ModuleAccess::canRunFileModule($mgrRole, $content)) {
+ $modx->webAlertAndQuit($_lang["module_exec_no_privileges"], "index.php?a=76&tab=5");
+ }
+
+ if (!is_file($content['file']) || !is_readable($content['file'])) {
+ $modx->webAlertAndQuit(sprintf($_lang["module_exec_not_found"], e($id)), "index.php?a=76&tab=5");
+ }
+
$content['modulecode'] = file_get_contents($content['file']);
$content["guid"] = '';
}
diff --git a/manager/views/page/user_roles/user_role.blade.php b/manager/views/page/user_roles/user_role.blade.php
index 343c30b729..599c239be1 100644
--- a/manager/views/page/user_roles/user_role.blade.php
+++ b/manager/views/page/user_roles/user_role.blade.php
@@ -82,6 +82,45 @@
+ @if($canManageModuleAccess)
+ {{ ManagerTheme::getLexicon('role_modules_tab', 'Modules') }}
+
+
+
+ {{ ManagerTheme::getLexicon('template_assignedtv_tab') }}