diff options
36 files changed, 1396 insertions, 60 deletions
diff --git a/inc/user.inc.php b/inc/user.inc.php index f7688b00..13e56cd3 100644 --- a/inc/user.inc.php +++ b/inc/user.inc.php @@ -26,13 +26,28 @@ class User return self::$user['fullname']; } - public static function hasPermission($permission) + public static function hasPermission($permission, $locationid = NULL) { if (!self::isLoggedIn()) return false; + if (Module::isAvailable("permissionmanager")) { + $module = Page::getModule(); + $permission = $module ? $module->getIdentifier().".".$permission : $permission; + return PermissionUtil::userHasPermission(self::$user['userid'], $permission, $locationid); + } return (self::$user['permissions'] & (Permission::get($permission) | Permission::get('superadmin'))) != 0; } + public static function getAllowedLocations($permission) + { + if (Module::isAvailable("permissionmanager")) { + $module = Page::getModule(); + $permission = $module ? $module->getIdentifier().".".$permission : $permission; + return PermissionUtil::getAllowedLocations(self::$user['userid'], $permission); + } + return array(); + } + public static function load() { if (self::isLoggedIn()) diff --git a/modules-available/js_stupidtable/clientscript.js b/modules-available/js_stupidtable/clientscript.js index bfbc9112..4e0dd4c9 100644 --- a/modules-available/js_stupidtable/clientscript.js +++ b/modules-available/js_stupidtable/clientscript.js @@ -24,7 +24,167 @@ SOFTWARE. */ -(function(c){c.fn.stupidtable=function(b){return this.each(function(){var a=c(this);b=b||{};b=c.extend({},c.fn.stupidtable.default_sort_fns,b);a.data("sortFns",b);a.on("click.stupidtable","thead th",function(){c(this).stupidsort()})})};c.fn.stupidsort=function(b){var a=c(this),g=0,f=c.fn.stupidtable.dir,e=a.closest("table"),k=a.data("sort")||null;if(null!==k){a.parents("tr").find("th").slice(0,c(this).index()).each(function(){var a=c(this).attr("colspan")||1;g+=parseInt(a,10)});var d;1==arguments.length? - d=b:(d=b||a.data("sort-default")||f.ASC,a.data("sort-dir")&&(d=a.data("sort-dir")===f.ASC?f.DESC:f.ASC));if(a.data("sort-dir")!==d)return a.data("sort-dir",d),e.trigger("beforetablesort",{column:g,direction:d}),e.css("display"),setTimeout(function(){var b=[],l=e.data("sortFns")[k],h=e.children("tbody").children("tr");h.each(function(a,d){var e=c(d).children().eq(g),f=e.data("sort-value");"undefined"===typeof f&&(f=e.text(),e.data("sort-value",f));b.push([f,d])});b.sort(function(a,b){return l(a[0], - b[0])});d!=f.ASC&&b.reverse();h=c.map(b,function(a){return a[1]});e.children("tbody").append(h);e.find("th").data("sort-dir",null).removeClass("sorting-desc sorting-asc");a.data("sort-dir",d).addClass("sorting-"+d);e.trigger("aftertablesort",{column:g,direction:d});e.css("display")},10),a}};c.fn.updateSortVal=function(b){var a=c(this);a.is("[data-sort-value]")&&a.attr("data-sort-value",b);a.data("sort-value",b);return a};c.fn.stupidtable.dir={ASC:"asc",DESC:"desc"};c.fn.stupidtable.default_sort_fns= - {"int":function(b,a){return parseInt(b,10)-parseInt(a,10)},"float":function(b,a){return parseFloat(b)-parseFloat(a)},string:function(b,a){return b.toString().localeCompare(a.toString())},"string-ins":function(b,a){b=b.toString().toLocaleLowerCase();a=a.toString().toLocaleLowerCase();return b.localeCompare(a)}}})(jQuery); +(function($) { + $.fn.stupidtable = function(sortFns) { + return this.each(function() { + var $table = $(this); + sortFns = sortFns || {}; + sortFns = $.extend({}, $.fn.stupidtable.default_sort_fns, sortFns); + $table.data('sortFns', sortFns); + + $table.on("click.stupidtable", "thead th", function() { + $(this).stupidsort(); + }); + + // to show the sort-arrow next to the table header + $table.on("aftertablesort", function (event, data) { + var th = $(this).find("th"); + th.find(".arrow").remove(); + var dir = $.fn.stupidtable.dir; + var arrow = data.direction === dir.ASC ? "down" : "up"; + th.eq(data.column).append(' <span class="arrow glyphicon glyphicon-chevron-'+arrow+'"></span>'); + }); + }); + }; + + + // Expects $("#mytable").stupidtable() to have already been called. + // Call on a table header. + $.fn.stupidsort = function(force_direction){ + var $this_th = $(this); + var th_index = 0; // we'll increment this soon + var dir = $.fn.stupidtable.dir; + var $table = $this_th.closest("table"); + var datatype = $this_th.data("sort") || null; + + // No datatype? Nothing to do. + if (datatype === null) { + return; + } + + // Account for colspans + $this_th.parents("tr").find("th").slice(0, $(this).index()).each(function() { + var cols = $(this).attr("colspan") || 1; + th_index += parseInt(cols,10); + }); + + var sort_dir; + if(arguments.length == 1){ + sort_dir = force_direction; + } + else{ + sort_dir = force_direction || $this_th.data("sort-default") || dir.ASC; + if ($this_th.data("sort-dir")) + sort_dir = $this_th.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC; + } + + // Bail if already sorted in this direction + if ($this_th.data("sort-dir") === sort_dir) { + return; + } + // Go ahead and set sort-dir. If immediately subsequent calls have same sort-dir they will bail + $this_th.data("sort-dir", sort_dir); + + $table.trigger("beforetablesort", {column: th_index, direction: sort_dir}); + + // More reliable method of forcing a redraw + $table.css("display"); + + // Run sorting asynchronously on a timout to force browser redraw after + // `beforetablesort` callback. Also avoids locking up the browser too much. + setTimeout(function() { + // Gather the elements for this column + var column = []; + var sortFns = $table.data('sortFns'); + var sortMethod = sortFns[datatype]; + var trs = $table.children("tbody").children("tr"); + + // Extract the data for the column that needs to be sorted and pair it up + // with the TR itself into a tuple. This way sorting the values will + // incidentally sort the trs. + trs.each(function(index,tr) { + var $e = $(tr).children().eq(th_index); + var sort_val = $e.data("sort-value"); + + // Store and read from the .data cache for display text only sorts + // instead of looking through the DOM every time + if(typeof(sort_val) === "undefined"){ + var txt = $e.text(); + $e.data('sort-value', txt); + sort_val = txt; + } + column.push([sort_val, tr]); + }); + + // Sort by the data-order-by value + column.sort(function(a, b) { return sortMethod(a[0], b[0]); }); + if (sort_dir != dir.ASC) + column.reverse(); + + // Replace the content of tbody with the sorted rows. Strangely + // enough, .append accomplishes this for us. + trs = $.map(column, function(kv) { return kv[1]; }); + $table.children("tbody").append(trs); + + // Reset siblings + $table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc"); + $this_th.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir); + + $table.trigger("aftertablesort", {column: th_index, direction: sort_dir}); + $table.css("display"); + }, 10); + + return $this_th; + }; + + // Call on a sortable td to update its value in the sort. This should be the + // only mechanism used to update a cell's sort value. If your display value is + // different from your sort value, use jQuery's .text() or .html() to update + // the td contents, Assumes stupidtable has already been called for the table. + $.fn.updateSortVal = function(new_sort_val){ + var $this_td = $(this); + if($this_td.is('[data-sort-value]')){ + // For visual consistency with the .data cache + $this_td.attr('data-sort-value', new_sort_val); + } + $this_td.data("sort-value", new_sort_val); + return $this_td; + }; + + // ------------------------------------------------------------------ + // Default settings + // ------------------------------------------------------------------ + $.fn.stupidtable.dir = {ASC: "asc", DESC: "desc"}; + $.fn.stupidtable.default_sort_fns = { + "int": function(a, b) { + return parseInt(a, 10) - parseInt(b, 10); + }, + "float": function(a, b) { + return parseFloat(a) - parseFloat(b); + }, + "string": function(a, b) { + return a.toString().localeCompare(b.toString()); + }, + "string-ins": function(a, b) { + a = a.toString().toLocaleLowerCase(); + b = b.toString().toLocaleLowerCase(); + return a.localeCompare(b); + }, + "ipv4":function(a,b){ + var aa = a.split("."); + var bb = b.split("."); + + var resulta = aa[0]*0x1000000 + aa[1]*0x10000 + aa[2]*0x100 + aa[3]*1; + var resultb = bb[0]*0x1000000 + bb[1]*0x10000 + bb[2]*0x100 + bb[3]*1; + + return resulta-resultb; + } + }; +})(jQuery); + +document.addEventListener("DOMContentLoaded", function() { + var table = $(".stupidtable"); + if (table.length) { + table = table.stupidtable(); + } +});
\ No newline at end of file diff --git a/modules-available/js_stupidtable/style.css b/modules-available/js_stupidtable/style.css new file mode 100644 index 00000000..614a3d38 --- /dev/null +++ b/modules-available/js_stupidtable/style.css @@ -0,0 +1,3 @@ +th[data-sort] { + cursor: pointer; +}
\ No newline at end of file diff --git a/modules-available/permissionmanager/api.inc.php b/modules-available/permissionmanager/api.inc.php new file mode 100644 index 00000000..0d84ebce --- /dev/null +++ b/modules-available/permissionmanager/api.inc.php @@ -0,0 +1,7 @@ +<?php + +echo json_encode(array( + 'key' => 'value', + 'number' => 123, + 'list' => array(1,2,3,4,5,6,'foo') +)); diff --git a/modules-available/permissionmanager/clientscript.js b/modules-available/permissionmanager/clientscript.js new file mode 100644 index 00000000..1881c70d --- /dev/null +++ b/modules-available/permissionmanager/clientscript.js @@ -0,0 +1,48 @@ +document.addEventListener("DOMContentLoaded", function() { + var selectize = $('#select-role'); + if (selectize.length) { + selectize = selectize.selectize({ + allowEmptyOption: false, + maxItems: null, + highlight: false, + hideSelected: true, + create: false, + plugins: ["remove_button"] + })[0].selectize; + + // If Site gets refreshed, all data-selectizeCounts will be reset to 0, so delete the filters from the selectize + selectize.clear(); + + selectize.on('item_add', function (value, $item) { + // When first item gets added the filter isn't empty anymore, so hide all rows + if (selectize.items.length === 1) { + $('.dataTable tbody').find('tr').hide(); + } + // Find all rows which shall be shown and increase their counter by 1 + $(".roleId-" + value).closest("tr").each(function () { + $(this).data("selectizeCount", $(this).data("selectizeCount") + 1); + $(this).show(); + }); + }); + + selectize.on('item_remove', function (value, $item) { + // When no items in the filter, show all rows again + if (selectize.items.length === 0) { + $('.dataTable tbody').find('tr').show(); + } else { + // Find all rows which have the delete role, decrease their counter by 1 + $(".roleId-" + value).closest("tr").each(function () { + $(this).data("selectizeCount", $(this).data("selectizeCount") - 1); + // If counter is 0, hide the row (no filter given to show the row anymore) + if ($(this).data("selectizeCount") === 0) { + $(this).closest("tr").hide(); + } + }); + } + }); + } + + $("form input").keydown(function(e) { + if (e.keyCode === 13) e.preventDefault(); + }); +});
\ No newline at end of file diff --git a/modules-available/permissionmanager/config.json b/modules-available/permissionmanager/config.json new file mode 100644 index 00000000..c92e917a --- /dev/null +++ b/modules-available/permissionmanager/config.json @@ -0,0 +1,4 @@ +{ + "category":"main.content", + "dependencies": [ "locations", "js_stupidtable", "bootstrap_switch", "js_selectize" ] +} diff --git a/modules-available/permissionmanager/inc/getpermissiondata.inc.php b/modules-available/permissionmanager/inc/getpermissiondata.inc.php new file mode 100644 index 00000000..5114f4ef --- /dev/null +++ b/modules-available/permissionmanager/inc/getpermissiondata.inc.php @@ -0,0 +1,111 @@ +<?php + +class GetPermissionData { + + // get UserIDs, User Login Names, User Roles + public static function getUserData() { + $res = self::queryUserData(); + $userdata= array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $userdata[$row['userid'].' '.$row['login']][] = array( + 'roleId' => $row['roleId'], + 'roleName' => $row['roleName'] + ); + } + $data = array(); + foreach($userdata AS $user => $roles) { + $user = explode(" ", $user, 2); + $data[] = array( + 'userid' => $user[0], + 'username' => $user[1], + 'roles' => $roles + ); + } + return $data; + } + + // get LocationIDs, Location Names, Roles of each Location + public static function getLocationData() { + $res = self::queryLocationData(); + $locdata = array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $locdata[$row['locid'].' '.$row['locname']][] = array( + 'roleId' => $row['roleId'], + 'roleName' => $row['roleName'] + ); + } + $data = array(); + foreach($locdata AS $loc => $roles) { + $loc = explode(" ", $loc, 2); + $data[] = array( + 'locid' => $loc[0], + 'locname' => $loc[1], + 'roles' => $roles + ); + } + return $data; + } + + // get all roles from database (id and name) + public static function getRoles() { + $res = Database::simpleQuery("SELECT id, name FROM role ORDER BY name ASC"); + $data = array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $data[] = array( + 'roleId' => $row['id'], + 'roleName' => $row['name'] + ); + } + return $data; + } + + public static function getLocations($selected) { + $res = Database::simplequery("SELECT locationid, locationname FROM location"); + $data = array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $data[] = array('locid' => $row['locationid'], 'locName' => $row['locationname'], + 'selected' => in_array($row['locationid'], $selected) ? "selected" : ""); + } + return $data; + } + + public static function getRoleData($roleId) { + $query = "SELECT id, name FROM role WHERE id = :roleId"; + $data = Database::queryFirst($query, array("roleId" => $roleId)); + $query = "SELECT roleid, locid FROM role_x_location WHERE roleid = :roleId"; + $res = Database::simpleQuery($query, array("roleId" => $roleId)); + $data["locations"] = array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $data["locations"][] = $row['locid']; + } + $query = "SELECT roleid, permissionid FROM role_x_permission WHERE roleid = :roleId"; + $res = Database::simpleQuery($query, array("roleId" => $roleId)); + $data["permissions"] = array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $data["permissions"][] = $row['permissionid']; + } + return $data; + } + + // UserID, User Login Name, Roles of each User + private static function queryUserData() { + $res = Database::simpleQuery("SELECT user.userid AS userid, user.login AS login, role.name AS roleName, role.id AS roleId + FROM user + LEFT JOIN user_x_role ON user.userid = user_x_role.userid + LEFT JOIN role ON user_x_role.roleid = role.id + "); + return $res; + } + + // LocationID, Location Name, Roles of each Location + private static function queryLocationData() { + $res = Database::simpleQuery("SELECT location.locationid AS locid, location.locationname AS locname, role.name AS roleName, role.id AS roleId + FROM location + LEFT JOIN role_x_location ON location.locationid = role_x_location.locid + LEFT JOIN role ON role_x_location.roleid = role.id + ORDER BY location.locationname + "); + return $res; + } + +}
\ No newline at end of file diff --git a/modules-available/permissionmanager/inc/permissiondbupdate.inc.php b/modules-available/permissionmanager/inc/permissiondbupdate.inc.php new file mode 100644 index 00000000..87c989fa --- /dev/null +++ b/modules-available/permissionmanager/inc/permissiondbupdate.inc.php @@ -0,0 +1,57 @@ +<?php + +class PermissionDbUpdate { + + // insert new user_x_role to database. "ignore" to ignore duplicate entry try + public static function addRoleToUser($users, $roles) { + foreach($users AS $user) { + foreach ($roles AS $role) { + $query = "INSERT IGNORE INTO user_x_role (userid, roleid) VALUES (:user, :role)"; + Database::exec($query, array("user" => $user, "role" => $role)); + } + } + } + + // remove user_x_role entry from database + public static function removeRoleFromUser($users, $roles) { + foreach($users AS $user) { + foreach ($roles AS $role) { + $query = "DELETE FROM user_x_role WHERE userid = :user AND roleid = :role"; + Database::exec($query, array("user" => $user, "role" => $role)); + } + } + } + + // delete role, delete user_x_role relationships, delete role_x_location relationships, delete role_x_permission relationships + public static function deleteRole($id) { + $query = "DELETE FROM role WHERE id = :id"; + Database::exec($query, array("id" => $id)); + $query = "DELETE FROM user_x_role WHERE roleid = :id"; + Database::exec($query, array("id" => $id)); + $query = "DELETE FROM role_x_location WHERE roleid = :id"; + Database::exec($query, array("id" => $id)); + $query = "DELETE FROM role_x_permission WHERE roleid = :id"; + Database::exec($query, array("id" => $id)); + } + + public static function saveRole($roleName, $locations, $permissions, $role = NULL) { + if ($role) { + Database::exec("UPDATE role SET name = :roleName WHERE id = :role", + array("roleName" => $roleName, "role" => $role)); + Database::exec("DELETE FROM role_x_location WHERE roleid = :role", array("role" => $role)); + Database::exec("DELETE FROM role_x_permission WHERE roleid = :role", array("role" => $role)); + } else { + Database::exec("INSERT INTO role (name) VALUES (:roleName)", array("roleName" => $roleName)); + $role = Database::lastInsertId(); + } + foreach ($locations as $locID) { + Database::exec("INSERT INTO role_x_location (roleid, locid) VALUES (:role, :locid)", + array("role" => $role, "locid" => $locID)); + } + foreach ($permissions as $permission) { + Database::exec("INSERT INTO role_x_permission (roleid, permissionid) VALUES (:role, :permission)", + array("role" => $role, "permission" => $permission)); + } + } + +} diff --git a/modules-available/permissionmanager/inc/permissionutil.inc.php b/modules-available/permissionmanager/inc/permissionutil.inc.php new file mode 100644 index 00000000..df877520 --- /dev/null +++ b/modules-available/permissionmanager/inc/permissionutil.inc.php @@ -0,0 +1,110 @@ +<?php + +class PermissionUtil +{ + public static function userHasPermission($userid, $permissionid, $locationid) { + $locations = array(); + if (!is_null($locationid)) { + $locations = Location::getLocationRootChain($locationid); + if (count($locations) == 0) return false; + else $locations[] = 0; + } + + $res = Database::simpleQuery("SELECT role_x_permission.permissionid as 'permissionid', + role_x_location.locid as 'locationid' + FROM user_x_role + INNER JOIN role_x_permission ON user_x_role.roleid = role_x_permission.roleid + LEFT JOIN role_x_location ON role_x_permission.roleid = role_x_location.roleid + WHERE user_x_role.userid = :userid", array("userid" => $userid)); + + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $userPermission = trim($row["permissionid"], "*"); + if (substr($permissionid, 0, strlen($userPermission)) === $userPermission + && (is_null($locationid) || in_array($row["locationid"], $locations))) { + return true; + } + } + return false; + } + + public static function getAllowedLocations($userid, $permissionid) { + + $res = Database::simpleQuery("SELECT role_x_permission.permissionid as 'permissionid', + role_x_location.locid as 'locationid' + FROM user_x_role + INNER JOIN role_x_permission ON user_x_role.roleid = role_x_permission.roleid + LEFT JOIN role_x_location ON role_x_permission.roleid = role_x_location.roleid + WHERE user_x_role.userid = :userid", array("userid" => $userid)); + + $allowedLocations = array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $userPermission = trim($row["permissionid"], "*"); + if (!is_null($row["locationid"]) && substr($permissionid, 0, strlen($userPermission)) === $userPermission) { + $allowedLocations[$row["locationid"]] = 1; + } + } + $allowedLocations = array_keys($allowedLocations); + $locations = Location::getTree(); + if (count($allowedLocations) == 1 && $allowedLocations[0] == "0") { + $allowedLocations = array_map("intval", Location::extractIds($locations)); + } else { + $allowedLocations = self::getSublocations($locations, $allowedLocations); + } + return $allowedLocations; + } + + private static function getSublocations($tree, $locations) { + $result = array_flip($locations); + foreach ($tree as $location) { + if (array_key_exists("children", $location)) { + if (in_array($location["locationid"], $locations)) { + $result += array_flip(Location::extractIds($location["children"])); + } else { + $result += array_flip(self::getSublocations($location["children"], $locations)); + } + } + } + return array_keys($result); + } + + public static function getPermissions() + { + $permissions = array(); + foreach (glob("modules/*/permissions/permissions.json", GLOB_NOSORT) as $file) { + $data = json_decode(file_get_contents($file), true); + if (!is_array($data)) + continue; + preg_match('#^modules/([^/]+)/#', $file, $out); + $newData = array(); + foreach( $data as $k => $v ) { + $newData[] = $v; + $permissions = self::putInPermissionTree($out[1].".".$k, $v, $permissions); + } + } + ksort($permissions); + global $MENU_CAT_OVERRIDE; + $sortingOrder = $MENU_CAT_OVERRIDE; + foreach ($permissions as $module => $v) $sortingOrder[Module::get($module)->getCategory()][] = $module; + $permissions = array_replace(array_flip(call_user_func_array('array_merge', $sortingOrder)), $permissions); + foreach ($permissions as $module => $v) if (is_int($v)) unset($permissions[$module]); + + + return $permissions; + } + + private static function putInPermissionTree($permission, $description, $tree) + { + $subPermissions = explode('.', $permission); + $original =& $tree; + foreach ($subPermissions as $subPermission) { + if ($subPermission) { + if (!array_key_exists($subPermission, $tree)) { + $tree[$subPermission] = array(); + } + $tree =& $tree[$subPermission]; + } + } + $tree = $description; + return $original; + } +}
\ No newline at end of file diff --git a/modules-available/permissionmanager/install.inc.php b/modules-available/permissionmanager/install.inc.php new file mode 100644 index 00000000..8c882498 --- /dev/null +++ b/modules-available/permissionmanager/install.inc.php @@ -0,0 +1,27 @@ +<?php + +$res = array(); + +$res[] = tableCreate('role', " + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(200) NOT NULL, + PRIMARY KEY (`id`) +"); + +$res[] = tableCreate('user_x_role', " + `userid` int(10) unsigned NOT NULL, + `roleid` int(10) unsigned NOT NULL, + PRIMARY KEY (`userid`, `roleid`) +"); + +$res[] = tableCreate('role_x_location', " + `roleid` int(10) unsigned NOT NULL, + `locid` int(10) unsigned NOT NULL, + PRIMARY KEY (`roleid`, `locid`) +"); + +$res[] = tableCreate('role_x_permission', " + `roleid` int(10) unsigned NOT NULL, + `permissionid` varchar(200) NOT NULL, + PRIMARY KEY (`roleid`, `permissionid`) +"); diff --git a/modules-available/permissionmanager/lang/de/module.json b/modules-available/permissionmanager/lang/de/module.json new file mode 100644 index 00000000..aa73da91 --- /dev/null +++ b/modules-available/permissionmanager/lang/de/module.json @@ -0,0 +1,4 @@ +{ + "module_name": "Rechtemanager", + "page_title": "Rechtemanager" +}
\ No newline at end of file diff --git a/modules-available/permissionmanager/lang/de/template-tags.json b/modules-available/permissionmanager/lang/de/template-tags.json new file mode 100644 index 00000000..e9146d2c --- /dev/null +++ b/modules-available/permissionmanager/lang/de/template-tags.json @@ -0,0 +1,23 @@ +{ + "lang_Roles": "Rollen", + "lang_Users": "Nutzer", + "lang_Locations": "Räume", + "lang_addRole": "Rolle zuweisen", + "lang_removeRole": "Rolle entfernen", + "lang_newRole": "Rolle anlegen", + "lang_Selected": "Ausgewählt", + "lang_Edit": "Bearbeiten", + "lang_Remove": "Entfernen", + "lang_Delete": "Löschen", + "lang_removeCheck": "Sind Sie sich sicher, dass Sie diese Rolle entfernen wollen?", + "lang_deleteCheck": "Sind Sie sich sicher, dass Sie diese Rolle löschen wollen?", + "lang_emptyNameWarning": "Der Name der Rolle darf nicht leer sein!", + "lang_Name": "Name", + "lang_Cancel": "Abbrechen", + "lang_Save": "Speichern", + "lang_all": "alle", + "lang_selected": "ausgewählte", + "lang_Permissions": "Rechte", + "lang_selectizePlaceholder": "Nach Rollen filtern...", + "lang_searchPlaceholder": "Nach Rollen suchen..." +}
\ No newline at end of file diff --git a/modules-available/permissionmanager/lang/en/module.json b/modules-available/permissionmanager/lang/en/module.json new file mode 100644 index 00000000..5a5c838b --- /dev/null +++ b/modules-available/permissionmanager/lang/en/module.json @@ -0,0 +1,4 @@ +{ + "module_name": "Permission Manager", + "page_title": "Permission Manager" +}
\ No newline at end of file diff --git a/modules-available/permissionmanager/lang/en/template-tags.json b/modules-available/permissionmanager/lang/en/template-tags.json new file mode 100644 index 00000000..ce35a6ce --- /dev/null +++ b/modules-available/permissionmanager/lang/en/template-tags.json @@ -0,0 +1,23 @@ +{ + "lang_Roles": "Roles", + "lang_Users": "Users", + "lang_Locations": "Locations", + "lang_addRole": "Add Role", + "lang_removeRole": "Remove Role", + "lang_newRole": "New Role", + "lang_Selected": "Selected", + "lang_Edit": "Edit", + "lang_Remove": "Remove", + "lang_Delete": "Delete", + "lang_removeCheck": "Are you sure you want to remove this role?", + "lang_deleteCheck": "Are you sure you want to delete this role?", + "lang_emptyNameWarning": "Role name can not be empty!", + "lang_Name": "Name", + "lang_Cancel": "Cancel", + "lang_Save": "Save", + "lang_all": "all", + "lang_selected": "selected", + "lang_Permissions": "Permissions", + "lang_selectizePlaceholder": "Filter for roles...", + "lang_searchPlaceholder": "Search for roles..." +}
\ No newline at end of file diff --git a/modules-available/permissionmanager/page.inc.php b/modules-available/permissionmanager/page.inc.php new file mode 100644 index 00000000..7f288fd9 --- /dev/null +++ b/modules-available/permissionmanager/page.inc.php @@ -0,0 +1,144 @@ +<?php + +class Page_PermissionManager extends Page +{ + + /** + * Called before any page rendering happens - early hook to check parameters etc. + */ + protected function doPreprocess() + { + User::load(); + + if (!User::isLoggedIn()) { + Message::addError('main.no-permission'); + Util::redirect('?do=Main'); // does not return + } + + $action = Request::any('action', 'show', 'string'); + if ($action === 'addRoleToUser') { + $users = Request::post('users', ''); + $roles = Request::post('roles', ''); + PermissionDbUpdate::addRoleToUser($users, $roles); + } elseif ($action === 'removeRoleFromUser') { + $users = Request::post('users', ''); + $roles = Request::post('roles', ''); + PermissionDbUpdate::removeRoleFromUser($users, $roles); + } elseif ($action === 'deleteRole') { + $id = Request::post('deleteId', false, 'string'); + PermissionDbUpdate::deleteRole($id); + } elseif ($action === 'saveRole') { + $roleID = Request::post("roleid", false); + $roleName = Request::post("roleName"); + $locations = Request::post("allLocations", "off") == "on" ? array(0) : Request::post("locations"); + $permissions = Request::post("allPermissions", "off") == "on" ? array("*") : Request::post("permissions");; + PermissionDbUpdate::saveRole($roleName, $locations, $permissions, $roleID); + } + } + + /** + * Menu etc. has already been generated, now it's time to generate page content. + */ + protected function doRender() + { + $show = Request::get("show", "roles"); + + // switch between tables, but always show menu to switch tables + if ( $show === 'roles' || $show === 'users' || $show === 'locations' ) { + // get menu button colors + $buttonColors = self::setButtonColors($show); + + $data = array(); + + Render::openTag('div', array('class' => 'row')); + Render::addtemplate('_page', $buttonColors); + Render::closeTag('div'); + + if ($show === "roles") { + $data = array("roles" => GetPermissionData::getRoles()); + Render::addTemplate('rolestable', $data); + } elseif ($show === "users") { + $data = array("user" => GetPermissionData::getUserData(), "roles" => GetPermissionData::getRoles()); + Render::addTemplate('userstable', $data); + } elseif ($show === "locations") { + $data = array("location" => GetPermissionData::getLocationData(), "roles" => GetPermissionData::getRoles()); + Render::addTemplate('locationstable', $data); + } + } elseif ($show === "roleEditor") { + $data = array(); + + $roleID = Request::get("roleid", false); + $selectedLocations = array(); + if ($roleID) { + $roleData = GetPermissionData::getRoleData($roleID); + $data["roleid"] = $roleID; + $data["roleName"] = $roleData["name"]; + if (count($roleData["locations"]) == 1 && $roleData["locations"][0] == 0) { + $data["allLocChecked"] = "checked"; + $data["selectizeClass"] = "faded unclickable"; + } else { + $data["allLocChecked"] = ""; + $data["selectizeClass"] = ""; + $selectedLocations = $roleData["locations"]; + } + if (count($roleData["permissions"]) == 1 && $roleData["permissions"][0] == "*") { + $data["allPermChecked"] = "checked"; + $data["permissionsClass"] = "faded unclickable"; + } else { + $data["allPermChecked"] = ""; + $data["permissionsClass"] = ""; + $data["selectedPermissions"] = implode(" ", $roleData["permissions"]); + } + } + + $permissions = PermissionUtil::getPermissions(); + + $data["locations"] = GetPermissionData::getLocations($selectedLocations); + $data["moduleNames"] = array(); + foreach (array_keys($permissions) as $moduleid) { + $data["moduleNames"][] = array("id" => $moduleid, "name" => Module::get($moduleid)->getDisplayName()); + } + $data["permissionHTML"] = self::generatePermissionHTML($permissions, "*"); + Render::addTemplate('roleeditor', $data); + + } + } + + // Menu: Selected table is shown in blue (btn-primary) + private function setButtonColors($show) { + if ($show === 'roles') { + $buttonColors['rolesButtonClass'] = 'btn-primary'; + $buttonColors['usersButtonClass'] = 'btn-default'; + $buttonColors['locationsButtonClass'] = 'btn-default'; + } elseif ($show === 'users') { + $buttonColors['rolesButtonClass'] = 'btn-default'; + $buttonColors['usersButtonClass'] = 'btn-primary'; + $buttonColors['locationsButtonClass'] = 'btn-default'; + } elseif ($show === 'locations') { + $buttonColors['rolesButtonClass'] = 'btn-default'; + $buttonColors['usersButtonClass'] = 'btn-default'; + $buttonColors['locationsButtonClass'] = 'btn-primary'; + } else { + $buttonColors['rolesButtonClass'] = 'btn-default'; + $buttonColors['usersButtonClass'] = 'btn-default'; + $buttonColors['locationsButtonClass'] = 'btn-default'; + } + + return $buttonColors; + } + + private static function generatePermissionHTML($subPermissions, $permString) + { + $genModuleBox = $permString == "*"; + $res = ""; + foreach ($subPermissions as $k => $v) { + $res .= Render::parse($genModuleBox ? "modulepermissionbox" : (is_array($v) ? "permissiontreenode" : "permission"), + array("id" => $genModuleBox ? $k : $permString.".".$k, + "name" => $genModuleBox ? Module::get($k)->getDisplayName(): $k, + "HTML" => is_array($v) ? self::generatePermissionHTML($v, $genModuleBox ? $k : $permString.".".$k) : "", + "description" => $v)); + } + return $res; + } + +} diff --git a/modules-available/permissionmanager/style.css b/modules-available/permissionmanager/style.css new file mode 100644 index 00000000..504df511 --- /dev/null +++ b/modules-available/permissionmanager/style.css @@ -0,0 +1,93 @@ +#switchForm { + text-align: center; + margin-bottom: 50px; +} + +#saveButton { + margin-right: 10px; +} + + +#roleName { + width: 200px; + display: inline-block; + margin-left: 20px; +} + +.table { + margin-top: 20px; +} + +.table > tbody > tr > td { + vertical-align: middle; + height: 50px; +} + +.scrollingTable { + height: 500px; + overflow: auto; +} + +.customSpanMargin { + display: inline-block; + margin-top: 2px; + margin-bottom: 2px; +} + +.panel-primary > .panel-heading { + background-image: none; +} + +.panel, .row { + margin-bottom: 20px; +} + +.list-group, .checkbox { + margin: 0; +} + +.faded { + opacity: 0.6; +} + +.unclickable { + pointer-events: none; +} + +input[type='checkbox']:disabled { + cursor: inherit; +} + +.module-toggle-group { + width: 100%; + margin-top: 20px; +} + +.module-container { + -moz-column-gap: 20px; + -webkit-column-gap: 20px; + column-gap: 20px; +} + + +.module-container div { + display: inline-block; + width: 100%; +} + + +@media (max-width: 767px) { + .module-container { + -moz-column-count: 1; + -webkit-column-count: 1; + column-count: 1; + } +} + +@media (min-width: 768px) { + .module-container { + -moz-column-count: 2; + -webkit-column-count: 2; + column-count: 2; + } +} diff --git a/modules-available/permissionmanager/templates/_page.html b/modules-available/permissionmanager/templates/_page.html new file mode 100644 index 00000000..3b436eda --- /dev/null +++ b/modules-available/permissionmanager/templates/_page.html @@ -0,0 +1,20 @@ +<form id="switchForm" method="GET" action="?do=permissionmanager"> + <input type="hidden" name="do" value="permissionmanager"> + + <div class="btn-group"> + <button class="btn {{rolesButtonClass}}" type="submit" name="show" value="roles"> + <span class="glyphicon glyphicon-education"></span> + {{lang_Roles}} + </button> + + <button class="btn {{usersButtonClass}}" type="submit" name="show" value="users"> + <span class="glyphicon glyphicon-user"></span> + {{lang_Users}} + </button> + + <button class="btn {{locationsButtonClass}}" type="submit" name="show" value="locations"> + <span class="glyphicon glyphicon-home"></span> + {{lang_Locations}} + </button> + </div> +</form>
\ No newline at end of file diff --git a/modules-available/permissionmanager/templates/locationstable.html b/modules-available/permissionmanager/templates/locationstable.html new file mode 100644 index 00000000..79b0a076 --- /dev/null +++ b/modules-available/permissionmanager/templates/locationstable.html @@ -0,0 +1,37 @@ +<div class="row"> + <div class="col-md-4"></div> + <div class="col-md-4"> + <select multiple name="roles[]" id="select-role"> + <option value>{{lang_selectizePlaceholder}}</option> + {{#roles}} + <option value="{{roleId}}">{{roleName}}</option> + {{/roles}} + </select> + </div> +</div> + +<div class="row"> + <div class="col-md-12"> + <table id="locationsTable" class="table table-condensed table-hover stupidtable dataTable"> + <thead> + <tr> + <th data-sort="string">{{lang_Locations}}</th> + <th>{{lang_Roles}}</th> + </tr> + </thead> + + <tbody> + {{#location}} + <tr data-selectizeCount='0'> + <td>{{locname}}</td> + <td> + {{#roles}} + <span class="label label-default customSpanMargin roleId-{{roleId}}">{{roleName}}</span> + {{/roles}} + </td> + </tr> + {{/location}} + </tbody> + </table> + </div> +</div>
\ No newline at end of file diff --git a/modules-available/permissionmanager/templates/modulepermissionbox.html b/modules-available/permissionmanager/templates/modulepermissionbox.html new file mode 100644 index 00000000..69bde718 --- /dev/null +++ b/modules-available/permissionmanager/templates/modulepermissionbox.html @@ -0,0 +1,13 @@ +<div id='{{id}}' class='panel panel-primary module-box' style='display: none;'> + <div class='panel-heading'> + <div class='checkbox'> + <input name='permissions[]' value='{{id}}.*' type='checkbox' class='form-control'> + <label>{{name}}</label> + </div> + </div> + <div class='panel-body'> + <ul class='list-group'> + {{{HTML}}} + </ul> + </div> +</div>
\ No newline at end of file diff --git a/modules-available/permissionmanager/templates/permission.html b/modules-available/permissionmanager/templates/permission.html new file mode 100644 index 00000000..b28b9099 --- /dev/null +++ b/modules-available/permissionmanager/templates/permission.html @@ -0,0 +1,6 @@ +<li class='list-group-item' title="{{description}}" data-toggle="tooltip" data-placement="left"> + <div class='checkbox'> + <input name='permissions[]' value='{{id}}' type='checkbox' class='form-control'> + <label>{{name}}</label> + </div> +</li>
\ No newline at end of file diff --git a/modules-available/permissionmanager/templates/permissiontreenode.html b/modules-available/permissionmanager/templates/permissiontreenode.html new file mode 100644 index 00000000..47bff1f2 --- /dev/null +++ b/modules-available/permissionmanager/templates/permissiontreenode.html @@ -0,0 +1,9 @@ +<li class='list-group-item'> + <div class='checkbox'> + <input name='permissions[]' value='{{id}}.*' type='checkbox' class='form-control'> + <label>{{name}}</label> + </div> + <ul class='list-group'> + {{{HTML}}} + </ul> +</li> diff --git a/modules-available/permissionmanager/templates/roleeditor.html b/modules-available/permissionmanager/templates/roleeditor.html new file mode 100644 index 00000000..d1535332 --- /dev/null +++ b/modules-available/permissionmanager/templates/roleeditor.html @@ -0,0 +1,149 @@ +<form method="post" action="?do=permissionmanager"> + <input type="hidden" name="action" value="saveRole"> + <input type="hidden" name="token" value="{{token}}"> + <input type="hidden" name="roleid" value="{{roleid}}"> + <div class="row"> + <div class="col-md-12"> + <b>{{lang_Name}}:</b> + <input name="roleName" value="{{roleName}}" type="text" id="roleName" class="form-control"> + <button type="button" id="cancelButton" class="btn btn-default pull-right"><span class="glyphicon glyphicon-remove"></span> {{lang_Cancel}}</button> + <button type="submit" id="saveButton" class="btn btn-primary pull-right"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_Save}}</button> + </div> + </div> + <div class="row"> + <div class="col-md-3"> + <b style="line-height: 34px">{{lang_Locations}}:</b> + <div class="pull-right"><input name="allLocations" {{allLocChecked}} type="checkbox" id="allLocations"></div> + </div> + <div id="selectize-container" class="col-md-9 text-left {{selectizeClass}}"> + <select multiple name="locations[]" id="select-location"> + <option value></option> + {{#locations}} + <option value="{{locid}}" {{selected}}>{{locName}}</option> + {{/locations}} + </select> + </div> + </div> + <div class="row"> + <div class="col-md-3"> + <b style="line-height: 34px">{{lang_Permissions}}:</b> + <div class="pull-right"><input name="allPermissions" {{allPermChecked}} type="checkbox" id="allPermissions"></div> + <div class="btn-group-vertical module-toggle-group permissions-container {{permissionsClass}}" role="group"> + {{#moduleNames}} + <button id="button-{{id}}" type="button" class="btn btn-default module-toggle" data-moduleid="{{id}}">{{name}}</button> + {{/moduleNames}} + </div> + </div> + <div class="col-md-9 module-container permissions-container {{permissionsClass}}"> + {{{permissionHTML}}} + </div> + </div> +</form> + +<script type="application/javascript"> + + selectedPermissions = "{{selectedPermissions}}"; + + document.addEventListener("DOMContentLoaded", function () { + + $('#select-location').selectize({ + allowEmptyOption: false, + maxItems: null, + highlight: false, + hideSelected: true, + create: false, + plugins: [ "remove_button" ] + }); + + var allLocations = $("#allLocations"); + allLocations.bootstrapSwitch("size", "normal"); + allLocations.bootstrapSwitch("labelWidth", 1); + allLocations.bootstrapSwitch("onText", "{{lang_all}}"); + allLocations.bootstrapSwitch("offText", "{{lang_selected}}"); + + allLocations.on('switchChange.bootstrapSwitch', function(event, state) { + if (state) { + $("#selectize-container").addClass("faded unclickable"); + } else { + $("#selectize-container").removeClass("faded unclickable"); + } + }); + + var allPermissions = $("#allPermissions"); + allPermissions.bootstrapSwitch("size", "normal"); + allPermissions.bootstrapSwitch("labelWidth", 1); + allPermissions.bootstrapSwitch("onText", "{{lang_all}}"); + allPermissions.bootstrapSwitch("offText", "{{lang_selected}}"); + + + allPermissions.on('switchChange.bootstrapSwitch', function(event, state) { + if (state) { + $(".permissions-container").addClass("faded unclickable"); + } else { + $(".permissions-container").removeClass("faded unclickable"); + } + }); + + $(".module-toggle").click(function () { + var button = $(this); + var moduleBox = $("#" + button.data("moduleid")); + if (button.hasClass("btn-default")) { + button.removeClass("btn-default"); + button.addClass("btn-primary"); + moduleBox.show(); + } else { + button.removeClass("btn-primary"); + button.addClass("btn-default"); + moduleBox.hide(); + } + }); + + $(".module-container input[type=checkbox]").change(function () { + var parent = $(this).parent().parent(); + if (parent.hasClass("panel-heading")) parent = parent.parent(); + parent = parent.find("ul:first"); + parent.find("ul").removeClass("faded"); + var checkboxes = parent.find("input[type=checkbox]"); + if (parent.hasClass("faded")) { + checkboxes.prop("disabled", false); + checkboxes.prop("checked", false); + parent.removeClass("faded"); + } else { + checkboxes.prop("disabled", true); + checkboxes.prop("checked", true); + parent.addClass("faded"); + } + }); + + $("#cancelButton").click(function () { + window.location.replace("?do=permissionmanager&show=roles"); + }); + + $('form').submit(function () { + var name = $.trim($('#roleName').val()); + if (name === '') { + alert('{{lang_emptyNameWarning}}'); + return false; + } + }); + + var permissions = selectedPermissions.split(" "); + var arrayLength = permissions.length; + for (var i = 0; i < arrayLength; i++) { + var checkbox = $("input[type=checkbox][value='"+permissions[i]+"']"); + checkbox.trigger('change').attr('checked', 'checked'); + var moduleBox = checkbox.closest(".module-box"); + moduleBox.show(); + var button = $("#button-"+moduleBox.attr('id')); + button.removeClass("btn-default"); + button.addClass("btn-primary"); + } + + + $('[data-toggle="tooltip"]').tooltip({ + container: 'body', + trigger : 'hover' + }); + }); + +</script>
\ No newline at end of file diff --git a/modules-available/permissionmanager/templates/rolestable.html b/modules-available/permissionmanager/templates/rolestable.html new file mode 100644 index 00000000..a3e31e15 --- /dev/null +++ b/modules-available/permissionmanager/templates/rolestable.html @@ -0,0 +1,91 @@ +<form method="post" action="?do=permissionmanager"> + <input type="hidden" name="token" value="{{token}}"> + + <div class="row"> + <div class="col-md-4"> + <button class="btn btn-success" type="button" onclick="openRoleEditor()"><span class="glyphicon glyphicon-plus"></span> {{lang_newRole}}</button> + </div> + <div class="col-md-4"> + <input type="text" class="form-control" id="roleNameSearchField" onkeyup="searchFieldFunction()" placeholder="{{lang_searchPlaceholder}}"> + </div> + </div> + + <div class="row"> + <div class="col-md-12"> + <table id="rolesTable" class="table table-condensed table-hover stupidtable"> + <thead> + <tr> + <th data-sort="string">{{lang_Roles}}</th> + <th>{{lang_Edit}}</th> + <th>{{lang_Delete}}</th> + </tr> + </thead> + + <tbody> + {{#roles}} + <tr class="rolesRow"> + <td class="rolesData">{{roleName}}</td> + <td> + <a href="?do=permissionmanager&show=roleEditor&roleid={{roleId}}">{{lang_Edit}}</a> + </td> + <td> + <a href="#deleteModal" data-toggle="modal" data-target="#deleteModal" onclick="deleteRole('{{roleId}}')">{{lang_Delete}}</a> + </td> + </tr> + {{/roles}} + </tbody> + </table> + </div> + </div> + + + <!-- Modals --> + <div class ="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title" id="myModalLabel">{{lang_Delete}}</h4> + </div> + <div class="modal-body"> + {{lang_deleteCheck}} + </div> + <div class="modal-footer"> + <input type="hidden" id="deleteId" name="deleteId" value=""/> + <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button> + <button type="submit" name="action" value="deleteRole" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> {{lang_Delete}}</button> + </div> + </div> + </div> + </div> + +</form> + +<script> + function openRoleEditor() { + window.location.href = "?do=permissionmanager&show=roleEditor" + } + + function deleteRole($roleId) { + $(".modal-footer #deleteId").val($roleId); + } + + function searchFieldFunction() { + // Declare variables + var input, filter, table, trs, a, i; + input = document.getElementById('roleNameSearchField'); + filter = input.value.toUpperCase(); + table = document.getElementById("rolesTable"); + trs = table.getElementsByClassName('rolesRow'); + + // Loop through all list items, and hide those who don't match the search query + for (i = 0; i < trs.length; i++) { + a = trs[i].getElementsByClassName("rolesData")[0]; + if (a.innerHTML.toUpperCase().indexOf(filter) > -1) { + trs[i].style.display = ""; + } else { + trs[i].style.display = "none"; + } + } + } +</script>
\ No newline at end of file diff --git a/modules-available/permissionmanager/templates/userstable.html b/modules-available/permissionmanager/templates/userstable.html new file mode 100644 index 00000000..e794608c --- /dev/null +++ b/modules-available/permissionmanager/templates/userstable.html @@ -0,0 +1,170 @@ +<form method="post" action="?do=permissionmanager&show=users"> + <input type="hidden" name="token" value="{{token}}"> + + <div class="row"> + <div class="col-md-4"> + <button class="btn btn-success" type="button" data-toggle="modal" data-target="#addRoleToUserModal"><span class="glyphicon glyphicon-share-alt"></span> {{lang_addRole}}</button> + <button class="btn btn-danger" type="button" data-toggle="modal" data-target="#removeRoleFromUserModal"><span class="glyphicon glyphicon-trash"></span> {{lang_removeRole}}</button> + </div> + <div class="col-md-4 text-left"> + <select multiple name="roles[]" id="select-role"> + <option value>{{lang_selectizePlaceholder}}</option> + {{#roles}} + <option value="{{roleId}}">{{roleName}}</option> + {{/roles}} + </select> + </div> + </div> + + <div class="row"> + <div class="col-md-12"> + <table id="usersTable" class="table table-condensed table-hover stupidtable dataTable"> + <thead> + <tr> + <th data-sort="string">{{lang_Users}}</th> + <th>{{lang_Roles}}</th> + <th data-sort="int" data-sort-default="desc">{{lang_Selected}}</th> + </tr> + </thead> + + <tbody> + {{#user}} + <tr data-selectizeCount='0'> + <td>{{username}}</td> + <td> + {{#roles}} + <span class="label label-default customSpanMargin roleId-{{roleId}}">{{roleName}}</span> + {{/roles}} + </td> + <td data-sort-value="0"> + <div class="checkbox"> + <input id="{{userid}}" type="checkbox" name="users[]" value='{{userid}}'> + <label for="{{userid}}"></label> + </div> + </td> + </tr> + {{/user}} + </tbody> + </table> + </div> + </div> + + <!-- Modals --> + <div class ="modal fade" id="addRoleToUserModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title" id="myModalLabel">{{lang_addRole}}</h4> + </div> + <div class="modal-body"> + <div class="row"> + <div class="col-md-12 scrollingTable"> + <table id="addRoleToUserTable" class="table table-condensed table-hover stupidtable"> + <thead> + <tr> + <th data-sort="string">{{lang_Roles}}</th> + <th data-sort="int" data-sort-default="desc">{{lang_Selected}}</th> + </tr> + </thead> + + <tbody> + {{#roles}} + <tr> + <td>{{roleName}}</td> + <td data-sort-value="0"> + <div class="checkbox"> + <input id="add{{roleId}}" type="checkbox" name="roles[]" value='{{roleId}}'> + <label for="add{{roleId}}"></label> + </div> + </td> + </tr> + {{/roles}} + </tbody> + </table> + </div> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button> + <button type="submit" name="action" value="addRoleToUser" class="btn btn-success" onclick="clearRemoveRoleModal()"><span class="glyphicon glyphicon-share-alt"></span> {{lang_addRole}}</button> + </div> + </div> + </div> + </div> + + <div class ="modal fade" id="removeRoleFromUserModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title" id="myModalLabel2">{{lang_Remove}}</h4> + </div> + <div class="modal-body"> + <div class="row"> + <div class="col-md-12 scrollingTable"> + <table id="removeRoleFromUserTable" class="table table-condensed table-hover stupidtable"> + <thead> + <tr> + <th data-sort="string">{{lang_Roles}}</th> + <th data-sort="int" data-sort-default="desc">{{lang_Selected}}</th> + </tr> + </thead> + + <tbody> + {{#roles}} + <tr> + <td>{{roleName}}</td> + <td data-sort-value="0"> + <div class="checkbox"> + <input id="remove{{roleId}}" type="checkbox" name="roles[]" value='{{roleId}}'> + <label for="remove{{roleId}}"></label> + </div> + </td> + </tr> + {{/roles}} + </tbody> + </table> + </div> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button> + <button type="submit" name="action" value="removeRoleFromUser" class="btn btn-danger" onclick="clearAddRoleModal()"><span class="glyphicon glyphicon-trash"></span> {{lang_Remove}}</button> + </div> + </div> + </div> + </div> +</form> + +<script> + document.addEventListener("DOMContentLoaded", function() { + // if checked,: mark green, else: unmark + $('input:checkbox').change(function() { + if ($(this).is(':checked')) { + $(this).closest("td").data("sort-value", 1); + $(this).closest("tr").css("background-color", "#f2ffe6"); + } else { + $(this).closest("td").data("sort-value", 0); + $(this).closest("tr").css("background-color", ""); + } + + }); + }); + + // if remove-Role button is clicked, uncheck all checkboxes in add-role modal so they aren't submitted too + function clearAddRoleModal () { + $('#addRoleToUserModal') + .find("input[type=checkbox]") + .prop("checked", "") + .end(); + } + + // if add-Role button is clicked, uncheck all checkboxes in remove-role modal so they aren't submitted too + function clearRemoveRoleModal() { + $('#removeRoleFromUserModal') + .find("input[type=checkbox]") + .prop("checked", "") + .end(); + } +</script>
\ No newline at end of file diff --git a/modules-available/rebootcontrol/clientscript.js b/modules-available/rebootcontrol/clientscript.js deleted file mode 100644 index d3ecbe48..00000000 --- a/modules-available/rebootcontrol/clientscript.js +++ /dev/null @@ -1,22 +0,0 @@ -document.addEventListener("DOMContentLoaded", function() { - var table = $("table"); - table.stupidtable({ - "ipsort":function(a,b){ - var aa = a.split("."); - var bb = b.split("."); - - var resulta = aa[0]*0x1000000 + aa[1]*0x10000 + aa[2]*0x100 + aa[3]*1; - var resultb = bb[0]*0x1000000 + bb[1]*0x10000 + bb[2]*0x100 + bb[3]*1; - - return resulta-resultb; - } - }); - - table.on("aftertablesort", function (event, data) { - var th = $(this).find("th"); - th.find(".arrow").remove(); - var dir = $.fn.stupidtable.dir; - var arrow = data.direction === dir.ASC ? "down" : "up"; - th.eq(data.column).append(' <span class="arrow glyphicon glyphicon-chevron-'+arrow+'"></span>'); - }); -});
\ No newline at end of file diff --git a/modules-available/rebootcontrol/templates/_page.html b/modules-available/rebootcontrol/templates/_page.html index 065a9f01..4d2bf60e 100644 --- a/modules-available/rebootcontrol/templates/_page.html +++ b/modules-available/rebootcontrol/templates/_page.html @@ -18,11 +18,11 @@ </div> <div class="row"> <div class="col-md-12"> - <table class="table table-condensed table-hover" id="dataTable"> + <table class="table table-condensed table-hover stupidtable" id="dataTable"> <thead> <tr> <th data-sort="string">{{lang_client}}</th> - <th data-sort="ipsort">{{lang_ip}}</th> + <th data-sort="ipv4">{{lang_ip}}</th> <th data-sort="string">{{lang_status}}</th> <th data-sort="string">{{lang_session}}</th> <th data-sort="string">{{lang_user}}</th> diff --git a/modules-available/rebootcontrol/templates/status.html b/modules-available/rebootcontrol/templates/status.html index 35bbe42f..c2fdab46 100644 --- a/modules-available/rebootcontrol/templates/status.html +++ b/modules-available/rebootcontrol/templates/status.html @@ -10,11 +10,11 @@ <div data-tm-id="{{taskId}}" data-tm-log="error" data-tm-callback="updateStatus"></div> <div> - <table class="table table-hover" id="dataTable"> + <table class="table table-hover stupidtable" id="dataTable"> <thead> <tr> <th data-sort="string">{{lang_client}}</th> - <th data-sort="ipsort">{{lang_ip}}</th> + <th data-sort="ipv4">{{lang_ip}}</th> <th data-sort="string"> {{lang_status}} </th> diff --git a/modules-available/statistics_reporting/page.inc.php b/modules-available/statistics_reporting/page.inc.php index 52accaea..1a85bfa8 100644 --- a/modules-available/statistics_reporting/page.inc.php +++ b/modules-available/statistics_reporting/page.inc.php @@ -52,16 +52,25 @@ class Page_Statistics_Reporting extends Page // Export - handle in doPreprocess so we don't render the menu etc. if ($this->action === 'export') { - $this->doExport(); - // Does not return + if (User::hasPermission("table.export") && User::hasPermission("table.view.$this->type")) { + $this->doExport(); + // Does not return + } else { + Message::addError('main.no-permission'); + } } // Get report - fetch data exactly the way it would automatically be reported // so the user can know what is going on if ($this->action === 'getreport') { - $report = RemoteReport::generateReport(time()); - Header('Content-Disposition: attachment; filename=remote-report.json'); - Header('Content-Type: application/json; charset=utf-8'); - die(json_encode($report)); + if(User::hasPermission("reporting.download")) { + $report = RemoteReport::generateReport(strtotime('-7 days'), time('now')); + Header('Content-Disposition: attachment; filename=remote-report.json'); + Header('Content-Type: application/json; charset=utf-8'); + die(json_encode($report)); + } else { + Message::addError('main.no-permission'); + } + } } @@ -124,7 +133,12 @@ class Page_Statistics_Reporting extends Page Render::addTemplate('columnChooser', $data); $data['data'] = $this->fetchData(GETDATA_PRINTABLE); - Render::addTemplate('table-' . $this->type, $data); + + if (User::hasPermission("table.view.$this->type")) + Render::addTemplate('table-' . $this->type, $data); + else + Message::addError('main.no-permission'); + } } @@ -132,8 +146,8 @@ class Page_Statistics_Reporting extends Page { $this->action = Request::any('action', false, 'string'); if ($this->action === 'setReporting') { - if (!User::isLoggedIn()) { - die("No."); + if (!User::hasPermission("reporting.change")) { + die("Permission denied."); } $state = Request::post('reporting', false, 'string'); if ($state === false) { @@ -266,9 +280,28 @@ class Page_Statistics_Reporting extends Page } } } + // only show locations which you have permission for + $filterLocs = User::getAllowedLocations("table.view.location"); + foreach ($data as $key => $row) { + if (!in_array($row['locationId'], $filterLocs)) { + unset($data[$key]); + } + } + // correct indexing of array after deletions + $data = array_values($data); return $data; case 'client': - return GetData::perClient($flags); + $data = GetData::perClient($flags); + // only show clients from locations which you have permission for + $filterLocs = User::getAllowedLocations("table.view.location"); + foreach ($data as $key => $row) { + if (!in_array($row['locationId'], $filterLocs)) { + unset($data[$key]); + } + } + // correct indexing of array after deletions + $data = array_values($data); + return $data; case 'user': return GetData::perUser($flags); case 'vm': diff --git a/modules-available/statistics_reporting/permissions/permissions.json b/modules-available/statistics_reporting/permissions/permissions.json new file mode 100644 index 00000000..14f4ff3b --- /dev/null +++ b/modules-available/statistics_reporting/permissions/permissions.json @@ -0,0 +1,10 @@ +{ + "table.view.total": "View total table.", + "table.view.location": "View location table.", + "table.view.client": "View client table.", + "table.view.user": "View user table.", + "table.view.vm": "View lecture table.", + "table.export": "Export tables as JSON/CSV/XML.", + "reporting.download": "Download weekly report.", + "reporting.change": "Change weekly reporting settings." +}
\ No newline at end of file diff --git a/modules-available/statistics_reporting/style.css b/modules-available/statistics_reporting/style.css index 81dc74b0..3cd6653f 100644 --- a/modules-available/statistics_reporting/style.css +++ b/modules-available/statistics_reporting/style.css @@ -35,8 +35,4 @@ margin-left: -1.5em; text-align: center; line-height: 1.6em; -} - -th[data-sort] { - cursor: pointer; -} +}
\ No newline at end of file diff --git a/modules-available/statistics_reporting/templates/columnChooser.html b/modules-available/statistics_reporting/templates/columnChooser.html index e4069be9..d0408b6f 100644 --- a/modules-available/statistics_reporting/templates/columnChooser.html +++ b/modules-available/statistics_reporting/templates/columnChooser.html @@ -112,15 +112,6 @@ }, }); - var table = $("table").stupidtable(); - table.on("aftertablesort", function (event, data) { - var th = $(this).find("th"); - th.find(".arrow").remove(); - var dir = $.fn.stupidtable.dir; - var arrow = data.direction === dir.ASC ? "up" : "down"; - th.eq(data.column).append(' <span class="arrow glyphicon glyphicon-chevron-'+arrow+'"></span>'); - }); - $(".locationLink").click(function(e) { e.preventDefault(); var form = $('#controlsForm'); diff --git a/modules-available/statistics_reporting/templates/table-client.html b/modules-available/statistics_reporting/templates/table-client.html index be504cef..59153e01 100644 --- a/modules-available/statistics_reporting/templates/table-client.html +++ b/modules-available/statistics_reporting/templates/table-client.html @@ -1,4 +1,4 @@ -<table id="table-perclient" class="table table-condensed table-striped"> +<table id="table-perclient" class="table table-condensed table-striped stupidtable"> <thead> <tr> <th data-sort="string" class="text-left col-md-4">{{lang_hostname}}</th> diff --git a/modules-available/statistics_reporting/templates/table-location.html b/modules-available/statistics_reporting/templates/table-location.html index ccac623d..a0867208 100644 --- a/modules-available/statistics_reporting/templates/table-location.html +++ b/modules-available/statistics_reporting/templates/table-location.html @@ -1,4 +1,4 @@ -<table id="table-perlocation" class="table table-condensed table-striped"> +<table id="table-perlocation" class="table table-condensed table-striped stupidtable"> <thead> <tr> <th data-sort="string" class="text-left col-md-2">{{lang_location}}</th> diff --git a/modules-available/statistics_reporting/templates/table-total.html b/modules-available/statistics_reporting/templates/table-total.html index 4048a178..8d5d7571 100644 --- a/modules-available/statistics_reporting/templates/table-total.html +++ b/modules-available/statistics_reporting/templates/table-total.html @@ -1,4 +1,4 @@ -<table id="table-total" class="table table-condensed table-striped"> +<table id="table-total" class="table table-condensed table-striped stupidtable"> <thead> <tr> <th class="text-left col-md-2"></th> diff --git a/modules-available/statistics_reporting/templates/table-user.html b/modules-available/statistics_reporting/templates/table-user.html index 5c2ba56f..ea4d20f5 100644 --- a/modules-available/statistics_reporting/templates/table-user.html +++ b/modules-available/statistics_reporting/templates/table-user.html @@ -1,4 +1,4 @@ -<table id="table-peruser" class="table table-condensed table-striped"> +<table id="table-peruser" class="table table-condensed table-striped stupidtable"> <thead> <tr> <th data-sort="string" class="text-left col-md-4">{{lang_user}}</th> diff --git a/modules-available/statistics_reporting/templates/table-vm.html b/modules-available/statistics_reporting/templates/table-vm.html index 9a775709..4ffb4df2 100644 --- a/modules-available/statistics_reporting/templates/table-vm.html +++ b/modules-available/statistics_reporting/templates/table-vm.html @@ -1,4 +1,4 @@ -<table id="table-pervm" class="table table-condensed table-striped"> +<table id="table-pervm" class="table table-condensed table-striped stupidtable"> <thead> <tr> <th data-sort="string" class="text-left col-md-4">{{lang_vm}}</th> |