AJAX interface for listing ACL rules implemented
authorDan
Sat, 29 Mar 2008 15:58:59 -0700
changeset 513 c15fbf197a54
parent 512 13532b0a223f
child 514 ecbfb747743e
AJAX interface for listing ACL rules implemented
includes/clientside/static/acl.js
includes/clientside/static/misc.js
includes/constants.php
includes/pageutils.php
index.php
install/index.php
language/english/admin.json
--- 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 += '<p id="'+aclManagerID+'_deletelnk" style="text-align: right;"><a href="#delete_acl_rule" onclick="if(confirm(\'' + $lang.get('acl_msg_deleterule_confirm') + '\')) __aclDeleteRule(); return false;" style="color: red;">' + $lang.get('acl_lbl_deleterule') + '</a></p>';
             
             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 = '<h2>'+act_desc+'</h2>';
   html += '<p>' + $lang.get('acl_lbl_editwin_body', { target_type: target_type_t, target: target_name_t, scope_type: scope_type }) + '</p>';
   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();
--- 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 )
--- 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.
--- 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  = '<div style="width: 460px; margin: 0 auto; text-align: center; margin-bottom: 10px;">';
+  $out .= '<div style="float: left;">&larr; Deny</div>';
+  $out .= '<div style="float: right;">Allow &rarr;</div>';
+  $out .= 'Neutral';
+  $out .= '<div style="clear: both;"></div>';
+  // 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 .= '<div style="background-color: #' . $color . '; float: left; width: 20px;">&nbsp;</div>';
+  }
+  $out .= '<div style="background-color: #efefef; float: left; width: 20px;">&nbsp;</div>';
+  for ( $i = 0xFF; $i >= ACL_SCALE_MINIMAL_SHADE; $i-= $inc )
+  {
+    $octet = dechex($i);
+    $color = "{$octet}ff{$octet}";
+    $out .= '<div style="background-color: #' . $color . '; float: left; width: 20px;">&nbsp;</div>';
+  }
+  $out .= '<div style="clear: both;"></div>';
+  $out .= '<div style="float: left;">-100</div>';
+  $out .= '<div style="float: right;">+100</div>';
+  $out .= '0';
+  $out .= '</div>';
+  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);
+}
+
 ?>
--- 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
--- 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
--- 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: '&raquo; View existing rules',
     },
     acphome: {
       heading_main: 'Welcome to Runt, the Enano administration panel.',