__('Roles', __FILE__), // getModuleInfo title 'version' => 104, 'summary' => __('Manage user roles and what permissions are attached', __FILE__), // getModuleInfo summary 'permanent' => true, 'permission' => 'role-admin', // add this permission if you want this Process available for roles other than Superuser 'icon' => 'gears', 'useNavJSON' => true, ); } /** * Array of [ 'icon-name' => 'icon markup' ] * * @var array * */ protected $icons = array(); /** * Array of [ 'permission-name' => 'Additional notes for permission' ] * * @var array * */ protected $templatePermissionNotes = array(); /** * Array of [ 'permission-name' => 'Description of permission' ] * * @var array * */ protected $templatePermissionDescriptions = array(); /** * Role object for guest role * * @var Role * */ protected $guestRole; /** * Init and attach hooks * */ public function init() { parent::init(); /** @var JqueryUI $jQueryUI */ $jQueryUI = $this->wire()->modules->get('JqueryUI'); $jQueryUI->use('vex'); $this->guestRole = $this->wire()->roles->get($this->wire()->config->guestUserRolePageID); $this->addHookBefore('InputfieldForm::render', $this, 'hookFormRender'); $this->addHookBefore('ProcessPageEdit::processInput', $this, 'hookProcessInput'); $this->icons = array( 'edit' => wireIconMarkup('certificate', 'fw'), 'page' => wireIconMarkup('gear', 'fw'), 'info' => wireIconMarkup('info-circle', 'fw'), 'add' => wireIconMarkup('plus-circle', 'fw'), 'revoke' => wireIconMarkup('minus-circle', 'fw'), 'help' => wireIconMarkup('question-circle'), ); $this->templatePermissionDescriptions = array( 'page-view' => $this->_('Which types of pages may this role view?'), 'page-edit' => $this->_('Which types of pages may this role edit?'), 'page-add' => $this->_('Which types of pages may this role add children to?'), 'page-create' => $this->_('Which types of pages may this role create?'), 'default-add' => $this->_('If you want to add {permission} only to specific templates, check the boxes below for the templates you want to add it to, and leave the {permission} permission unchecked.'), 'default-revoke' => $this->_('The {permission} permission is checked, making it apply to all templates that are editable to the role. To revoke {permission} permission from specific templates, check the boxes below. To add this permission to only specific templates, un-check the {permission} permission first.'), ); $pageEditRequired = $this->_('Note that role must also have page-edit permission for any checked templates above.'); $this->templatePermissionNotes = array( 'default' => $this->_('Most permissions that can be assigned by template also require that the user have page-edit permission to the template. If a template you need is not listed, you must enable access control for it first (see “Access” tab when editing a template).'), 'page-create' => $pageEditRequired, 'page-publish' => $pageEditRequired, 'page-add' => $this->_('Unlike most other permissions, page-edit permission to a template is not a pre-requisite for this permission.'), 'page-edit' => $this->_('Templates with an asterisk (*) are configured for edit-related permissions to also inherit to children and through the page tree, unless/until overridden by a page using a different access controlled template.'), 'page-view' => $this->_('This permission is also inherited to children and through the page tree, unless/until overridden by a page using a different access controlled template.'), ); } /** * Hook ProcessPageEdit::processInput to save permission options * * @param HookEvent $event * */ public function hookProcessInput(HookEvent $event) { static $n = 0; if(!$n && $event->wire()->input->post('_pw_page_name')) { $this->savePermissionOptions(); $n++; } } /** * Hook before InputfieldForm::render to manipulate output of permissions field * * @param HookEvent $event * */ public function hookFormRender(HookEvent $event) { /** @var Inputfieldform $form */ $form = $event->object; /** @var InputfieldPage $f */ $f = $form->getChildByName('permissions'); if(!$f) return; if($this->getPage()->id == $this->wire()->config->superUserRolePageID) { $f->wrapAttr('style', 'display:none'); $fn = $form->getChildByName('_pw_page_name'); if($fn) $fn->notes = $this->_('Note: superuser role always has all permissions, so permissions field is not shown.'); } $f->entityEncodeText = false; $f->addClass('global-permission'); $f->label = $this->_('Permissions'); $f->description = $f->entityEncode( sprintf( $this->_('For detailed descriptions of these permissions, please see the [permissions reference](%s).'), // Permissions documentation info 'https://processwire.com/api/user-access/permissions/' ), true ); $f = $f->getInputfield(); /** @var InputfieldCheckboxes $f */ $f->table = true; $f->thead = $this->_('name|description| '); // Table head with each column title separated by a pipe "|" $value = $f->attr('value'); $options = $f->getOptions(); $pageViewID = 0; foreach($options as $name => $label) { $f->removeOption($name); } // establish root permission containers foreach($this->wire()->permissions as $permission) { if($permission->getParentPermission()->id) continue; $permissions[$permission->name] = array(); if($permission->name == 'page-view') $pageViewID = $permission->id; if($permission->name == 'page-edit') { $permissions[$permission->name]['page-add'] = array(); $permissions[$permission->name]['page-create'] = array(); } } ksort($permissions); $pageView = $permissions['page-view']; $pageEdit = $permissions['page-edit']; $permissions = array_merge(array('page-view' => $pageView, 'page-edit' => $pageEdit), $permissions); foreach($this->wire()->permissions as $permission) { /** @var Permission $permission */ /** @var Permission $parent */ $parent = $permission->getParentPermission(); if(!$parent->id) continue; if(isset($permissions[$parent->name])) { $permissions[$parent->name][$permission->name] = array(); } else { $grandparent = $parent->getParentPermission(); if($grandparent->id) { if(!isset($permissions[$grandparent->name][$parent->name])) { $permissions[$grandparent->name][$parent->name] = array(); } $permissions[$grandparent->name][$parent->name][$permission->name] = array(); } else { // this should not be able to occur, but here as a fallback just in case $permissions[$parent->name][$permission->name] = array(); } } } if(!in_array($pageViewID, $value)) $value[] = $pageViewID; // required $this->addPermissionOptions($permissions, $f, 0, $value); $f->attr('value', $value); } /** * Add permission options to checkboxes Inputfield * * @param array $permissions * @param Inputfield $f * @param int $level * @param $inputfieldValue * */ protected function addPermissionOptions(array $permissions, Inputfield $f, $level, &$inputfieldValue) { /** @var InputfieldCheckboxes $f */ /** @var Role $role */ $role = $this->getPage(); foreach($permissions as $name => $children) { $alert = ''; $confirm = ''; $addedTemplates = array(); $revokedTemplates = array(); $disabled = false; $checked = false; $appliesAllEditable = false; $templateCheckboxes = array(); $pageEditTemplates = array(); if($name == 'page-add' || $name == 'page-create') { $parent = $this->wire()->permissions->get('page-edit'); $rootParent = $parent; $permission = new Permission(); $permission->set('name', $name); if($name == 'page-add') { $title = $this->_('Add children to pages using template'); } else { $title = $this->_('Create pages using template'); } $alert = $this->_('This permission can only be assigned by template.'); } else { $permission = $this->wire()->permissions->get($name); if(!$permission->id) continue; $title = str_replace('|', ' ', $this->wire()->sanitizer->entities($permission->getUnformatted('title'))); $parent = $permission->getParentPermission(); $rootParent = $permission->getRootParentPermission(); $checked = in_array($permission->id, $inputfieldValue); } $title = "" . $this->icons['help'] . "" . $title; */ if(count($templateCheckboxes)) { $checkboxes = $this->renderTemplatePermissionCheckboxes($permission, $templateCheckboxes); $toggle = $this->renderTemplatePermissionToggle(); $f->addOption($value, "$name|$title$checkboxes|$toggle", $attributes); } else { $f->addOption($value, "$name|$title| ", $attributes); } if(count($children)) { $this->addPermissionOptions($children, $f, $level+1, $inputfieldValue); } } // foreach(permissions) } /** * Render a div containing template permission checkboxes * * @param Permission $permission * @param array $checkboxes Array of individually rendered checkboxes for each template * @return string * */ protected function renderTemplatePermissionCheckboxes(Permission $permission, array $checkboxes) { $name = $permission->name; if(isset($this->templatePermissionNotes[$name])) { $note = $this->templatePermissionNotes[$name]; } else { $note = $this->templatePermissionNotes['default']; } if(isset($this->templatePermissionDescriptions[$name])) { $desc = "
"; if($name == 'page-view') { $title .= $this->renderDetail($this->_('(required)')); $alert = $this->_('This permission is required for all roles.'); } else if($name == 'page-edit') { } if(($parent->name == 'page-edit' || $rootParent->name == 'page-edit') && strpos($name, 'page-') === 0) { if($name == 'page-add' || $name == 'page-create') { // $title = $title; } else { $appliesAllEditable = true; $title .= $this->renderDetail('(' . $this->_('applies to all editable templates') . ')', 'permission-all-templates'); } } foreach($this->wire()->templates as $template) { if(!$template->useRoles) continue; $rolesPermissions = $template->rolesPermissions; $templateEditURL = "../../../setup/template/edit?id=$template->id#tab_access"; $templateEditLink = $this->renderLink($templateEditURL, $this->icons['add'] . $template->name, array( 'class' => 'tooltip', 'target' => '_blank', 'title' => '{tooltip}', )); if($name == 'page-edit') { if(in_array($role->id, $template->editRoles)) { $addedTemplates[$template->name] = $templateEditLink; $pageEditTemplates[$template->name] = $template; } } else if($name == 'page-create') { if(in_array($role->id, $template->createRoles)) { $checked = true; $addedTemplates[$template->name] = $templateEditLink; } } else if($name == 'page-add') { if(in_array($role->id, $template->addRoles)) { $checked = true; $addedTemplates[$template->name] = $templateEditLink; } } else if($name == 'page-view') { if($template->hasRole($role)) { $checked = true; $addedTemplates[$template->name] = $templateEditLink; } } else if(isset($rolesPermissions[$role->id])) { // custom added or revoked permission if(in_array($permission->id, $rolesPermissions[$role->id])) { $addedTemplates[$template->name] = $templateEditLink; } else if(in_array($permission->id * -1, $rolesPermissions[$role->id])) { $revokedTemplates[$template->name] = str_replace($this->icons['add'], $this->icons['revoke'], $templateEditLink); } } // if a system template, then do nothing further if($template->flags & Template::flagSystem) continue; if(isset($this->templatePermissionDescriptions[$name]) || $appliesAllEditable) { // base permission: page-view, page-edit, page-create, page-add $checked = isset($addedTemplates[$template->name]); $templateCheckboxes[] = $this->renderTemplatePermissionCheckbox($template, $permission, $checked); } } // foreach(templates) if(count($addedTemplates) || count($revokedTemplates)) { // permission was added or revoked from specific templates /* foreach($addedTemplates as $templateName => $link) { $tooltip = sprintf($this->_('%1$s added by template %2$s, click to edit'), $name, $templateName); $addedTemplates[$templateName] = str_replace('{tooltip}', $tooltip, $link); } foreach($revokedTemplates as $templateName => $link) { $tooltip = sprintf($this->_('%1$s revoked by template %2$s, click to edit'), $name, $templateName); $revokedTemplates[$templateName] = str_replace('{tooltip}', $tooltip, $link); } */ if($name != 'page-edit' && $permission->id) { if(!in_array($permission->id, $inputfieldValue)) { $confirm = $this->_('Checking this box adds the permission for all editable templates, but this permission is already being applied separately by one or more templates. To keep things tidy, we suggest removing the permission from those templates before enabling it for all. Are you sure you want to enable it now?'); // Alert for enabling a permission for all templates } } /* if(count($addedTemplates)) { $label = implode(' ', $addedTemplates); $title .= $this->renderDetail($label, 'permission-added'); } if(count($revokedTemplates)) { $label = implode(' ', $revokedTemplates); $title .= $this->renderDetail($label, 'permission-revoked'); } */ } $classes = array( "permission", "permission-$name", "level$level", ); $p = $parent; while($p->id) { $classes[] = "parent-permission$p->id"; $classes[] = "parent-permission-$p->name"; $p = $p->getParentPermission(); } if($permission->id) { $value = $permission->id; $id = "permission$permission->id"; if($appliesAllEditable) $classes[] = "page-edit-templates"; } else { $value = "0-$name"; $id = "permission0-$name"; $disabled = true; } if($disabled) $classes[] = 'checkbox-disabled'; $attributes = array( "id" => $id, "class" => implode(' ', $classes), "data-parent" => "permission$parent->id", "data-level" => $level ); if($disabled) $attributes['disabled'] = 'disabled'; if($alert) $attributes['data-alert'] = $alert; if($confirm) $attributes['data-confirm'] = $confirm; if(!$permission->id && $checked) $inputfieldValue[] = $value; /* $title = "" . $this->templatePermissionDescriptions[$name] . "
"; } else { $desc = "" . str_replace('{permission}', $name, $this->templatePermissionDescriptions['default-add']) . "
" . "" . str_replace('{permission}', $name, $this->templatePermissionDescriptions['default-revoke']) . "
"; } $class = 'template-permissions'; $checkboxes = implode('', $checkboxes); if(strpos($checkboxes, ' checked ') || in_array($name, array('page-edit', 'page-view', 'page-add', 'page-create'))) { $class .= ' template-permissions-click'; } return "$checkboxes
" . ($note ? "$note
" : "") . "