# HG changeset patch # User Dan # Date 1206831539 25200 # Node ID c15fbf197a5494e50527412d9e5af6a2d4c2b5a1 # Parent 13532b0a223fd0ba1ad9e420eb060d17631ada57 AJAX interface for listing ACL rules implemented diff -r 13532b0a223f -r c15fbf197a54 includes/clientside/static/acl.js --- a/includes/clientside/static/acl.js Thu Mar 27 16:41:07 2008 -0400 +++ b/includes/clientside/static/acl.js Sat Mar 29 15:58:59 2008 -0700 @@ -79,6 +79,13 @@ if ( !document.getElementById(aclManagerID) ) { __aclBuildWizardWindow(); + var main = document.getElementById(aclManagerID + '_main'); + main.style.padding = '10px'; + } + else + { + var main = document.getElementById(aclManagerID + '_main'); + main.style.backgroundImage = 'none'; } if ( response.mode == 'error' ) { @@ -87,7 +94,7 @@ return false; } aclDataCache = response; - aclBuildRuleEditor(response); + aclBuildRuleEditor(response, true); } }, true); } @@ -131,10 +138,30 @@ thispage = strToPageID(title); do_scopesel = ( thispage[0] == groups.page_id && thispage[1] == groups.namespace ); + document.getElementById(aclManagerID + '_next').style.display = 'inline'; + seed = Math.floor(Math.random() * 1000000); main = document.getElementById(aclManagerID + '_main'); main.style.padding = '10px'; + main.style.backgroundImage = 'none'; + + // the "edit existing" button + var editbtn_wrapper = document.createElement('div'); + editbtn_wrapper.style.styleFloat = 'right'; + editbtn_wrapper.style.cssFloat = 'right'; + editbtn_wrapper.style.fontSize = 'smaller'; + var editbtn = document.createElement('a'); + editbtn.href = '#'; + editbtn.innerHTML = $lang.get('acl_btn_show_existing'); + editbtn_wrapper.appendChild(editbtn); + main.appendChild(editbtn_wrapper); + + editbtn.onclick = function() + { + aclSetViewListExisting(); + return false; + } selector = document.createElement('div'); @@ -363,10 +390,14 @@ handle_invalid_json(ajax.responseText); return false; } - try { - data = parseJSON(ajax.responseText); - } catch(e) { + try + { + var data = parseJSON(ajax.responseText); + } + catch(e) + { handle_invalid_json(ajax.responseText); + return false; } aclDataCache = data; switch(data.mode) @@ -420,6 +451,7 @@ document.getElementById(aclManagerID + '_main').innerHTML += '

' + $lang.get('acl_lbl_deleterule') + '

'; document.getElementById(aclManagerID+'_main').scrollTop = 0; + document.getElementById(aclManagerID+'_main').style.backgroundImage = 'none'; aclDataCache.mode = 'save_edit'; break; @@ -476,6 +508,9 @@ case 'debug': aclDebug(data.text); break; + case 'list_existing': + aclSetViewListExistingRespond(data); + break; default: handle_invalid_json(ajax.responseText); break; @@ -484,13 +519,15 @@ }, true); } -function aclBuildRuleEditor(data) +function aclBuildRuleEditor(data, from_direct) { var act_desc = ( data.type == 'new' ) ? $lang.get('acl_lbl_editwin_title_create') : $lang.get('acl_lbl_editwin_title_edit'); var target_type_t = ( data.target_type == 1 ) ? $lang.get('acl_target_type_group') : $lang.get('acl_target_type_user'); var target_name_t = data.target_name; var scope_type = ( data.page_id == false && data.namespace == false ) ? $lang.get('acl_scope_type_wholesite') : ( data.namespace == '__PageGroup' ) ? $lang.get('acl_scope_type_pagegroup') : $lang.get('acl_scope_type_thispage'); + document.getElementById(aclManagerID + '_next').style.display = 'inline'; + html = '

'+act_desc+'

'; html += '

' + $lang.get('acl_lbl_editwin_body', { target_type: target_type_t, target: target_name_t, scope_type: scope_type }) + '

'; parser = new templateParser(data.template.acl_field_begin); @@ -558,11 +595,19 @@ var form = document.getElementById(aclManagerID + '_formobj_id'); - var modeobj = form_fetch_field(form, 'mode'); - if ( modeobj ) - modeobj.value = 'save_' + data.type; + if ( from_direct ) + { + var modeobj = document.getElementById(aclManagerID + '_mode'); + modeobj.value = 'save_edit'; + } else - alert('modeobj is invalid: '+modeobj); + { + var modeobj = form_fetch_field(form, 'mode'); + if ( modeobj ) + modeobj.value = 'save_' + data.type; + else + alert('modeobj is invalid: '+modeobj); + } aclPermList = array_keys(data.acl_types); @@ -918,6 +963,67 @@ __aclJSONSubmitAjaxHandler(parms); } +function aclSetViewListExisting() +{ + if ( !document.getElementById(aclManagerID) ) + { + return false; + } + + var main = document.getElementById(aclManagerID + '_main'); + main.innerHTML = ''; + main.style.backgroundImage = 'url(' + scriptPath + '/images/loading-big.gif)'; + main.style.backgroundRepeat = 'no-repeat'; + main.style.backgroundPosition = 'center center'; + + var parms = { + 'mode' : 'list_existing' + }; + __aclJSONSubmitAjaxHandler(parms); +} + +function aclSetViewListExistingRespond(data) +{ + var main = document.getElementById(aclManagerID + '_main'); + main.style.padding = '10px'; + main.innerHTML = ''; + + var heading = document.createElement('h3'); + heading.appendChild(document.createTextNode($lang.get('acl_msg_scale_intro_title'))); + main.appendChild(heading); + + var p = document.createElement('p'); + p.appendChild(document.createTextNode($lang.get('acl_msg_scale_intro_body'))); + main.appendChild(p); + + + main.innerHTML += data.key; + main.style.backgroundImage = 'none'; + + document.getElementById(aclManagerID + '_back').style.display = 'inline'; + document.getElementById(aclManagerID + '_next').style.display = 'none'; + + for ( var i = 0; i < data.rules.length; i++ ) + { + var rule = data.rules[i]; + // build the rule, this is just more boring DOM crap. + var div = document.createElement('div'); + div.style.padding = '5px 3px'; + div.style.backgroundColor = '#' + rule.color; + div.style.cursor = 'pointer'; + div.rule_id = rule.rule_id; + div.onclick = function() + { + var main = document.getElementById(aclManagerID + '_main'); + main.innerHTML = ''; + main.style.backgroundImage = 'url(' + scriptPath + '/images/loading-big.gif)'; + ajaxOpenDirectACLRule(parseInt(this.rule_id)); + } + div.innerHTML = rule.score_string; + main.appendChild(div); + } +} + function array_keys(obj) { keys = new Array(); diff -r 13532b0a223f -r c15fbf197a54 includes/clientside/static/misc.js --- a/includes/clientside/static/misc.js Thu Mar 27 16:41:07 2008 -0400 +++ b/includes/clientside/static/misc.js Sat Mar 29 15:58:59 2008 -0700 @@ -589,14 +589,19 @@ var sets = document.getElementsByTagName('fieldset'); if ( sets.length < 1 ) return false; - for ( var i = 0; i < sets.length; i++ ) + var init_us = []; + for ( var index = 0; index < sets.length; index++ ) { - var mode = sets[i].getAttribute('enano:expand'); + var mode = sets[index].getAttribute('enano:expand'); if ( mode == 'closed' || mode == 'open' ) { - expander_init_element(sets[i]); + init_us.push(sets[index]); } } + for ( var k = 0; k < init_us.length; k++ ) + { + expander_init_element(init_us[k]); + } } function expander_init_element(el) @@ -624,6 +629,7 @@ } catch(e) { + console.debug('Exception caught: ', e); } return false; } @@ -662,8 +668,11 @@ $(a).addClass('expander-closed'); continue; } - child.expander_meta_old_state = child.style.display; - child.style.display = 'none'; + if ( child.style ) + { + child.expander_meta_old_state = child.style.display; + child.style.display = 'none'; + } } el.expander_meta_padbak = el.style.padding; el.setAttribute('enano:expand', 'closed'); @@ -682,14 +691,17 @@ $(a).addClass('expander-open'); continue; } - if ( child.expander_meta_old_state ) + if ( child.expander_meta_old_state && child.style ) { child.style.display = child.expander_meta_old_state; child.expander_meta_old_state = null; } else { - child.style.display = null; + if ( child.style ) + { + child.style.display = null; + } } } if ( el.expander_meta_padbak ) diff -r 13532b0a223f -r c15fbf197a54 includes/constants.php --- a/includes/constants.php Thu Mar 27 16:41:07 2008 -0400 +++ b/includes/constants.php Sat Mar 29 15:58:59 2008 -0700 @@ -29,6 +29,11 @@ define('ACL_TYPE_USER', 2); define('ACL_TYPE_PRESET', 3); +// ACL color scale minimal shade for red and green ends +// The lower, the more saturated the color on the scale. +// Purely cosmetic. 0x0 - 0xFF, 0xFF will basically disable the scale +define('ACL_SCALE_MINIMAL_SHADE', 0xA8); + // ACL switch // If this is defined, administrators can edit ACLs regardless of current // permissions. This is enabled by default. diff -r 13532b0a223f -r c15fbf197a54 includes/pageutils.php --- a/includes/pageutils.php Thu Mar 27 16:41:07 2008 -0400 +++ b/includes/pageutils.php Sat Mar 29 15:58:59 2008 -0700 @@ -1672,11 +1672,15 @@ // regenerate page selection $parms['page_id'] = ( isset($parms['page_id']) ) ? $parms['page_id'] : false; $parms['namespace'] = ( isset($parms['namespace']) ) ? $parms['namespace'] : false; + $parms['mode'] = 'seltarget_id'; $page_id =& $parms['page_id']; $namespace =& $parms['namespace']; $page_where_clause = ( empty($page_id) || empty($namespace) ) ? 'AND a.page_id IS NULL AND a.namespace IS NULL' : 'AND a.page_id=\'' . $db->escape($page_id) . '\' AND a.namespace=\'' . $db->escape($namespace) . '\''; $page_where_clause_lite = ( empty($page_id) || empty($namespace) ) ? 'AND page_id IS NULL AND namespace IS NULL' : 'AND page_id=\'' . $db->escape($page_id) . '\' AND namespace=\'' . $db->escape($namespace) . '\''; + $return['page_id'] = $parms['page_id']; + $return['namespace'] = $parms['namespace']; + // From here, let the seltarget handler take over case 'seltarget': $return['mode'] = 'seltarget'; @@ -1688,24 +1692,25 @@ switch($parms['target_type']) { case ACL_TYPE_USER: - $q = $db->sql_query('SELECT a.rules,u.user_id FROM ' . table_prefix.'users AS u + $user_col = ( $parms['mode'] == 'seltarget_id' ) ? 'user_id' : 'username'; + $q = $db->sql_query('SELECT a.rules,u.user_id,u.username FROM ' . table_prefix.'users AS u LEFT JOIN ' . table_prefix.'acl AS a ON a.target_id=u.user_id WHERE a.target_type='.ACL_TYPE_USER.' - AND u.username=\'' . $db->escape($parms['target_id']) . '\' + AND u.' . $user_col . ' = \'' . $db->escape($parms['target_id']) . '\' ' . $page_where_clause . ';'); if(!$q) return(Array('mode'=>'error','error'=>$db->get_error())); if($db->numrows() < 1) { $return['type'] = 'new'; - $q = $db->sql_query('SELECT user_id FROM ' . table_prefix.'users WHERE username=\'' . $db->escape($parms['target_id']) . '\';'); + $q = $db->sql_query('SELECT user_id,username FROM ' . table_prefix.'users WHERE username=\'' . $db->escape($parms['target_id']) . '\';'); if(!$q) return(Array('mode'=>'error','error'=>$db->get_error())); if($db->numrows() < 1) - return Array('mode'=>'error','error'=>$lang->get('acl_err_user_not_found')); + return Array('mode'=>'error','error'=>$lang->get('acl_err_user_not_found'),'debug' => $db->sql_backtrace()); $row = $db->fetchrow(); - $return['target_name'] = $return['target_id']; + $return['target_name'] = $row['username']; $return['target_id'] = intval($row['user_id']); $return['current_perms'] = array(); } @@ -1713,7 +1718,7 @@ { $return['type'] = 'edit'; $row = $db->fetchrow(); - $return['target_name'] = $return['target_id']; + $return['target_name'] = $row['username']; $return['target_id'] = intval($row['user_id']); $return['current_perms'] = $session->string_to_perm($row['rules']); } @@ -1830,8 +1835,9 @@ { return Array('mode'=>'error','error'=>$lang->get('acl_err_demo')); } - $q = $db->sql_query('DELETE FROM ' . table_prefix.'acl WHERE target_type='.intval($parms['target_type']).' AND target_id='.intval($parms['target_id']).' - ' . $page_where_clause_lite . ';'); + $sql = 'DELETE FROM ' . table_prefix.'acl WHERE target_type='.intval($parms['target_type']).' AND target_id='.intval($parms['target_id']).' + ' . $page_where_clause_lite . ';'; + $q = $db->sql_query($sql); if(!$q) return Array('mode'=>'error','error'=>$db->get_error()); return Array( @@ -1843,6 +1849,117 @@ 'namespace' => $namespace, ); break; + case 'list_existing': + + $return = array( + 'mode' => 'list_existing', + 'key' => acl_list_draw_key(), + 'rules' => array() + ); + + $q = $db->sql_query("SELECT a.rule_id, u.username, g.group_name, a.target_type, a.target_id, a.page_id, a.namespace, a.rules, p.pg_name\n" + . " FROM " . table_prefix . "acl AS a\n" + . " LEFT JOIN " . table_prefix . "users AS u\n" + . " ON ( (a.target_type = " . ACL_TYPE_USER . " AND a.target_id = u.user_id) OR (u.user_id IS NULL) )\n" + . " LEFT JOIN " . table_prefix . "groups AS g\n" + . " ON ( (a.target_type = " . ACL_TYPE_GROUP . " AND a.target_id = g.group_id) OR (g.group_id IS NULL) )\n" + . " LEFT JOIN " . table_prefix . "page_groups as p\n" + . " ON ( (a.namespace = '__PageGroup' AND a.page_id = p.pg_id) OR (p.pg_id IS NULL) )\n" + . " GROUP BY a.rule_id\n" + . " ORDER BY a.target_type ASC, a.rule_id ASC;" + ); + + if ( !$q ) + $db->_die(); + + while ( $row = $db->fetchrow($q) ) + { + if ( $row['target_type'] == ACL_TYPE_USER && empty($row['username']) ) + { + // This is only done if we have an ACL affecting a user that doesn't exist. + // Nice little bit of maintenance to have. + if ( !$db->sql_query("DELETE FROM " . table_prefix . "acl WHERE rule_id = {$row['rule_id']};") ) + $db->_die(); + continue; + } + $score = get_acl_rule_score($row['rules']); + $deep_limit = ACL_SCALE_MINIMAL_SHADE; + // Determine background color of cell by score + if ( $score > 5 ) + { + // high score, show in green + $color = 2.5 * $score; + if ( $color > 255 ) + $color = 255; + $color = round($color); + // blend with the colordepth limit + $color = $deep_limit + ( ( 0xFF - $deep_limit ) - ( ( $color / 0xFF ) * ( 0xFF - $deep_limit ) ) ); + $color = dechex($color); + $color = "{$color}ff{$color}"; + } + else if ( $score < -5 ) + { + // low score, show in red + $color = 0 - $score; + $color = 2.5 * $color; + if ( $color > 255 ) + $color = 255; + $color = round($color); + // blend with the colordepth limit + $color = $deep_limit + ( ( 0xFF - $deep_limit ) - ( ( $color / 0xFF ) * ( 0xFF - $deep_limit ) ) ); + $color = dechex($color); + $color = "ff{$color}{$color}"; + } + else + { + $color = 'efefef'; + } + + // Rate rule textually based on its score + if ( $score >= 70 ) + $desc = $lang->get('acl_msg_scale_allow'); + else if ( $score >= 50 ) + $desc = $lang->get('acl_msg_scale_mostly_allow'); + else if ( $score >= 25 ) + $desc = $lang->get('acl_msg_scale_some_allow'); + else if ( $score >= -25 ) + $desc = $lang->get('acl_msg_scale_mixed'); + else if ( $score <= -70 ) + $desc = $lang->get('acl_msg_scale_deny'); + else if ( $score <= -50 ) + $desc = $lang->get('acl_msg_scale_mostly_deny'); + else if ( $score <= -25 ) + $desc = $lang->get('acl_msg_scale_some_deny'); + + // group and user target info + $info = ''; + if ( $row['target_type'] == ACL_TYPE_USER ) + $info = $lang->get('acl_msg_list_user', array( 'username' => $row['username'] )); // "(User: {$row['username']})"; + else if ( $row['target_type'] == ACL_TYPE_GROUP ) + $info = $lang->get('acl_msg_list_group', array( 'group' => $row['group_name'] )); + + // affected pages info + if ( $row['page_id'] && $row['namespace'] && $row['namespace'] != '__PageGroup' ) + $info .= $lang->get('acl_msg_list_on_page', array( 'page_name' => "{$row['namespace']}:{$row['page_id']}" )); + else if ( $row['page_id'] && $row['namespace'] && $row['namespace'] == '__PageGroup' ) + $info .= $lang->get('acl_msg_list_on_page_group', array( 'page_group' => $row['pg_name'] )); + else + $info .= $lang->get('acl_msg_list_entire_site'); + + $score_string = $lang->get('acl_msg_list_score', array + ( + 'score' => $score, + 'desc' => $desc, + 'info' => $info + )); + $return['rules'][] = array( + 'score_string' => $score_string, + 'rule_id' => $row['rule_id'], + 'color' => $color + ); + } + + break; default: return Array('mode'=>'error','error'=>'Hacking attempt'); break; @@ -2125,4 +2242,84 @@ } +/** + * Generates a graphical key showing how the ACL rule list works. + * @return string + */ + +function acl_list_draw_key() +{ + $out = '
'; + $out .= '
← Deny
'; + $out .= '
Allow →
'; + $out .= 'Neutral'; + $out .= '
'; + // 11 boxes on each side of the center + $inc = ceil ( ( 0xFF - ACL_SCALE_MINIMAL_SHADE ) / 11 ); + for ( $i = ACL_SCALE_MINIMAL_SHADE; $i <= 0xFF; $i+= $inc ) + { + $octet = dechex($i); + $color = "ff$octet$octet"; + $out .= '
 
'; + } + $out .= '
 
'; + for ( $i = 0xFF; $i >= ACL_SCALE_MINIMAL_SHADE; $i-= $inc ) + { + $octet = dechex($i); + $color = "{$octet}ff{$octet}"; + $out .= '
 
'; + } + $out .= '
'; + $out .= '
-100
'; + $out .= '
+100
'; + $out .= '0'; + $out .= '
'; + return $out; +} + +/** + * Gets the numerical score for the serialized form of an ACL rule + */ + +function get_acl_rule_score($perms) +{ + global $db, $session, $paths, $template, $plugins; // Common objects + if ( is_string($perms) ) + $perms = $session->string_to_perm($perms); + else if ( !is_array($perms) ) + return false; + $score = 0; + foreach ( $perms as $item ) + { + switch ( $item ) + { + case AUTH_ALLOW : + $inc = 2; + break; + case AUTH_WIKIMODE: + $inc = 1; + break; + case AUTH_DISALLOW: + $inc = -1; + break; + case AUTH_DENY: + $inc = -2; + break; + default: + $inc = 0; + break; + } + $score += $inc; + } + // this is different from the beta; calculate highest score and + // get percentage to be fairer to smaller/less broad rules + $divisor = count($perms) * 2; + if ( $divisor == 0 ) + { + return 0; + } + $score = 100 * ( $score / $divisor ); + return round($score); +} + ?> diff -r 13532b0a223f -r c15fbf197a54 index.php --- a/index.php Thu Mar 27 16:41:07 2008 -0400 +++ b/index.php Sat Mar 29 15:58:59 2008 -0700 @@ -2,7 +2,7 @@ /* * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between - * Version 1.1.2 + * Version 1.1.3 (Caoineag alpha 3) * Copyright (C) 2006-2007 Dan Fuhry * * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License diff -r 13532b0a223f -r c15fbf197a54 install/index.php --- a/install/index.php Thu Mar 27 16:41:07 2008 -0400 +++ b/install/index.php Sat Mar 29 15:58:59 2008 -0700 @@ -2,7 +2,7 @@ /* * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between - * Version 1.1.2 + * Version 1.1.3 (Caoineag alpha 3) * Copyright (C) 2006-2007 Dan Fuhry * Installation package * welcome.php - Portal to upgrade, readme, and install pages diff -r 13532b0a223f -r c15fbf197a54 language/english/admin.json --- a/language/english/admin.json Thu Mar 27 16:41:07 2008 -0400 +++ b/language/english/admin.json Sat Mar 29 15:58:59 2008 -0700 @@ -136,12 +136,32 @@ msg_closeacl_confirm: 'Do you really want to close the ACL manager?', msg_deny_everyone_confirm: 'CAUTION: You are setting a Deny ruling for everyone on this site. This will block the selected actions from being performed at all. Do you really want to do this?\n\nPlease also note that the following core pages will not be blocked from being accessed: Special:Login, Special:Logout, and Special:LangExportJSON.', + msg_scale_intro_title: 'Existing ACL rules', + msg_scale_intro_body: 'This is a list of all the existing ACL rules on your site. You can click an entry to edit it. Selecting the same criteria in the user and group selection page will also allow editing a rule.', + + msg_scale_allow: 'Allow', + msg_scale_mostly_allow: 'Mostly allow', + msg_scale_some_allow: 'Some allow', + msg_scale_mixed: 'Mixed', + msg_scale_some_deny: 'Some deny', + msg_scale_mostly_deny: 'Mostly deny', + msg_scale_deny: 'Deny', + + msg_list_user: '(User: %username%)', + msg_list_group: '(Group: %group%)', + msg_list_on_page: ' on page: %page_name%', + msg_list_on_page_group: ' for page group: %page_group%', + msg_list_entire_site: ' for the entire site', + + msg_list_score: 'Score: %score% (%desc%) %info%', + btn_success_dismiss: 'dismiss', btn_success_close: 'close manager', btn_deleterule: 'Delete rule', btn_createrule: 'Create rule', btn_returnto_editor: 'Return to ACL editor', btn_returnto_userscope: 'Return to user/scope selection', + btn_show_existing: '» View existing rules', }, acphome: { heading_main: 'Welcome to Runt, the Enano administration panel.',