diff --git a/classes/install/PKPInstall.php b/classes/install/PKPInstall.php index eb39b49e6c0..76a9c0dc2d3 100644 --- a/classes/install/PKPInstall.php +++ b/classes/install/PKPInstall.php @@ -235,6 +235,7 @@ public function createData() $adminUserGroup = Repo::userGroup()->newDataObject(); $adminUserGroup->setRoleId(Role::ROLE_ID_SITE_ADMIN); $adminUserGroup->setContextId(\PKP\core\PKPApplication::SITE_CONTEXT_ID); + $adminUserGroup->setPermitSettings(true); $adminUserGroup->setDefault(true); foreach ($this->installedLocales as $locale) { $name = __('default.groups.name.siteAdmin', [], $locale); diff --git a/classes/migration/install/RolesAndUserGroupsMigration.php b/classes/migration/install/RolesAndUserGroupsMigration.php index d8c98f8e4d3..95395ffc02a 100644 --- a/classes/migration/install/RolesAndUserGroupsMigration.php +++ b/classes/migration/install/RolesAndUserGroupsMigration.php @@ -36,6 +36,7 @@ public function up(): void $table->smallInteger('show_title')->default(1); $table->smallInteger('permit_self_registration')->default(0); $table->smallInteger('permit_metadata_edit')->default(0); + $table->smallInteger('permit_settings')->default(0); $table->smallInteger('masthead')->default(0); $table->index(['user_group_id'], 'user_groups_user_group_id'); $table->index(['role_id'], 'user_groups_role_id'); diff --git a/classes/migration/upgrade/v3_5_0/I5504_UserGroupsSettings.php b/classes/migration/upgrade/v3_5_0/I5504_UserGroupsSettings.php new file mode 100644 index 00000000000..87ce66551c9 --- /dev/null +++ b/classes/migration/upgrade/v3_5_0/I5504_UserGroupsSettings.php @@ -0,0 +1,47 @@ +smallInteger('permit_settings')->default(0); + }); + DB::table('user_groups')->where('role_id', 1)->update(['permit_settings' => 1]); // role_id = 1 is ROLE_ID_SITE_ADMIN + DB::table('user_groups')->where('role_id', 16)->update(['permit_settings' => 1]); // role_id = 16 is ROLE_ID_MANAGER + } + + /** + * Reverse the downgrades + */ + public function down(): void + { + Schema::table('user_groups', function (Blueprint $table) { + if (Schema::hasColumn($table->getTable(), 'permit_settings')) { + $table->dropColumn('permit_settings'); + }; + }); + } +} diff --git a/classes/security/authorization/UserRolesRequiredPolicy.php b/classes/security/authorization/UserRolesRequiredPolicy.php index 61cba807689..8f5acf5e032 100644 --- a/classes/security/authorization/UserRolesRequiredPolicy.php +++ b/classes/security/authorization/UserRolesRequiredPolicy.php @@ -60,6 +60,7 @@ public function effect() $roleIds = array_map(fn ($userGroup) => $userGroup->getRoleId(), $userGroups); $this->addAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES, $roleIds); + $this->addAuthorizedContextObject(Application::ASSOC_TYPE_USER_GROUP, $userGroups); return AuthorizationPolicy::AUTHORIZATION_PERMIT; } diff --git a/classes/template/PKPTemplateManager.php b/classes/template/PKPTemplateManager.php index e6ae0c3f01e..b5eac3350f1 100644 --- a/classes/template/PKPTemplateManager.php +++ b/classes/template/PKPTemplateManager.php @@ -1046,36 +1046,40 @@ public function setupBackendPage() 'isCurrent' => $request->getRequestedPage() === 'management' && in_array('institutions', (array) $request->getRequestedArgs()), ]; } - $menu['settings'] = [ - 'name' => __('navigation.settings'), - 'submenu' => [ - 'context' => [ - 'name' => __('context.context'), - 'url' => $router->url($request, null, 'management', 'settings', ['context']), - 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('context', (array) $router->getRequestedArgs($request)), - ], - 'website' => [ - 'name' => __('manager.website'), - 'url' => $router->url($request, null, 'management', 'settings', ['website']), - 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('website', (array) $router->getRequestedArgs($request)), - ], - 'workflow' => [ - 'name' => __('manager.workflow'), - 'url' => $router->url($request, null, 'management', 'settings', ['workflow']), - 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('workflow', (array) $router->getRequestedArgs($request)), - ], - 'distribution' => [ - 'name' => __('manager.distribution'), - 'url' => $router->url($request, null, 'management', 'settings', ['distribution']), - 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('distribution', (array) $router->getRequestedArgs($request)), - ], - 'access' => [ - 'name' => __('navigation.access'), - 'url' => $router->url($request, null, 'management', 'settings', ['access']), - 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('access', (array) $router->getRequestedArgs($request)), + $userGroups = (array) $router->getHandler()->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_GROUP); + $hasSettingsAccess = array_reduce($userGroups, fn ($carry, $userGroup) => $carry || $userGroup->getPermitSettings(), false); + if ($hasSettingsAccess) { + $menu['settings'] = [ + 'name' => __('navigation.settings'), + 'submenu' => [ + 'context' => [ + 'name' => __('context.context'), + 'url' => $router->url($request, null, 'management', 'settings', ['context']), + 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('context', (array) $router->getRequestedArgs($request)), + ], + 'website' => [ + 'name' => __('manager.website'), + 'url' => $router->url($request, null, 'management', 'settings', ['website']), + 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('website', (array) $router->getRequestedArgs($request)), + ], + 'workflow' => [ + 'name' => __('manager.workflow'), + 'url' => $router->url($request, null, 'management', 'settings', ['workflow']), + 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('workflow', (array) $router->getRequestedArgs($request)), + ], + 'distribution' => [ + 'name' => __('manager.distribution'), + 'url' => $router->url($request, null, 'management', 'settings', ['distribution']), + 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('distribution', (array) $router->getRequestedArgs($request)), + ], + 'access' => [ + 'name' => __('navigation.access'), + 'url' => $router->url($request, null, 'management', 'settings', ['access']), + 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('access', (array) $router->getRequestedArgs($request)), + ] ] - ] - ]; + ]; + } } if (count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR], $userRoles))) { diff --git a/classes/userGroup/DAO.php b/classes/userGroup/DAO.php index dcf670c6efd..639f2a6c7c6 100644 --- a/classes/userGroup/DAO.php +++ b/classes/userGroup/DAO.php @@ -23,7 +23,6 @@ use Illuminate\Support\LazyCollection; use PKP\core\Core; use PKP\core\EntityDAO; -use PKP\core\PKPApplication; use PKP\core\traits\EntityWithParent; use PKP\services\PKPSchemaService; @@ -57,6 +56,7 @@ class DAO extends EntityDAO 'showTitle' => 'show_title', 'permitSelfRegistration' => 'permit_self_registration', 'permitMetadataEdit' => 'permit_metadata_edit', + 'permitSettings' => 'permit_settings', 'masthead' => 'masthead', ]; diff --git a/classes/userGroup/Repository.php b/classes/userGroup/Repository.php index dcaf172669d..e3c9658b6a6 100644 --- a/classes/userGroup/Repository.php +++ b/classes/userGroup/Repository.php @@ -500,7 +500,6 @@ public function getFirstSubmitAsAuthorUserGroup(int $contextId): ?UserGroup /** * Load the XML file and move the settings to the DB * - * @param int $contextId * @param string $filename * * @return bool true === success @@ -524,11 +523,13 @@ public function installSettings(?int $contextId, $filename) $abbrevKey = $setting->getAttribute('abbrev'); $permitSelfRegistration = $setting->getAttribute('permitSelfRegistration'); $permitMetadataEdit = $setting->getAttribute('permitMetadataEdit'); + $permitSettings = $setting->getAttribute('permitSettings'); $masthead = $setting->getAttribute('masthead'); // If has manager role then permitMetadataEdit can't be overridden if (in_array($roleId, [Role::ROLE_ID_MANAGER])) { $permitMetadataEdit = $setting->getAttribute('permitMetadataEdit'); + $permitSettings = $setting->getAttribute('permitSettings'); } $defaultStages = explode(',', (string) $setting->getAttribute('stages')); @@ -539,6 +540,7 @@ public function installSettings(?int $contextId, $filename) $userGroup->setContextId($contextId); $userGroup->setPermitSelfRegistration($permitSelfRegistration ?? false); $userGroup->setPermitMetadataEdit($permitMetadataEdit ?? false); + $userGroup->setPermitSettings($permitSettings ?? false); $userGroup->setDefault(true); $userGroup->setShowTitle(true); $userGroup->setMasthead($masthead ?? false); diff --git a/classes/userGroup/UserGroup.php b/classes/userGroup/UserGroup.php index 404180d2c98..53b0f9555ed 100644 --- a/classes/userGroup/UserGroup.php +++ b/classes/userGroup/UserGroup.php @@ -16,8 +16,6 @@ namespace PKP\userGroup; -use PKP\core\PKPApplication; - class UserGroup extends \PKP\core\DataObject { /** @@ -243,6 +241,24 @@ public function setPermitMetadataEdit(bool $permitMetadataEdit) $this->setData('permitMetadataEdit', $permitMetadataEdit); } + /** + * Getter for permitSettings attribute. + * + * @return bool + */ + public function getPermitSettings() + { + return $this->getData('permitSettings'); + } + + /** + * Setter for permitSettings attribute. + */ + public function setPermitSettings(bool $permitSettings) + { + $this->setData('permitSettings', $permitSettings); + } + /** * Get the masthead flag */ diff --git a/controllers/grid/settings/roles/form/UserGroupForm.php b/controllers/grid/settings/roles/form/UserGroupForm.php index 3a9333ee0a8..9ccd778e3a8 100644 --- a/controllers/grid/settings/roles/form/UserGroupForm.php +++ b/controllers/grid/settings/roles/form/UserGroupForm.php @@ -122,6 +122,7 @@ public function initData() 'showTitle' => $userGroup->getShowTitle(), 'permitSelfRegistration' => $userGroup->getPermitSelfRegistration(), 'permitMetadataEdit' => $userGroup->getPermitMetadataEdit(), + 'permitSettings' => $userGroup->getPermitSettings(), 'recommendOnly' => $userGroup->getRecommendOnly(), 'masthead' => $userGroup->getMasthead(), ]; @@ -137,7 +138,7 @@ public function initData() */ public function readInputData() { - $this->readUserVars(['roleId', 'name', 'abbrev', 'assignedStages', 'showTitle', 'permitSelfRegistration', 'recommendOnly', 'permitMetadataEdit', 'masthead']); + $this->readUserVars(['roleId', 'name', 'abbrev', 'assignedStages', 'showTitle', 'permitSelfRegistration', 'recommendOnly', 'permitMetadataEdit', 'permitSettings', 'masthead']); } /** @@ -157,6 +158,7 @@ public function fetch($request, $template = null, $display = false) $disableRoleSelect = ($this->getUserGroupId() > 0) ? true : false; $templateMgr->assign('disableRoleSelect', $disableRoleSelect); $templateMgr->assign('selfRegistrationRoleIds', $this->getPermitSelfRegistrationRoles()); + $templateMgr->assign('permitSettingsRoleIds', $this->getPermitSettingsRoles()); $templateMgr->assign('recommendOnlyRoleIds', $this->getRecommendOnlyRoles()); $templateMgr->assign('notChangeMetadataEditPermissionRoles', Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES); @@ -165,20 +167,24 @@ public function fetch($request, $template = null, $display = false) /** * Get a list of roles optionally permitting user self-registration. - * - * @return array */ - public function getPermitSelfRegistrationRoles() + public function getPermitSelfRegistrationRoles(): array { return [Role::ROLE_ID_REVIEWER, Role::ROLE_ID_AUTHOR, Role::ROLE_ID_READER]; } + /** + * Get a list of roles optionally permitting settings access. + */ + public function getPermitSettingsRoles(): array + { + return [Role::ROLE_ID_MANAGER]; + } + /** * Get a list of roles optionally permitting recommendOnly option. - * - * @return array */ - public function getRecommendOnlyRoles() + public function getRecommendOnlyRoles(): array { return [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]; } @@ -210,6 +216,7 @@ public function execute(...$functionParams) $userGroup->setShowTitle(is_null($this->getData('showTitle')) ? false : $this->getData('showTitle')); $userGroup->setPermitSelfRegistration($this->getData('permitSelfRegistration') && in_array($userGroup->getRoleId(), $this->getPermitSelfRegistrationRoles())); $userGroup->setPermitMetadataEdit($this->getData('permitMetadataEdit') && !in_array($this->getData('roleId'), Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)); + $userGroup->setPermitSettings($this->getData('permitSettings') && $userGroup->getRoleId() == Role::ROLE_ID_MANAGER); if (in_array($this->getData('roleId'), Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)) { $userGroup->setPermitMetadataEdit(true); } @@ -224,6 +231,7 @@ public function execute(...$functionParams) $userGroup->setShowTitle(is_null($this->getData('showTitle')) ? false : $this->getData('showTitle')); $userGroup->setPermitSelfRegistration($this->getData('permitSelfRegistration') && in_array($userGroup->getRoleId(), $this->getPermitSelfRegistrationRoles())); $userGroup->setPermitMetadataEdit($this->getData('permitMetadataEdit') && !in_array($userGroup->getRoleId(), Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)); + $userGroup->setPermitSettings($this->getData('permitSettings') && $userGroup->getRoleId() == Role::ROLE_ID_MANAGER); if (in_array($userGroup->getRoleId(), Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)) { $userGroup->setPermitMetadataEdit(true); } else { diff --git a/js/controllers/grid/settings/roles/form/UserGroupFormHandler.js b/js/controllers/grid/settings/roles/form/UserGroupFormHandler.js index 8ed9adce959..24004908ba2 100644 --- a/js/controllers/grid/settings/roles/form/UserGroupFormHandler.js +++ b/js/controllers/grid/settings/roles/form/UserGroupFormHandler.js @@ -52,6 +52,12 @@ this.recommendOnlyRoleIds_ = options.recommendOnlyRoleIds; } + // Set the role IDs for which the permitSettings checkbox + // is relevant. + if (options.permitSettingsRoleIds) { + this.permitSettingsRoleIds_ = options.permitSettingsRoleIds; + } + // Set the roles that are not able to change // submission metadata edit perissions if (options.notChangeMetadataEditPermissionRoles) { @@ -72,6 +78,11 @@ this.updatePermitMetadataEdit( /** @type {string} */ ($roleId.val()), false); + // Initialize the "permit settings" checkbox disabled + // state based on the form's current selection + this.updatePermitSettings( + /** @type {string} */ ($roleId.val()), false); + // ...also initialize the stage options, disabling the ones // that are forbidden for the current role. this.updateStageOptions( @@ -102,6 +113,15 @@ UserGroupFormHandler.prototype.selfRegistrationRoleIds_ = null; + /** + * The list of role IDs for which settings can be permitted/restricted + * @private + * @type {Object?} + */ + $.pkp.controllers.grid.settings.roles.form. + UserGroupFormHandler.prototype.permitSettingsRoleIds_ = null; + + /** * A list of role forbidden stages. * @private @@ -143,6 +163,7 @@ this.updatePermitSelfRegistration((dropDownValue)); this.updatePermitMetadataEdit(/** @type {string} */ (dropDownValue), true); + this.updatePermitSettings(/** @type {string} */ (dropDownValue), true); // Also update the stages options. this.updateStageOptions(/** @type {string} */ (dropDownValue)); @@ -182,6 +203,35 @@ }; + /** + * Update the enabled/disabled state of the permitSettings + * checkbox. + * @param {number|string} roleId The role ID to select. + */ + $.pkp.controllers.grid.settings.roles.form.UserGroupFormHandler.prototype. + updatePermitSettings = function(roleId) { + + // JQuerify the element + var $checkbox = $('[id^="permitSettings"]'), + $form = this.getHtmlElement(), + i, + found = false; + + for (i = 0; i < this.selfRegistrationRoleIds_.length; i++) { + if (this.permitSettingsRoleIds_[i] == roleId) { + found = true; + } + } + + if (found) { + $checkbox.removeAttr('disabled'); + } else { + $checkbox.attr('disabled', 'disabled'); + $checkbox.removeAttr('checked'); + } + }; + + /** * Update the enabled/disabled state of the PermitMetadataEdit * checkbox. diff --git a/locale/en/manager.po b/locale/en/manager.po index 54e1e87cfb6..abc283ba766 100644 --- a/locale/en/manager.po +++ b/locale/en/manager.po @@ -1733,6 +1733,9 @@ msgstr "Show role title in contributor list" msgid "settings.roles.permitSelfRegistration" msgstr "Allow user self-registration" +msgid "settings.roles.permitSettings" +msgstr "Permit changes to Settings" + msgid "settings.roles.recommendOnly" msgstr "" "This role is only allowed to recommend a review decision and will require an " diff --git a/schemas/userGroup.json b/schemas/userGroup.json index ea266ea8553..cfb060ba0d8 100644 --- a/schemas/userGroup.json +++ b/schemas/userGroup.json @@ -31,6 +31,9 @@ "permitMetadataEdit": { "type": "boolean" }, + "permitSettings": { + "type": "boolean" + }, "recommendOnly": { "type": "boolean" }, diff --git a/templates/controllers/grid/settings/roles/form/userGroupForm.tpl b/templates/controllers/grid/settings/roles/form/userGroupForm.tpl index 0a3cc201d9d..b8148ef6a4c 100644 --- a/templates/controllers/grid/settings/roles/form/userGroupForm.tpl +++ b/templates/controllers/grid/settings/roles/form/userGroupForm.tpl @@ -14,6 +14,7 @@ $('#userGroupForm').pkpHandler( '$.pkp.controllers.grid.settings.roles.form.UserGroupFormHandler', {ldelim} selfRegistrationRoleIds: {$selfRegistrationRoleIds|@json_encode}, + permitSettingsRoleIds: {$permitSettingsRoleIds|@json_encode}, recommendOnlyRoleIds: {$recommendOnlyRoleIds|@json_encode}, roleForbiddenStagesJSON: {$roleForbiddenStagesJSON}, notChangeMetadataEditPermissionRoles: {$notChangeMetadataEditPermissionRoles|@json_encode}, @@ -57,6 +58,7 @@ {fbvElement type="checkbox" name="recommendOnly" id="recommendOnly" checked=$recommendOnly label="settings.roles.recommendOnly"} {fbvElement type="checkbox" name="permitMetadataEdit" id="permitMetadataEdit" checked=$permitMetadataEdit label="settings.roles.permitMetadataEdit"} {fbvElement type="checkbox" name="masthead" id="masthead" checked=$masthead label="settings.roles.masthead"} + {fbvElement type="checkbox" name="permitSettings" id="permitSettings" checked=$permitSettings label="settings.roles.permitSettings"} {/fbvFormSection} {/fbvFormArea}