* @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(' ');
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(' ');
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(' ');
ptln(' ',4);
// deletion form
$ask = $lang['del_confirm'].'\\n';
$ask .= $id.' '.$conf['name'].' '.$conf['perm'];
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