summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--inc/user.inc.php17
-rw-r--r--modules-available/js_stupidtable/clientscript.js168
-rw-r--r--modules-available/js_stupidtable/style.css3
-rw-r--r--modules-available/permissionmanager/api.inc.php7
-rw-r--r--modules-available/permissionmanager/clientscript.js48
-rw-r--r--modules-available/permissionmanager/config.json4
-rw-r--r--modules-available/permissionmanager/inc/getpermissiondata.inc.php111
-rw-r--r--modules-available/permissionmanager/inc/permissiondbupdate.inc.php57
-rw-r--r--modules-available/permissionmanager/inc/permissionutil.inc.php110
-rw-r--r--modules-available/permissionmanager/install.inc.php27
-rw-r--r--modules-available/permissionmanager/lang/de/module.json4
-rw-r--r--modules-available/permissionmanager/lang/de/template-tags.json23
-rw-r--r--modules-available/permissionmanager/lang/en/module.json4
-rw-r--r--modules-available/permissionmanager/lang/en/template-tags.json23
-rw-r--r--modules-available/permissionmanager/page.inc.php144
-rw-r--r--modules-available/permissionmanager/style.css93
-rw-r--r--modules-available/permissionmanager/templates/_page.html20
-rw-r--r--modules-available/permissionmanager/templates/locationstable.html37
-rw-r--r--modules-available/permissionmanager/templates/modulepermissionbox.html13
-rw-r--r--modules-available/permissionmanager/templates/permission.html6
-rw-r--r--modules-available/permissionmanager/templates/permissiontreenode.html9
-rw-r--r--modules-available/permissionmanager/templates/roleeditor.html149
-rw-r--r--modules-available/permissionmanager/templates/rolestable.html91
-rw-r--r--modules-available/permissionmanager/templates/userstable.html170
-rw-r--r--modules-available/rebootcontrol/clientscript.js22
-rw-r--r--modules-available/rebootcontrol/templates/_page.html4
-rw-r--r--modules-available/rebootcontrol/templates/status.html4
-rw-r--r--modules-available/statistics_reporting/page.inc.php53
-rw-r--r--modules-available/statistics_reporting/permissions/permissions.json10
-rw-r--r--modules-available/statistics_reporting/style.css6
-rw-r--r--modules-available/statistics_reporting/templates/columnChooser.html9
-rw-r--r--modules-available/statistics_reporting/templates/table-client.html2
-rw-r--r--modules-available/statistics_reporting/templates/table-location.html2
-rw-r--r--modules-available/statistics_reporting/templates/table-total.html2
-rw-r--r--modules-available/statistics_reporting/templates/table-user.html2
-rw-r--r--modules-available/statistics_reporting/templates/table-vm.html2
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">&times;</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">&times;</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">&times;</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>