1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-09 08:17:12 +02:00

Add improved Roles editor that now lets you manage permissions by template

This commit is contained in:
Ryan Cramer
2017-10-27 11:21:13 -04:00
parent e3fc776c53
commit c90fc3f872
10 changed files with 717 additions and 84 deletions

View File

@@ -45,7 +45,7 @@ class ProcessWire extends Wire {
* Reversion revision number
*
*/
const versionRevision = 80;
const versionRevision = 81;
/**
* Version suffix string (when applicable)

View File

@@ -25,6 +25,8 @@ class Role extends Page {
/**
* Create a new Role page in memory.
*
* @param Template $tpl
*
*/
public function __construct(Template $tpl = null) {
parent::__construct($tpl);

View File

@@ -362,7 +362,7 @@ class Template extends WireData implements Saveable, Exportable {
* - `edit`
* - `create`
* - `add`
* - Or a `Permission` object
* - Or a `Permission` object of `page-view` or `page-edit`
* @return bool True if template has the role, false if not
*
*/
@@ -402,12 +402,12 @@ class Template extends WireData implements Saveable, Exportable {
* #pw-group-manipulation
*
* @param array|PageArray $value Role objects or array or Role IDs.
* @param string Specify one of the following:
* @param string $type Specify one of the following:
* - `view` (default)
* - `edit`
* - `create`
* - `add`
* - Or a `Permission` object
* - Or a `Permission` object of `page-view` or `page-edit`
*
*/
public function setRoles($value, $type = 'view') {
@@ -433,6 +433,40 @@ class Template extends WireData implements Saveable, Exportable {
}
}
/**
* Add a permission that applies to users having a specific role with pages using this template
*
* Note that the change is not committed until you save() the template.
*
* @param Permission|int|string $permission Permission object, name, or id
* @param Role|int|string $role Role object, name or id
* @param bool $test Specify true to only test if an update would be made, without changing anything
* @return bool Returns true if an update was made (or would be made), false if not
*
*/
public function addPermissionByRole($permission, $role, $test = false) {
return $this->wire('templates')->setTemplatePermissionByRole($this, $permission, $role, false, $test);
}
/**
* Revoke a permission that applies to users having a specific role with pages using this template
*
* Note that the change is not committed until you save() the template.
*
* @param Permission|int|string $permission Permission object, name, or id
* @param Role|int|string $role Role object, name or id
* @param bool $test Specify true to only test if an update would be made, without changing anything
* @return bool Returns true if an update was made (or would be made), false if not
*
*/
public function revokePermissionByRole($permission, $role, $test = false) {
return $this->wire('templates')->setTemplatePermissionByRole($this, $permission, $role, true, $test);
}
public function hasPermissionByRole($permission, $role) {
}
/**
* Does this template have the given Field?
*
@@ -748,7 +782,7 @@ class Template extends WireData implements Saveable, Exportable {
*
* #pw-group-manipulation
*
* @return $this|bool Returns Template if successful, or false if not
* @return Template|bool Returns Template if successful, or false if not
*
*/
public function save() {
@@ -809,7 +843,7 @@ class Template extends WireData implements Saveable, Exportable {
*
*/
public function hookFinished(HookEvent $e) {
foreach($this->wire('templates') as $template) {
foreach($e->wire('templates') as $template) {
if($template->isChanged('modified') || $template->isChanged('ns')) $template->save();
}
}
@@ -1041,7 +1075,7 @@ class Template extends WireData implements Saveable, Exportable {
*
* #pw-group-identification
*
* @param $icon Font-awesome icon name
* @param string $icon Font-awesome icon name
* @return $this
*
*/

View File

@@ -556,6 +556,88 @@ class Templates extends WireSaveableItems {
return $this->getParentPage($template, $checkAccess, true);
}
/**
* Set a Permission for a Template for and specific Role
*
* Note: you must also save() the template to commit the change.
*
* #pw-internal
*
* @param Template $template
* @param Permission|string|int $permission
* @param Role|string|int $role
* @param bool $revoke Specify true to revoke the permission, or omit to add the permission
* @param bool $test When true, no changes are made but return value still applicable
* @return bool True if an update was made (or would be made), false if not
* @throws WireException If given unknown Role or Permission
*
*/
public function setTemplatePermissionByRole(Template $template, $permission, $role, $revoke = false, $test = false) {
if(!$template->useRoles) throw new WireException("Template $template does not have access control enabled");
$defaultPermissions = array('page-view', 'page-edit', 'page-create', 'page-add');
$updated = false;
if(is_string($role) || is_int($role)) $role = $this->wire('roles')->get($role);
if(!$role instanceof Role) throw new WireException("Unknown role for Template::setPermissionByRole");
if(is_string($permission) && in_array($permission, $defaultPermissions)) {
$permissionName = $permission;
} else if($permission instanceof Permission) {
$permissionName = $permission->name;
} else {
$permission = $this->wire('permissions')->get($permission);
$permissionName = $permission ? $permission->name : '';
}
if(in_array($permissionName, $defaultPermissions)) {
// use pre-defined view/edit/create/add roles
$roles = $template->getRoles($permissionName);
$has = $roles->has($role);
if($revoke) {
if($has) {
if($test) return true;
$roles->remove($role);
$template->setRoles($roles, $permissionName);
$updated = true;
}
} else if(!$has) {
if($test) return true;
$roles->add($role);
$template->setRoles($roles, $permissionName);
$updated = true;
}
} else if($permission instanceof Permission) {
$rolesPermissions = $template->get('rolesPermissions');
if(!is_array($rolesPermissions)) $rolesPermissions = array();
$rolePermissions = isset($rolesPermissions["$role->id"]) ? $rolesPermissions["$role->id"] : array();
$_rolePermissions = $rolePermissions;
if($revoke) {
$key = array_search("$permission->id", $rolePermissions);
if($key !== false) unset($rolePermissions[$key]);
if(!in_array("-$permission->id", $rolePermissions)) $rolePermissions[] = "-$permission->id";
} else {
$key = array_search("-$permission->id", $rolePermissions);
if($key !== false) unset($rolePermissions[$key]);
if(!in_array("$permission->id", $rolePermissions)) $rolePermissions[] = "$permission->id";
}
if($rolePermissions !== $_rolePermissions) {
if($test) return true;
$rolesPermissions["$role->id"] = $rolePermissions;
$template->set('rolesPermissions', $rolesPermissions);
$updated = true;
}
} else {
throw new WireException("Unknown permission for Templates::setPermissionByRole");
}
return $updated;
}
/**
* FUTURE USE: Is the parent/child relationship allowed?
*

View File

@@ -44,6 +44,7 @@ class InputfieldCheckboxes extends InputfieldSelectMultiple implements Inputfiel
/** @var MarkupAdminDataTable $table */
$table = $this->modules->get("MarkupAdminDataTable");
$table->setEncodeEntities(false);
$table->setSortable(false);
$table->addClass('pw-no-select');
if($this->thead) $table->headerRow(explode('|', htmlspecialchars($this->thead, ENT_QUOTES, 'UTF-8')));

View File

@@ -6,15 +6,12 @@ label.level2 {
padding-left: 2.25em;
}
#wrap_Inputfield_permissions td i.fa {
opacity: 0.6;
}
#wrap_Inputfield_permissions table td {
width: 70%;
width: 75%;
}
#wrap_Inputfield_permissions table > tbody > tr > td:first-child {
width: 30%;
width: 25%;
}
tr:not(.permission32):not(.permission0-page-add):not(.permission0-page-create).permission-checked .permission-added {
@@ -58,3 +55,56 @@ p.description + p.description {
margin: 0.5em 0;
}
.template-permissions {
margin-top: 0.5em;
}
.template-permissions:not(.template-permissions-open) {
display: none;
}
.template-permissions > p {
margin: 0.5em 0;
}
.template-permissions label {
display: block;
}
.toggle-template-permissions {
display: block;
padding-left: 5px;
padding-right: 5px;
}
.permission-title {
font-weight: bold;
}
.page-edit-templates .permission-title {
cursor: pointer;
}
tr.permission-page-edit:not(.permission-checked) .toggle-template-permissions {
display: none;
}
tr.permission-page-edit:not(.permission-checked) .template-permissions,
tr.permission-page-edit:not(.permission-checked) .template-permissions-open {
display: none !important;
}
tr.permission-checked .template-permissions label.template-permission-add {
display: none;
}
tr.permission-checked .description-not-checked {
display: none;
}
tr:not(.permission-checked) .description-checked {
display: none;
}
tr:not(.permission-checked) .template-permissions label.template-permission-revoke {
display: none;
}
#ProcessPageEdit table.AdminDataTable tr > th,
#ProcessPageEdit table.AdminDataTable tr > td {
/* AdminThemeDefault and AdminThemeReno */
border-left: none;
border-right: none;
}

View File

@@ -2,7 +2,7 @@
function ProcessRoleUpdatePermissions(init, $checkbox) {
var $inputfield = $("#wrap_Inputfield_permissions");
var $checkboxes = $checkbox == null ? $inputfield.find(".permission > input[type=checkbox]") : $checkbox;
var $checkboxes = $checkbox == null ? $inputfield.find("input.global-permission") : $checkbox;
if(init) {
// update row classes to be the same as the label classes
@@ -30,11 +30,21 @@ function ProcessRoleUpdatePermissions(init, $checkbox) {
$children = $children.filter(".level" + (level+1));
init ? $children.show() : $children.fadeIn();
$row.addClass('permission-checked');
if($row.hasClass('permission-page-edit')) {
if(!$row.find('.template-permissions-open').length) {
$row.find('.toggle-template-permissions').click();
}
}
} else {
$children.find("input:not(:disabled)").removeAttr('checked');
$children.find("input.global-permission:not(:disabled)").removeAttr('checked');
init ? $children.hide() : $children.fadeOut();
$row.removeClass('permission-checked');
if($row.hasClass('permission-page-edit')) {
if($row.find('.template-permissions-open').length) {
$row.find('.toggle-template-permissions').click();
}
}
}
});
}
@@ -46,7 +56,7 @@ $(document).ready(function() {
ProcessRoleUpdatePermissions(true, null);
$("#wrap_Inputfield_permissions").on("click", "input[type=checkbox], label.checkbox-disabled", function(e) {
$("#wrap_Inputfield_permissions").on("click", "input.global-permission, label.checkbox-disabled", function(e) {
if($(this).is("label")) {
var $label = $(this);
@@ -76,4 +86,36 @@ $(document).ready(function() {
}
});
$(".toggle-template-permissions").click(function() {
var $div = $(this).closest('tr').find('.template-permissions');
if($div.hasClass('template-permissions-open')) {
$div.fadeOut('fast', function() {
$div.removeClass('template-permissions-open');
});
} else {
$div.fadeIn('fast', function() {
$div.addClass('template-permissions-open');
});
}
var $icon = $(this).find('i');
$icon.toggleClass($icon.attr('data-toggle'));
return false;
});
// make some of the open when page loads
$('.template-permissions-click').each(function() {
$(this).closest('tr').find('.toggle-template-permissions').click();
$(this).removeClass('template-permissions-click');
});
$('.permission-title').click(function() {
$(this).closest('tr').find('.toggle-template-permissions').click();
});
// ensure checkbox classes are consistent (like for uk-checkbox)
a = $('input.global-permission:eq(0)');
b = $('<div />').addClass(a.attr('class')).removeClass('permission permission-checked global-permission');
c = $('input.template-permission').addClass(b.attr('class'));
});

View File

@@ -1 +1 @@
function ProcessRoleUpdatePermissions(d,b){var a=$("#wrap_Inputfield_permissions");var c=b==null?a.find(".permission > input[type=checkbox]"):b;if(d){c.each(function(){var g=$(this);var f=g.closest("label");var e=g.closest("tr");e.addClass(f.attr("class"))})}c.each(function(){var k=$(this);var f=k.closest("label");var e=k.closest("tr");var i=$("#"+f.attr("data-parent"));var h=f.text();var j=parseInt(f.attr("data-level"));var g=e.nextAll(".parent-"+f.attr("id"));e.addClass(f.attr("id"));if(k.is(":checked")){g=g.filter(".level"+(j+1));d?g.show():g.fadeIn();e.addClass("permission-checked")}else{g.find("input:not(:disabled)").removeAttr("checked");d?g.hide():g.fadeOut();e.removeClass("permission-checked")}})}$(document).ready(function(){var a=$("#Inputfield_permissions_36");if(!a.is(":checked")){a.attr("checked","checked")}ProcessRoleUpdatePermissions(true,null);$("#wrap_Inputfield_permissions").on("click","input[type=checkbox], label.checkbox-disabled",function(f){if($(this).is("label")){var b=$(this);var c=b.children("input")}else{var c=$(this);var b=c.parent()}var g=b.attr("data-alert");var d=b.attr("data-confirm");if(typeof g!="undefined"&&g.length){ProcessWire.alert(g);return false}else{if(typeof d!="undefined"&&d.length){if(c.is(":checked")){if(!confirm(d)){return false}}}}if($(this).is("input")){var c=$(this);setTimeout(function(){ProcessRoleUpdatePermissions(false,c)},100)}})});
function ProcessRoleUpdatePermissions(g,e){var d=$("#wrap_Inputfield_permissions");var f=e==null?d.find("input.global-permission"):e;if(g){f.each(function(){var j=$(this);var i=j.closest("label");var h=j.closest("tr");h.addClass(i.attr("class"))})}f.each(function(){var n=$(this);var i=n.closest("label");var h=n.closest("tr");var l=$("#"+i.attr("data-parent"));var k=i.text();var m=parseInt(i.attr("data-level"));var j=h.nextAll(".parent-"+i.attr("id"));h.addClass(i.attr("id"));if(n.is(":checked")){j=j.filter(".level"+(m+1));g?j.show():j.fadeIn();h.addClass("permission-checked");if(h.hasClass("permission-page-edit")){if(!h.find(".template-permissions-open").length){h.find(".toggle-template-permissions").click()}}}else{j.find("input.global-permission:not(:disabled)").removeAttr("checked");g?j.hide():j.fadeOut();h.removeClass("permission-checked");if(h.hasClass("permission-page-edit")){if(h.find(".template-permissions-open").length){h.find(".toggle-template-permissions").click()}}}})}$(document).ready(function(){var d=$("#Inputfield_permissions_36");if(!d.is(":checked")){d.attr("checked","checked")}ProcessRoleUpdatePermissions(true,null);$("#wrap_Inputfield_permissions").on("click","input.global-permission, label.checkbox-disabled",function(i){if($(this).is("label")){var f=$(this);var g=f.children("input")}else{var g=$(this);var f=g.parent()}var j=f.attr("data-alert");var h=f.attr("data-confirm");if(typeof j!="undefined"&&j.length){ProcessWire.alert(j);return false}else{if(typeof h!="undefined"&&h.length){if(g.is(":checked")){if(!confirm(h)){return false}}}}if($(this).is("input")){var g=$(this);setTimeout(function(){ProcessRoleUpdatePermissions(false,g)},100)}});$(".toggle-template-permissions").click(function(){var f=$(this).closest("tr").find(".template-permissions");if(f.hasClass("template-permissions-open")){f.fadeOut("fast",function(){f.removeClass("template-permissions-open")})}else{f.fadeIn("fast",function(){f.addClass("template-permissions-open")})}var e=$(this).find("i");e.toggleClass(e.attr("data-toggle"));return false});$(".template-permissions-click").each(function(){$(this).closest("tr").find(".toggle-template-permissions").click();$(this).removeClass("template-permissions-click")});$(".permission-title").click(function(){$(this).closest("tr").find(".toggle-template-permissions").click()});a=$("input.global-permission:eq(0)");b=$("<div />").addClass(a.attr("class")).removeClass("permission permission-checked global-permission");c=$("input.template-permission").addClass(b.attr("class"))});

View File

@@ -16,7 +16,7 @@ class ProcessRole extends ProcessPageType {
static public function getModuleInfo() {
return array(
'title' => __('Roles', __FILE__), // getModuleInfo title
'version' => 103,
'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
@@ -26,10 +26,28 @@ class ProcessRole extends ProcessPageType {
}
protected $icons = array();
protected $templatePermissionNotes = array();
protected $templatePermissionDescriptions = array();
/**
* @var Role
*
*/
protected $guestRole;
/**
* Init and attach hooks
*
*/
public function init() {
parent::init();
$this->wire('modules')->get('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'),
@@ -38,55 +56,80 @@ class ProcessRole extends ProcessPageType {
'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' => '',
'page-view' => '',
);
}
/**
* Hook ProcessPageEdit::processInput to save permission options
*
* @param HookEvent $event
*
*/
public function hookProcessInput(HookEvent $event) {
if($event->wire('input')->post('_pw_page_name')) {
$this->savePermissionOptions();
}
}
/**
* 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).'), 'https://processwire.com/api/user-access/permissions/'), true); // Permissions documentation info
$strikethrough = '<s>' . $this->_('strikethrough') . '</s>';
$f->appendMarkup =
"<div class='permissions-footer detail'>" .
"<p>" .
$this->icons['edit'] .
$this->_('Checking the page-edit permission here does not grant edit access to any pages on its own. Instead, it enables you to configure edit access for this role in template access settings. The page-edit permission is also what enables users to see Pages in the admin. This permission is recommended for all administrative users.') . // Description of page-edit
"</p>" .
"<p>" .
$this->icons['page'] .
$this->_('Indicates permission may be optionally added or revoked from user roles in template access settings. Checking the permission enables it for all editable templates. If you only want a user role to have one of these permissions for pages using specific template(s), you should leave it unchecked here and instead select it in a template "Access" tab.') . // Description of permission that can be assigned by templates
"</p>" .
"<p>" .
$this->icons['info'] .
$this->_('The page-add and page-create permissions can only be added from the template access settings and are shown here just for informational purposes.') . // Description of informational permissions
"</p>" .
"<p>" .
$this->icons['add'] .
sprintf($this->_('Indicates a template that adds the permission. Click to open a window to the access settings for that template. If the template name has %s it indicates the permission is added at the template, but being overridden by your selection here.'), $strikethrough) . // Description of template added permission
"</p>" .
"<p>" .
$this->icons['revoke'] .
$this->_('Same as the above except that the permission is being revoked by that template (rather than added).') . // Description of template revoked permission
"</p>" .
"</div>";
$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 "|"
$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);
$permissions = array();
// establish root permission containers
foreach($this->wire('permissions') as $permission) {
@@ -98,9 +141,15 @@ class ProcessRole extends ProcessPageType {
$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])) {
@@ -126,8 +175,19 @@ class ProcessRole extends ProcessPageType {
$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 = 0, &$inputfieldValue) {
/** @var InputfieldCheckboxes $f */
/** @var Role $role */
$role = $this->getPage();
foreach($permissions as $name => $children) {
@@ -138,14 +198,21 @@ class ProcessRole extends ProcessPageType {
$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 = $this->wire('pages')->newNullPage();
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 access settings.');
$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;
@@ -155,31 +222,39 @@ class ProcessRole extends ProcessPageType {
$checked = in_array($permission->id, $inputfieldValue);
}
$title = "<span class='permission-title'>$title</span>";
if($name == 'page-view') {
$title .= " <span class='detail'>" . $this->_('(required)') . "</span>";
$title .= $this->renderDetail($this->_('(required)'));
$alert = $this->_('This permission is required for all roles.');
} else if($name == 'page-edit') {
$title = $this->icons['edit'] . $title;
}
if(($parent->name == 'page-edit' || $rootParent->name == 'page-edit') && strpos($name, 'page-') === 0) {
if($name == 'page-add' || $name == 'page-create') {
$title = $this->icons['info'] . $title;
$title = $title;
} else {
$title = $this->icons['page'] . $title . ' ' .
"<span class='detail permission-all-templates'>(" . $this->_('applies to all editable templates') . ')</span>';
$appliesAllEditable = true;
$title .= $this->renderDetail('(' . $this->_('applies to all editable templates') . ')', 'permission-all-templates');
}
}
foreach($this->wire('templates') as $template) {
/** @var Template $template */
if(!$template->useRoles) continue;
$rolesPermissions = $template->rolesPermissions;
$templateEditURL = "../../../setup/template/edit?id=$template->id#tab_access";
$templateEditLink =
"<a class='tooltip' title='{tooltip}' target='_blank' href='$templateEditURL'>" .
$this->icons['add'] . "$template->name</a>";
$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)) {
@@ -191,27 +266,38 @@ class ProcessRole extends ProcessPageType {
$checked = true;
$addedTemplates[$template->name] = $templateEditLink;
}
} else {
$rolesPermissions = $template->rolesPermissions;
if(!isset($rolesPermissions[$role->id])) continue;
} 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);
$addedTemplates[$templateName] = str_replace('{tooltip}', $tooltip, $link);
$revokedTemplates[$templateName] = str_replace('{tooltip}', $tooltip, $link);
}
*/
if($name != 'page-edit' && $permission->id) {
if(!in_array($permission->id, $inputfieldValue)) {
@@ -219,62 +305,394 @@ class ProcessRole extends ProcessPageType {
}
}
/*
if(count($addedTemplates)) {
$label = implode(' ', $addedTemplates);
$title .= " <span class='detail permission-added'>$label</span> ";
$title .= $this->renderDetail($label, 'permission-added');
}
if(count($revokedTemplates)) {
$label = implode(' ', $revokedTemplates);
$title .= " <span class='detail permission-revoked'>$label</span>";
$title .= $this->renderDetail($label, 'permission-revoked');
}
} else if($name == 'page-edit') {
$title .= " <span class='detail permission-added'>(" . $this->_('not currently applied to any templates') . ")</span>";
*/
}
$class = "permission level$level";
$classes = array(
"permission",
"permission-$name",
"level$level",
);
$p = $parent;
while($p->id) {
$class .= " parent-permission$p->id";
$p = $p->getParentPermission($p);
$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" => $class,
"class" => implode(' ', $classes),
"data-parent" => "permission$parent->id",
"data-level" => $level
);
if(!$permission->id && $checked) $inputfieldValue[] = $value;
if($disabled) {
$attributes['disabled'] = 'disabled';
$attributes['class'] .= ' checkbox-disabled';
}
if($disabled) $attributes['disabled'] = 'disabled';
if($alert) $attributes['data-alert'] = $alert;
if($confirm) $attributes['data-confirm'] = $confirm;
if(!$permission->id && $checked) $inputfieldValue[] = $value;
/*
$title =
"<a class='permission-help' target='_blank' href='https://processwire.com/api/user-access/permissions/#$name'>" .
$this->icons['help'] . "</a>" . $title;
*/
$f->addOption($value, "$name|$title", $attributes);
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 =
"<p class='description'>" . $this->templatePermissionDescriptions[$name] . "</p>";
} else {
$desc =
"<p class='description description-not-checked'>" .
str_replace('{permission}', $name, $this->templatePermissionDescriptions['default-add']) .
"</p>" .
"<p class='description description-checked'>" .
str_replace('{permission}', $name, $this->templatePermissionDescriptions['default-revoke']) .
"</p>";
}
$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
"<div class='$class'>" .
$desc .
"<p class='template-checkboxes'>$checkboxes</p>" .
($note ? "<p class='detail'>$note</p>" : "") .
"</div>";
}
/**
* Render a single template permission checkbox
*
* @param Template $template
* @param Permission $permission
* @param bool $checked
* @return string
*
*/
protected function renderTemplatePermissionCheckbox(Template $template, Permission $permission, $checked) {
$disabled = false;
$note = '';
if($permission->name == 'page-view' && $this->guestRole->hasPermission('page-view', $template)) {
$checked = true;
$disabled = true;
$note = $this->_('(inherited from guest role)');
}
$checked = $checked ? 'checked' : '';
$disabled = $disabled ? 'disabled' : '';
$class = "template-permission template{$template->id}-permission$permission->id";
// note: pt=permission+template, tp=template+permission
if($permission->name == 'page-add') {
$name = "pt_add_$template->id";
} else if($permission->name == 'page-create') {
$name = "pt_create_$template->id";
} else if(in_array($permission->name, array('page-edit', 'page-view'))) {
$name = "pt_{$permission->id}_{$template->id}";
} else {
$name = '';
}
if($name) {
// checkbox
$out =
"<label>" .
"<input type='checkbox' $checked $disabled name='$name' value='1' class='$class'>" .
"$template->name <span class='detail'>$note</span>" .
"</label>";
} else {
// select add or revoke
/** @var Role $role */
$role = $this->getPage();
$name = "tp_{$template->id}[]";
$rolesPermissions = $template->rolesPermissions;
$rolePermissions = isset($rolesPermissions["$role->id"]) ? $rolesPermissions["$role->id"] : array();
$addChecked = in_array("$permission->id", $rolePermissions) ? 'checked' : '';
$revokeChecked = in_array("-$permission->id", $rolePermissions) ? 'checked' : '';
$out =
"<label class='template-permission-add'>" .
"<input type='checkbox' name='add_$name' value='$permission->id' $addChecked class='$class'>" .
sprintf($this->_('Add to: %s'), $template->name) .
"</label>" .
"<label class='template-permission-revoke'>" .
"<input type='checkbox' name='revoke_$name' value='$permission->id' $revokeChecked class='$class'>" .
sprintf($this->_('Revoke from: %s'), $template->name) .
"</label>";
}
return $out;
}
/**
* Render the toggle that can trigger the template permission checkboxes
*
* @return string
*
*/
protected function renderTemplatePermissionToggle() {
return
"<a href='#' class='toggle-template-permissions tooltip' title='" . $this->_('Click to open/close permission settings by template') . "'>" .
"<i class='fa fa-chevron-circle-right' data-toggle='fa-chevron-circle-down fa-chevron-circle-right'></i>" .
"</a>";
}
/**
* Render an <a> link
*
* @param string $href
* @param string $text
* @param array $attr
* @return string
*
*/
protected function renderLink($href, $text, array $attr = array()) {
$attr['href'] = $href;
$out = "<a ";
foreach($attr as $key => $value) {
$out .= " $key='" . $this->wire('sanitizer')->entities($value) . "'";
}
$out .= ">$text</a>";
return $out;
}
/**
* Render a detail
*
* @param string $text Markup to render in the detail
* @param string $class May be omitted if not needed
* @param string $tag Default is span
* @return string
*
*/
protected function renderDetail($text, $class = '', $tag = 'span') {
$class = $class ? "detail $class" : "detail";
return ' ' . $this->renderText($text, $class, $tag);
}
/**
* Render paragraph of text (or other tag as specified)
*
* @param string $text
* @param string $class Default is blank
* @param string $tag Default is p
* @return string
*
*/
protected function renderText($text, $class = '', $tag = 'p') {
$class = $class ? " class='$class'" : "";
return "<$tag$class>$text</$tag>";
}
/**
* Save posted permission options to templates
*
*/
protected function savePermissionOptions() {
$role = $this->getPage();
if(!$role->id) return;
$isGuestRole = $role->id == $this->guestRole->id;
/** @var WireInput $input */
$input = $this->wire('input');
$viewPermission = $this->wire('permissions')->get('page-view');
$editPermission = $this->wire('permissions')->get('page-edit');
foreach($this->wire('templates') as $template) {
/** @var Template $template */
if(!$template->useRoles) continue;
$updates = array();
$createRoles = $template->createRoles;
$addRoles = $template->addRoles;
$editRoles = $template->editRoles;
$guestHasView = $this->guestRole->hasPermission($viewPermission, $template);
$rolesPermissions = $template->rolesPermissions;
$rolePermissions = isset($rolesPermissions["$role->id"]) ? $rolesPermissions["$role->id"] : array();
$view = $input->post("pt_{$viewPermission->id}_{$template->id}");
$edit = $input->post("pt_{$editPermission->id}_{$template->id}");
$add = $input->post("pt_add_{$template->id}");
$create = $input->post("pt_create_{$template->id}");
// page-view
if($view) {
if(!$template->roles->has($role)) {
$template->roles->add($role);
$updates[] = "Added page-view to template $template->name";
}
} else {
if($template->roles->has($role)) {
if($isGuestRole || !$guestHasView) {
$template->roles->remove($role);
$updates[] = "Removed page-view from template $template->name";
}
}
}
if(!$isGuestRole) {
// page-edit
if($edit) {
if(!in_array($role->id, $editRoles)) {
$editRoles[] = $role->id;
$updates[] = "Added page-edit to template $template->name";
}
} else {
$key = array_search($role->id, $editRoles);
if($key !== false) {
unset($editRoles[$key]);
$updates[] = "Removed page-edit from template $template->name";
}
}
// page-add
if($add) {
if(!in_array($role->id, $addRoles)) {
$addRoles[] = $role->id;
$updates[] = "Added page-add to template $template->name";
}
} else {
$key = array_search($role->id, $addRoles);
if($key !== false) {
unset($addRoles[$key]);
$updates[] = "Removed page-add from template $template->name";
}
}
// page-create
if($create) {
if(!in_array($role->id, $createRoles)) {
$createRoles[] = $role->id;
$updates[] = "Added page-create to template $template->name";
}
} else {
$key = array_search($role->id, $createRoles);
if($key !== false) {
unset($createRoles[$key]);
$updates[] = "Removed page-create from template $template->name";
}
}
} // if(!isGuestRole)
// rolesPermissions
$adds = $input->post->intArray("add_tp_$template->id");
$revokes = $input->post->intArray("revoke_tp_$template->id");
foreach($adds as $key => $permissionID) {
// force as strings
$adds[$key] = "$permissionID";
}
foreach($revokes as $key => $permissionID) {
// force as negative integer strings
$revokes[$key] = (string) (-1 * $permissionID);
}
$rolePermissionsNew = array_merge($adds, $revokes);
sort($rolePermissionsNew);
sort($rolePermissions);
if($rolePermissionsNew != $rolePermissions) {
if($this->wire('config')->debug) {
$removedPermissions = array_diff($rolePermissions, $rolePermissionsNew);
$addedPermissions = array_diff($rolePermissionsNew, $rolePermissions);
foreach($removedPermissions as $permissionID) {
$permissionID = (int) $permissionID;
$permission = $this->wire('permissions')->get(abs($permissionID));
$updates[] = ($permissionID < 0 ? "Removed revoke" : "Removed add") . " " .
"$permission->name for template $template->name" ;
}
foreach($addedPermissions as $permissionID) {
$permissionID = (int) $permissionID;
$permission = $this->wire('permissions')->get(abs($permissionID));
$updates[] = ($permissionID < 0 ? "Added revoke" : "Added add") . " " .
"$permission->name for template $template->name" ;
}
}
$updates[] = "Updated rolesPermissions for template $template->name";
$rolesPermissions["$role->id"] = $rolePermissionsNew;
$template->rolesPermissions = $rolesPermissions;
}
// save changes
if(count($updates)) {
if($editRoles != $template->editRoles) $template->editRoles = $editRoles;
if($addRoles != $template->addRoles) $template->addRoles = $addRoles;
if($createRoles != $template->createRoles) $template->createRoles = $createRoles;
if($this->wire('config')->debug) {
foreach($updates as $update) $this->message($update);
}
$template->save();
}
}
}
}

View File

@@ -608,6 +608,10 @@ class ProcessTemplate extends Process {
*/
public function ___executeEdit() {
if(substr($this->wire('input')->url(), -1) === '/') {
// we require non-trailing slash for edit requests
$this->wire('session')->redirect("../edit?id={$this->template->id}");
}
$this->wire('breadcrumbs')->add(new Breadcrumb('./', $this->moduleInfo['title']));
$min = $this->wire('config')->debug ? '' : '.min';
$this->wire('config')->scripts->add($this->wire('config')->urls->ProcessTemplate . "ProcessTemplateFieldCreator$min.js?v=1");