====== SuperACL Plugin ====== ---- plugin ---- description: Manage ACL settings globally and filter ACL table view author : Pascal Bihler email : bihler@iai.uni-bonn.de type : admin lastupdate : 2008-05-25 compatible : 2010-11-07, 2006-11-06, 2007-06-26, 2008-05-05 depends : conflicts : similar : acl tags : acl, users, groups downloadurl: http://pb.wh4f.de/dokuwiki/superacl2.zip ---- ===== Current edition ===== //If you're looking for the SuperACL plugin for pre-2008-05-05 DokuWiki, please scroll down.// This plugin is based on [[plugin:acl]], but allows to filter the bottom table of ACLs, so that you don't lose the overview over all the rules applying to your currently selected page/namespace. ==== Download / Installation ==== Download the plugin here (manually or via Plugin Manager): http://pb.wh4f.de/dokuwiki/superacl2.zip //:!: Use this download only for DokuWiki 2008-05-05 and newer!// ===== Former edition ===== //The following only corresponds to the SuperACL-version for DokuWiki releases 2006-11-06 and 2007-06-26// This plugin is based on [[plugin:acl]], but allows to select the namespace where you want to edit access rights, in opposite to the original plugin which requires you to browse a page in the corresponding namespace. This results in better overview about the global ACL situation in the wiki. Since in big wikis it takes some time to display all namespaces and all the pages of the selected namespace, the new version of SuperACL offers the configuration option to enable "Use AJAX". This will speedup all roundtrip processes (e.g. set an ACL) by deferring the population of the dropdown-selectors. ==== Download / Installation ==== Download the plugin here (manually or via Plugin Manager): http://pb.wh4f.de/dokuwiki/superacl.zip //:!: Use this download only for DokuWiki 2006-11-06 or 2007-06-26!// \\ Old version (without AJAX support): http://pb.wh4f.de/dokuwiki/superacl-2007-08-16.zip ==== Code ==== **admin.php:** * @author Frank Schubert */ // must be run within DokuWiki if(!defined('DOKU_INC')) die(); if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); require_once(DOKU_PLUGIN.'admin.php'); /** * All DokuWiki plugins to extend the admin function * need to inherit from this class */ class admin_plugin_superacl extends DokuWiki_Admin_Plugin { function admin_plugin_superacl(){ $this->setupLocale(); } /** * return some info */ function getInfo(){ return array( 'author' => 'Pascal Bihler', 'email' => 'bihler@iai.uni-bonn.de', 'date' => '2008-03-18', 'name' => 'SuperACL', 'desc' => 'Manage Access Control Lists for all workspaces', 'url' => 'http://www.dokuwiki.org/plugin:superacl', ); } /** * access for managers */ function forAdminOnly(){ return true; // set to false to allow Manager access to Super ACL } /** * return prompt for admin menu */ function getMenuText($language) { return $this->lang['admin_acl']; } /** * return sort order for position in admin menu */ function getMenuSort() { return 1; } /** * handle user request */ function handle() { global $AUTH_ACL; $cmd = $_REQUEST['acl_cmd']; $scope = $_REQUEST['acl_scope']; $type = $_REQUEST['acl_type']; $user = $_REQUEST['acl_user']; $perm = $_REQUEST['acl_perm']; if(is_array($perm)){ //use the maximum sort($perm); $perm = array_pop($perm); }else{ $perm = 0; } //sanitize $user = auth_nameencode($user); if($type == '@') $user = '@'.$user; if($user == '@all') $user = '@ALL'; //special group! (now case insensitive) $perm = (int) $perm; if($perm > AUTH_DELETE) $perm = AUTH_DELETE; //FIXME sanitize scope!!! //nothing to do? if(empty($cmd) || empty($scope) || empty($user)) return; if($cmd == 'save'){ $this->admin_acl_del($scope, $user); $this->admin_acl_add($scope, $user, $perm); }elseif($cmd == 'delete'){ $this->admin_acl_del($scope, $user); } // reload ACL config $AUTH_ACL = file(DOKU_CONF.'acl.auth.php'); } /** * ACL Output function * * print a table with all significant permissions for the * current id * * @author Frank Schubert * @author Andreas Gohr */ function html() { print $this->locale_xhtml('intro'); ptln('
'); ptln(''); //namespace selector $this->admin_superacl_select_ns(); //new $this->admin_superacl_html_new(); //current config $acls = $this->get_superacl_config($this->get_selected_ns()); foreach ($acls as $id => $acl){ $this->admin_superacl_html_current($id,$acl); } ptln('
'); ptln('
'); } /** * * Get current selected namespace (or namespace of $ID as alternative) * */ function get_selected_ns() { global $ID; $id = $_REQUEST['superacl_ns']; if (! $id) $id = getNS($ID); if (! $id) $id = '*'; return $id; } /** * * Get current selected acl_scope (or namespace of Selected NS as alternative) * */ function get_acl_scope() { $scope = $_REQUEST['acl_scope']; if (! $scope) $scope = $this->get_selected_ns() . ':*'; if (! $scope) $scope = '*'; return $scope; } /** * Get matching ACL lines for a namespace * * $id is namespacename, reads matching lines from $AUTH_ACL, * also reads ACLs from namespace * returns multi-array with key=pagename and value=array(user, acl) * * @author Pascal Bihler * @author Frank Schubert */ function get_superacl_config($id){ global $AUTH_ACL; $acl_config=array(); // match exact name $pages = $this->get_pages($id); foreach ($pages as $page_id) { if ($id != '*') $page_id = $id . ':' . $page_id; $matches = preg_grep('/^'.$page_id.'\s+.*/',$AUTH_ACL); if(count($matches)){ foreach($matches as $match){ $match = preg_replace('/#.*$/','',$match); //ignore comments $acl = preg_split('/\s+/',$match); //0 is pagename, 1 is user, 2 is acl $acl_config[$acl[0]][] = array( 'name' => $acl[1], 'perm' => $acl[2]); } } } $specific_found=array(); // match ns for(;$id !== false; $id = getNS($id)){ $id_pattern = str_replace('+', '\\+',str_replace('*', '\\*', $id)); $matches = preg_grep('/^'.$id_pattern.':\*\s+.*/',$AUTH_ACL); if(count($matches)){ foreach($matches as $match){ $match = preg_replace('/#.*$/','',$match); //ignore comments $acl = preg_split('/\s+/',$match); //0 is pagename, 1 is user, 2 is acl $acl_config[$acl[0]][] = array( 'name' => $acl[1], 'perm' => $acl[2]); $specific_found[]=$acl[1]; } } } //include *-config $matches = preg_grep('/^\*\s+.*/',$AUTH_ACL); if(count($matches)){ foreach($matches as $match){ $match = preg_replace('/#.*$/','',$match); //ignore comments $acl = preg_split('/\s+/',$match); // only include * for this user if not already found in ns if(!in_array($acl[1], $specific_found)){ //0 is pagename, 1 is user, 2 is acl $acl_config[$acl[0]][] = array( 'name' => $acl[1], 'perm' => $acl[2]); } } } //sort //FIXME: better sort algo: first sort by key, then sort by first value krsort($acl_config, SORT_STRING); return($acl_config); } /** * adds new acl-entry to conf/acl.auth.php * * @author Frank Schubert */ function admin_acl_add($acl_scope, $acl_user, $acl_level){ $acl_config = join("",file(DOKU_CONF.'acl.auth.php')); // max level for pagenames is edit if(strpos($acl_scope,'*') === false) { if($acl_level > AUTH_EDIT) $acl_level = AUTH_EDIT; } $new_acl = "$acl_scope\t$acl_user\t$acl_level\n"; $new_config = $acl_config.$new_acl; return io_saveFile(DOKU_CONF.'acl.auth.php', $new_config); } /** * remove acl-entry from conf/acl.auth.php * * @author Frank Schubert */ function admin_acl_del($acl_scope, $acl_user){ $acl_config = file(DOKU_CONF.'acl.auth.php'); $acl_pattern = '^'.preg_quote($acl_scope,'/').'\s+'.$acl_user.'\s+[0-8].*$'; // save all non!-matching #FIXME invert is available from 4.2.0 only! $new_config = preg_grep("/$acl_pattern/", $acl_config, PREG_GREP_INVERT); return io_saveFile(DOKU_CONF.'acl.auth.php', join('',$new_config)); } // --- HTML OUTPUT FUNCTIONS BELOW --- // /** * create dropdown with parent namespaces and pages of one namespace ID * * * * @author Pascal Bihler * @author Frank Schubert * @author Andreas Gohr */ function admin_superacl_html_dropdown($id){ $cur = $id; $ret = ''; $selected_id = $this->get_acl_scope(); $opt = array(); //prepare all options (in reversed order) $pages = array(); if ($this->getConf('use_ajax')) { $ret .= sprintf('',$id); $ret .= sprintf('',$selected_id); $ret .= sprintf('',$this->lang['page']); $ret .= sprintf('',$this->lang['namespace']); } else { // pages in this namespace $pages = array_reverse($this->get_pages($id)); } // add pages in list foreach ($pages as $page_id) { $page_id = ($id != '*' ? $id . ':' : '') . $page_id; $opt[] = array( 'value'=> $page_id, 'text'=> $page_id.' ('.$this->lang['page'].')' ); if ($page_id == $selected_id) $opt[count($opt)-1]['sel'] = true; } // add selected page (if not in list above) if (!($pages) && ($this->is_page($selected_id))) { $opt[] = array( 'value'=> $selected_id, 'text'=> $selected_id.' ('.$this->lang['page'].')', 'sel' => true ); } // additional namespaces for(; $id !== false && $id != '*'; $id=getNS($id)){ $opt[] = array('value'=> $id.':*', 'text'=> $id.':* ('.$this->lang['namespace'].')'); if ($id.':*' == $selected_id) $opt[count($opt)-1]['sel'] = true; } // the top namespace $opt[] = array( 'value'=> '*', 'text'=> '* ('.$this->lang['namespace'].')' ); // flip options $opt = array_reverse($opt); // create HTML $att = array( 'name' => 'acl_scope', 'id' => 'superacl__pageselect', 'class' => 'edit', 'title' => $this->lang['page'].'/'.$this->lang['namespace'] ); $ret .= ''; return $ret; } /** * Decides, if a id is a page * */ function is_page($id){ return (id != "") && ! (substr($id,-1) == '*'); } /** * creates dropdown with all namespaces * * @author Pascal Bihler */ function admin_superacl_html_ns_dropdown($id){ global $ID; $ret = ''; $opt = array(); $namespaces = array(); if ($this->getConf('use_ajax')) { $ret .= sprintf('',$ID); $ret .= sprintf('',$id); } else { $namespaces = $this->get_namespaces(); } // add namespaces of current page to the list of namespaces (even if the namespaces doesn't exist yet for($ns_id = getNS($ID); $ns_id !== false; $ns_id = getNS($ns_id)){ if (!in_array($ns_id,$namespaces)) $namespaces[] = $ns_id; } // add selected namespace, if not in list above if ($id && !in_array($id,$namespaces)) $namespaces[] = $id; sort($namespaces); $namespaces = array_reverse($namespaces); foreach ($namespaces as $ns_id) { if ($ns_id == "*") continue; $opt[] = array('value'=> $ns_id, 'text'=> $ns_id.':*'); if ($ns_id == $id) { // set sel on current selected namespcase $opt[count($opt)-1]['sel'] = true; } } $opt[] = array('value'=> '*', 'text'=> '*'); // flip options $opt = array_reverse($opt); // create HTML $att = array( 'name' => 'superacl_ns', 'id' => 'superacl__nsselect', 'class' => 'edit', 'title' => $this->lang['page'].'/'.$this->lang['namespace'] ); $ret .= ''; return $ret; } /** * print form to select namespace to modify * * @author Pascal Bihler * */ function admin_superacl_select_ns() { global $ID; global $lang; // table headers ptln('',2); ptln(' '.$this->lang['acl_select'].'',2); ptln('',2); ptln('',2); ptln('',4); ptln('
',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); //scope select ptln($this->lang['acl_perms'],4); ptln($this->admin_superacl_html_ns_dropdown($this->get_selected_ns()),4); ptln(' ',4); ptln('
'); ptln('',4); ptln('',2); } /** * print form to add new Permissions * * @author Pascal Bihler * @author Frank Schubert * @author Andreas Gohr */ function admin_superacl_html_new(){ global $ID; global $lang; // table headers ptln('',2); ptln(' '.$this->lang['acl_new'].'',2); ptln('',2); ptln('',2); ptln('',4); ptln('
',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); //scope select ptln($this->lang['acl_perms'],4); ptln($this->admin_superacl_html_dropdown($this->get_selected_ns()),4); $att = array( 'name' => 'acl_type', 'class' => 'edit', 'title' => $this->lang['acl_user'].'/'.$this->lang['acl_group'] ); ptln(' ',4); $att = array( 'name' => 'acl_user', 'type' => 'text', 'class' => 'edit', 'title' => $this->lang['acl_user'].'/'.$this->lang['acl_group'] ); ptln(' ',4); ptln('
'); ptln( $this->admin_acl_html_checkboxes(0,false),8); ptln(' ',4); ptln('
'); ptln('',4); ptln('',2); } /** * print tablerows with the current permissions for one id * * @author Pascal Bihler * @author Frank Schubert * @author Andreas Gohr */ function admin_superacl_html_current($id,$permissions){ global $lang; global $ID; //is it a page? $ispage = $this->is_page($id); // table headers ptln(' '); ptln(' '); ptln($this->lang['acl_perms'],6); if($ispage){ ptln($this->lang['page'],6); }else{ ptln($this->lang['namespace'],6); } ptln(''.$id.'',6); ptln(' '); ptln(' '); sort($permissions); foreach ($permissions as $conf){ //userfriendly group/user display $conf['name'] = rawurldecode($conf['name']); if(substr($conf['name'],0,1)=="@"){ $group = $this->lang['acl_group']; $name = substr($conf['name'],1); $type = '@'; }else{ $group = $this->lang['acl_user']; $name = $conf['name']; $type = ''; } ptln('',2); ptln(''.htmlspecialchars($group.' '.$name).'',4); // update form ptln('',4); ptln('
',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); ptln( $this->admin_acl_html_checkboxes($conf['perm'],$ispage),8); ptln(' ',4); ptln('
'); ptln('',4); // deletion form $ask = $lang['del_confirm'].'\\n'; $ask .= $id.' '.$conf['name'].' '.$conf['perm']; ptln('',4); ptln('
',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); ptln(' ',4); ptln('
',4); ptln('',4); ptln('',2); } } /** * print the permission checkboxes * * @author Frank Schubert * @author Andreas Gohr */ function admin_acl_html_checkboxes($setperm,$ispage){ global $lang; static $label = 0; //number labels $ret = ''; foreach(array(AUTH_READ,AUTH_EDIT,AUTH_CREATE,AUTH_UPLOAD,AUTH_DELETE) as $perm){ $label += 1; //general checkbox attributes $atts = array( 'type' => 'checkbox', 'id' => 'pbox'.$label, 'name' => 'acl_perm[]', 'value' => $perm ); //dynamic attributes if($setperm >= $perm) $atts['checked'] = 'checked'; $atts['onchange'] = "superacl_autoselect_permissions(this);"; if($ispage && $perm > AUTH_EDIT) $atts['disabled'] = 'disabled'; //build code $ret .= '\n"; } return $ret; } function get_pages($tns='') { return $this->_getlist($tns,false,false,true); } function get_namespaces($tns='') { return $this->_getlist($tns,true,true,false); } /** * inspired from addnewpageplugin: */ function _getlist ($tns='',$recursive = true, $namespaces=true,$pages=false) { require_once(DOKU_INC.'inc/search.php'); global $conf; if ($tns == '*') $tns = ''; if (!is_dir($tns)) $tns = str_replace(':','/',$tns); $data = array(); search($data,$conf['datadir'] ."/" . $tns,'search_index',array('ns' => '')); $data2 = array(); foreach($data as $k => $v) { if ($v['type']=='d') { //Namespace if ($namespaces) array_push($data2,$v['id']); if ($recursive) { $r=$this->_getlist($tns.'/'.$v['id'],$recursive,$namespaces,$pages); foreach ($r as $vv) { array_push($data2,$v['id'].':'.$vv); } } } elseif ($v['type']=='f') { //Page if ($pages) array_push($data2,$v['id']); } } return $data2; } }
**ajax.php:** FIXME move this to action.php where it can hook the [[devel:event:ajax_call_unknown]] event. * */ //fix for Opera XMLHttpRequests if(!count($_POST) && $HTTP_RAW_POST_DATA){ parse_str($HTTP_RAW_POST_DATA, $_POST); } if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../../').'/'); require_once(DOKU_INC.'inc/init.php'); require_once(DOKU_INC.'inc/common.php'); require_once(DOKU_INC.'inc/pageutils.php'); require_once(DOKU_INC.'inc/auth.php'); require_once(DOKU_INC.'inc/JSON.php'); //close session session_write_close(); // check, if user is admin (or at least manager) if (! auth_ismanager()) { exit; } if ($_POST["q"] == "namespaces") { get_namespace_list(); } elseif ($_POST["q"] == "pages") { get_pages_list(); } function get_namespace_list() { $ID = $_POST["pageid"]; $selid = $_POST["selid"]; $opt = array(); // all namespace $namespaces = get_namespaces(); // add namespaces of current page to the list of namespaces (even if the namespaces doesn't exist yet for($ns_id = getNS($ID); $ns_id !== false; $ns_id = getNS($ns_id)){ if (!in_array($ns_id,$namespaces)) $namespaces[] = $ns_id; } sort($namespaces); $namespaces = array_reverse($namespaces); foreach ($namespaces as $ns_id) { $opt[] = array('value'=> $ns_id, 'text'=> $ns_id.':*'); if ($ns_id == $selid) { // set sel on current selected namespcase $opt[count($opt)-1]['sel'] = true; } } $opt[] = array('value'=> '*', 'text'=> '*'); // flip options $opt = array_reverse($opt); /* now construct a json */ $json = new JSON(); //header('Content-Type: application/json'); header('Content-Type: text/javascript'); print $json->encode($opt); } function get_pages_list() { $id = $_POST["aclid"]; $selected_id = $_POST["selid"]; $opt = array(); $pages = array_reverse(get_pages($id)); // add pages in list foreach ($pages as $page_id) { $page_id = ($id != '*' ? $id . ':' : '') . $page_id; $opt[] = array( 'value'=> $page_id, 'text'=> $page_id.' ('.$_POST['page_text'].')' ); if ($page_id == $selected_id) $opt[count($opt)-1]['sel'] = true; } // additional namespaces for(; $id !== false && $id != '*'; $id=getNS($id)){ $opt[] = array('value'=> $id.':*', 'text'=> $id.':* ('.$_POST['ns_text'].')'); if ($id.':*' == $selected_id) $opt[count($opt)-1]['sel'] = true; } // the top namespace $opt[] = array('value'=> '*', 'text'=> '* ('.$_POST['ns_text'].')'); // flip options $opt = array_reverse($opt); /* now construct a json */ $json = new JSON(); //header('Content-Type: application/json'); header('Content-Type: text/javascript'); print $json->encode($opt); } function get_pages($tns='') { return _getlist($tns,false,false,true); } function get_namespaces($tns='') { return _getlist($tns,true,true,false); } // inspired from addnewpageplugin: function _getlist ($tns='',$recursive = true, $namespaces=true,$pages=false) { require_once(DOKU_INC.'inc/search.php'); global $conf; if ($tns == '*') $tns = ''; if (!is_dir($tns)) $tns = str_replace(':','/',$tns); $data = array(); search($data,$conf['datadir'] ."/" . $tns,'search_index',array('ns' => '')); $data2 = array(); foreach($data as $k => $v) { if ($v['type']=='d') { //Namespace if ($namespaces) array_push($data2,$v['id']); if ($recursive) { $r=_getlist($tns.'/'.$v['id'],$recursive,$namespaces,$pages); foreach ($r as $vv) { array_push($data2,$v['id'].':'.$vv); } } } elseif ($v['type']=='f') { //Page if ($pages) array_push($data2,$v['id']); } } return $data2; } ?> **script.js:** function superacl_autoselect_permissions(caller) { callerLabel = parseInt(caller.id.substring(4)); baseLabel = caller.id.substring(0,4); parent = caller.parentNode.parentNode; boxes = parent.getElementsByTagName('input'); for (i = 0; i < boxes.length; i++) { e = boxes[i]; if (! e) continue; id = e.id; if (!id || id.length<4 || id.substring(0,4) != baseLabel) continue; label = parseInt(id.substring(4)); //check lower rights, too if (caller.checked && label < callerLabel) e.checked = true; //uncheck upper rights too else if (! caller.checked && label > callerLabel) e.checked = false; } } function ajax_superacl_class() { this.sack = null; this.inObj = null; this.outObj = null; this.timer = null; } function fillNamespaceDropdown() { // fill namespace selector if ($('superacl__nsselid')) { var ajax_superaclns = new ajax_superacl_class(); ajax_superaclns.sack = new sack(DOKU_BASE + 'lib/plugins/superacl/ajax.php'); ajax_superaclns.sack.AjaxFailedAlert = ''; ajax_superaclns.sack.encodeURIString = false; ajax_superaclns.exec = function() { pageid = $("superacl__pageid").value; ns_selid = $("superacl__nsselid").value; ajax_superaclns.sack.runAJAX('q=namespaces&pageid=' + encodeURI(pageid) + '&selid=' + encodeURI(ns_selid)); }; ajax_superaclns.sack.onCompletion = function() { var data = eval(ajax_superaclns.sack.response); if(data === '') return; // add namespaces to select box select = $('superacl__nsselect'); for(i = 0; i < data.length; ++i) { option = new Option(data[i].text, data[i].value, data[i].sel, data[i].sel); select.options[i] = option; } }; ajax_superaclns.exec(); } // fill page selector if ($('superacl__aclid')) { var ajax_superaclpage = new ajax_superacl_class(); ajax_superaclpage.sack = new sack(DOKU_BASE + 'lib/plugins/superacl/ajax.php'); ajax_superaclpage.sack.AjaxFailedAlert = ''; ajax_superaclpage.sack.encodeURIString = false; ajax_superaclpage.exec = function() { acl_id = $("superacl__aclid").value; page_selid = $("superacl__pageselid").value; page_text = $("superacl__page_text").value; ns_text = $("superacl__namespace_text").value; ajax_superaclpage.sack.runAJAX( 'q=pages&aclid=' + encodeURI(acl_id) + '&selid=' + encodeURI(page_selid) + '&page_text=' + encodeURI(page_text) + '&ns_text=' + encodeURI(ns_text) ); }; ajax_superaclpage.sack.onCompletion = function() { var data = eval(ajax_superaclpage.sack.response); if(data === '') return; // add namespaces to select box select = $('superacl__pageselect'); for(i = 0; i < data.length; ++i) { option = new Option(data[i].text, data[i].value, data[i].sel, data[i].sel); select.options[i] = option; } }; ajax_superaclpage.exec(); } } addInitEvent(function(){fillNamespaceDropdown();}); ==== Patches ==== === Incompatible with "WeatherWax" ==== https://www.dokuwiki.org/changes#release_candidate_weatherwax === Bug : Problem with * and + characters === * description : if you try to get ACL for "*" (the first namespace in the list) you get at least 2 warnings "warning: Compilation failed: nothing to repeat at offset 1" for preg_grep on line 183. * solution I've put the following code just before line 183 : $id = str_replace('*', '\\*', $id); $id = str_replace('+', '\\+', $id); It seems it works... > Thank you for the hint, I included it with the new version 2007-08-13. > --- //[[bihler@iai.uni-bonn.de|Pascal Bihler]] 2007/08/13 16:06// === Bug : wrong variable name === In admin.php line 528 $idpage $ispage = $this->is_page($id); > Ok, fixed === Overriding the ACL Admin Task === Would it be possible to override the ACL task item in the first section of the Administration screen (the list with the big icons) ? --- [[user>petsagouris|George Petsagourakis]] //2011/02/13 13:05//