Several major improvements: Memberlist page added (planned since about beta 2), page group support added for non-JS ACL editor (oops!), and attempting to view a page for which you lack read permissions will get you logged.
authorDan
Sun, 26 Aug 2007 20:45:33 -0400
changeset 103 a8891e108c95
parent 102 d807dcd7aed7
child 104 9c17aacd5515
Several major improvements: Memberlist page added (planned since about beta 2), page group support added for non-JS ACL editor (oops!), and attempting to view a page for which you lack read permissions will get you logged.
includes/clientside/static/ajax.js
includes/pageprocess.php
includes/pageutils.php
index.php
plugins/SpecialAdmin.php
plugins/SpecialUserFuncs.php
plugins/SpecialUserPrefs.php
themes/oxygen/css/bleu.css
--- a/includes/clientside/static/ajax.js	Sun Aug 26 16:48:15 2007 -0400
+++ b/includes/clientside/static/ajax.js	Sun Aug 26 20:45:33 2007 -0400
@@ -812,6 +812,28 @@
   window.location = loc;
 }
 
+function ajaxAdminUser(username)
+{
+  // IE <6 pseudo-compatibility
+  if ( KILL_SWITCH )
+    return true;
+  if ( auth_level < USER_LEVEL_ADMIN )
+  {
+    ajaxPromptAdminAuth(function(k) {
+      ENANO_SID = k;
+      auth_level = USER_LEVEL_ADMIN;
+      var loc = String(window.location + '');
+      window.location = append_sid(loc);
+      var loc = makeUrlNS('Special', 'Administration', 'module=' + namespace_list['Admin'] + 'UserManager&src=get&user=' + ajaxEscape(username));
+      if ( (ENANO_SID + ' ').length > 1 )
+        window.location = loc;
+    }, 9);
+    return false;
+  }
+  var loc = makeUrlNS('Special', 'Administration', 'module=' + namespace_list['Admin'] + 'UserManager&src=get&user=' + ajaxEscape(username));
+  window.location = loc;
+}
+
 function ajaxDisableEmbeddedPHP()
 {
   // IE <6 pseudo-compatibility
--- a/includes/pageprocess.php	Sun Aug 26 16:48:15 2007 -0400
+++ b/includes/pageprocess.php	Sun Aug 26 20:45:33 2007 -0400
@@ -969,14 +969,39 @@
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     
+    // Log it for crying out loud
+    $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'illegal_page\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($session->username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', \'' . $db->escape(serialize(array($this->page_id, $this->namespace))) . '\')');
+    
     $ob = '';
-    $template->tpl_strings['PAGE_NAME'] = 'Access denied';
+    //$template->tpl_strings['PAGE_NAME'] = 'Access denied';
+    $template->tpl_strings['PAGE_NAME'] = htmlspecialchars( $this->title );
       
     if ( $this->send_headers )
     {
       $ob .= $template->getHeader();
     }
     
+    if ( count($this->redirect_stack) > 0 )
+    {
+      $stack = array_reverse($this->redirect_stack);
+      foreach ( $stack as $oldtarget )
+      {
+        $url = makeUrlNS($oldtarget[1], $oldtarget[0], 'redirect=no', true);
+        $page_id_key = $paths->nslist[ $oldtarget[1] ] . $oldtarget[0];
+        $page_data = $paths->pages[$page_id_key];
+        $title = ( isset($page_data['name']) ) ? $page_data['name'] : $paths->nslist[$oldtarget[1]] . htmlspecialchars( str_replace('_', ' ', dirtify_page_id( $oldtarget[0] ) ) );
+        $a = '<a href="' . $url . '">' . $title . '</a>';
+        
+        $url = makeUrlNS($this->namespace, $this->page_id, 'redirect=no', true);
+        $page_id_key = $paths->nslist[ $this->namespace ] . $this->page_id;
+        $page_data = $paths->pages[$page_id_key];
+        $title = ( isset($page_data['name']) ) ? $page_data['name'] : $paths->nslist[$this->namespace] . htmlspecialchars( str_replace('_', ' ', dirtify_page_id( $this->page_id ) ) );
+        $b = '<a href="' . $url . '">' . $title . '</a>';
+        
+        $ob .= '<small>(Redirected to ' . $b . ' from ' . $a . ')<br /></small>';
+      }
+    }
+    
     $ob .= '<div class="error-box"><b>Access to this page is denied.</b><br />This may be because you are not logged in or you have not met certain criteria for viewing this page.</div>';
     
     if ( $this->send_headers )
--- a/includes/pageutils.php	Sun Aug 26 16:48:15 2007 -0400
+++ b/includes/pageutils.php	Sun Aug 26 20:45:33 2007 -0400
@@ -1943,11 +1943,25 @@
         {
           echo '<option value="' . $group['id'] . '">' . $group['name'] . '</option>';
         }
+        // page group selector
+        $groupsel = '';
+        if ( count($response['page_groups']) > 0 )
+        {
+          $groupsel = '<p><label><input type="radio" name="data[scope]" value="page_group" /> A group of pages</label></p>
+                       <p><select name="data[pg_id]">';
+          foreach ( $response['page_groups'] as $grp )
+          {
+            $groupsel .= '<option value="' . $grp['id'] . '">' . htmlspecialchars($grp['name']) . '</option>';
+          }
+          $groupsel .= '</select></p>';
+        }
+        
         echo '</select></p>
               <p><label><input type="radio" name="data[target_type]" value="' . ACL_TYPE_USER . '" /> A specific user</label></p>
               <p>' . $template->username_field('data[target_id_user]') . '</p>
               <p>What should this access rule control?</p>
               <p><label><input name="data[scope]" value="only_this" type="radio" checked="checked" /> Only this page</p>
+              ' . $groupsel . '
               <p><label><input name="data[scope]" value="entire_site" type="radio" /> The entire site</p>
               <div style="margin: 0 auto 0 0; text-align: right;">
                 <input name="data[mode]" value="seltarget" type="hidden" />
@@ -1999,7 +2013,7 @@
           echo '<h3>Create new rule</h3>';
         }
         $type  = ( $response['target_type'] == ACL_TYPE_GROUP ) ? 'group' : 'user';
-        $scope = ( $response['page_id'] ) ? 'this page' : 'this entire site';
+        $scope = ( $response['page_id'] ) ? ( $response['namespace'] == '__PageGroup' ? 'this group of pages' : 'this page' ) : 'this entire site';
         echo 'This panel allows you to edit what the '.$type.' "'.$response['target_name'].'" can do on <b>'.$scope.'</b>. Unless you set a permission to "Deny", these permissions may be overridden by other rules.';
         echo $formstart;
         $parser = $template->makeParserText( $response['template']['acl_field_begin'] );
@@ -2047,7 +2061,7 @@
                 <input type="hidden" name="data[target_type]" value="' . $response['target_type'] . '" />
                 <input type="hidden" name="data[target_id]" value="' . $response['target_id'] . '" />
                 <input type="hidden" name="data[target_name]" value="' . $response['target_name'] . '" />
-                <input type="submit" value="Save changes" />&nbsp;&nbsp;<input type="submit" name="data[act_delete_rule]" value="Delete rule" style="color: #AA0000;" onclick="return confirm(\'Do you really want to delete this ACL rule?\');" />
+                ' . ( ( $response['type'] == 'edit' ) ? '<input type="submit" value="Save changes" />&nbsp;&nbsp;<input type="submit" name="data[act_delete_rule]" value="Delete rule" style="color: #AA0000;" onclick="return confirm(\'Do you really want to delete this ACL rule?\');" />' : '<input type="submit" value="Create rule" />' ) . '
               </div>';
         echo $formend;
         break;
@@ -2097,6 +2111,11 @@
           $parms['page_id']   = false;
           $parms['namespace'] = false;
         }
+        else if ( $parms['scope'] == 'page_group' )
+        {
+          $parms['page_id'] = $parms['pg_id'];
+          $parms['namespace'] = '__PageGroup';
+        }
         
         break;
     }
--- a/index.php	Sun Aug 26 16:48:15 2007 -0400
+++ b/index.php	Sun Aug 26 20:45:33 2007 -0400
@@ -15,7 +15,7 @@
 
   // Set up gzip encoding before any output is sent
   
-  $aggressive_optimize_html = false;
+  $aggressive_optimize_html = true;
   
   global $do_gzip;
   $do_gzip = true;
--- a/plugins/SpecialAdmin.php	Sun Aug 26 16:48:15 2007 -0400
+++ b/plugins/SpecialAdmin.php	Sun Aug 26 20:45:33 2007 -0400
@@ -128,10 +128,19 @@
     $q = $db->sql_query($l);
     while($r = $db->fetchrow())
     {
+      if ( $r['action'] == 'illegal_page' )
+      {
+        list($illegal_id, $illegal_ns) = unserialize($r['page_text']);
+        $url = makeUrlNS($illegal_ns, $illegal_id, false, true);
+        $title = get_page_title_ns($illegal_id, $illegal_ns);
+        $class = ( isPage($paths->nslist[$illegal_ns] . $illegal_id) ) ? '' : ' class="wikilink-nonexistent"';
+        $illegal_link = '<a href="' . $url . '"' . $class . ' onclick="window.open(this.href); return false;">' . $title . '</a>';
+      }
       if($cls == 'row2') $cls = 'row1';
       else $cls = 'row2';
       echo '<tr><td class="'.$cls.'">';
-      switch($r['action']) {
+      switch($r['action'])
+      {
         case "admin_auth_good": echo 'Successful elevated authentication'; if ( !empty($r['page_text']) ) { $level = $session->userlevel_to_string( intval($r['page_text']) ); echo "<br /><small>Authentication level: $level</small>"; } break;
         case "admin_auth_bad":  echo 'Failed elevated authentication'; if ( !empty($r['page_text']) ) { $level = $session->userlevel_to_string( intval($r['page_text']) ); echo "<br /><small>Attempted auth level: $level</small>"; } break;
         case "activ_good": echo 'Successful account activation'; break;
@@ -142,6 +151,7 @@
         case "db_backup": echo 'Database backup created<br /><small>Tables: ' . $r['page_text'] . '</small>'; break;
         case "install_enano": echo "Installed Enano version {$r['page_text']}"; break;
         case "upgrade_enano": echo "Upgraded Enano to version {$r['page_text']}"; break;
+        case "illegal_page": echo "Unauthorized viewing attempt<br /><small>Page: {$illegal_link}</small>"; break;
       }
       echo '</td><td class="'.$cls.'">'.date('d M Y h:i a', $r['time_id']).'</td><td class="'.$cls.'">'.$r['author'].'</td><td class="'.$cls.'" style="cursor: pointer;" onclick="ajaxReverseDNS(this);" title="Click for reverse DNS info">'.$r['edit_summary'].'</td></tr>';
     }
@@ -790,6 +800,12 @@
     return;
   }
   
+  if ( isset($_GET['src']) && $_GET['src'] == 'get' && !empty($_GET['user']) )
+  {
+    $_POST['go'] = true;
+    $_POST['username'] = $_GET['user'];
+  }
+  
   if(isset($_POST['go']))
   {
     // We need the user ID before we can do anything
@@ -2312,7 +2328,7 @@
       @set_time_limit(300); // five minutes
     // Do the actual export
     $aesext = ( defined('SQL_BACKUP_CRYPT') ) ? '.tea' : '';
-    $filename = 'enano_backup_' . date('dmy') . '.sql' . $aesext;
+    $filename = 'enano_backup_' . date('ymd') . '.sql' . $aesext;
     ob_start();
     header('Content-disposition: attachment, filename="'.$filename.'";');
     header('Content-type: application/transact-sql');
@@ -2342,6 +2358,7 @@
     }
     foreach($tables as $t)
     {
+      // THE FOLLOWING COMMENT DOES NOT APPLY AS OF 1.0.
       // Sorry folks - this script CAN'T backup enano_files, enano_search_index, and enano_search_cache due to the sheer size of the tables.
       // If encryption is enabled the log data will be excluded too.
       echo export_table(
--- a/plugins/SpecialUserFuncs.php	Sun Aug 26 16:48:15 2007 -0400
+++ b/plugins/SpecialUserFuncs.php	Sun Aug 26 20:45:33 2007 -0400
@@ -83,6 +83,13 @@
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
+    
+    $paths->add_page(Array(
+      \'name\'=>\'Member list\',
+      \'urlname\'=>\'Memberlist\',
+      \'namespace\'=>\'Special\',
+      \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
+      ));
     ');
 
 // function names are IMPORTANT!!! The name pattern is: page_<namespace ID>_<page URLname, without namespace>
@@ -1066,4 +1073,185 @@
   $template->footer();
 }
 
+function page_Special_Memberlist()
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  $template->header();
+  
+  $startletters = 'abcdefghijklmnopqrstuvwxyz';
+  $startletters = enano_str_split($startletters);
+  $startletter = ( isset($_GET['letter']) ) ? strtolower($_GET['letter']) : '';
+  if ( !in_array($startletter, $startletters) && $startletter != 'chr' )
+  {
+    $startletter = '';
+  }
+  
+  $startletter_sql = $startletter;
+  if ( $startletter == 'chr' )
+  {
+    $startletter_sql = '([^a-z])';
+  }
+  
+  // determine number of rows
+  $q = $db->sql_query('SELECT u.user_id FROM '.table_prefix.'users AS u WHERE u.username REGEXP "^' . $startletter_sql . '" AND u.username != "Anonymous";');
+  if ( !$q )
+    $db->_die();
+  
+  $num_rows = $db->numrows();
+  $db->free_result();
+  
+  // offset
+  $offset = ( isset($_GET['offset']) && strval(intval($_GET['offset'])) === $_GET['offset']) ? intval($_GET['offset']) : 0;
+  
+  // sort order
+  $sortkeys = array(
+      'uid' => 'u.user_id',
+      'username' => 'u.username',
+      'email' => 'u.email'
+    );
+  
+  $sortby = ( isset($_GET['sort']) && isset($sortkeys[$_GET['sort']]) ) ? $_GET['sort'] : 'username';
+  $sort_sqllet = $sortkeys[$sortby];
+  
+  $target_order = ( isset($_GET['orderby']) && in_array($_GET['orderby'], array('ASC', 'DESC')) )? $_GET['orderby'] : 'ASC';
+  
+  $sortorders = array();
+  foreach ( $sortkeys as $k => $_unused )
+  {
+    $sortorders[$k] = ( $sortby == $k ) ? ( $target_order == 'ASC' ? 'DESC' : 'ASC' ) : 'ASC';
+  }
+  
+  // Why 3.3714%? 100 percent / 28 cells, minus a little (0.2% / cell) to account for cell spacing
+  
+  echo '<div class="tblholder">
+          <table border="0" cellspacing="1" cellpadding="4" style="text-align: center;">
+            <tr>';
+  echo '<td class="row1" style="width: 3.3714%;"><a href="' . makeUrlNS('Special', 'Memberlist', 'letter=&sort=' . $sortby . '&orderby=' . $target_order, true) . '">All</a></td>';
+  echo '<td class="row1" style="width: 3.3714%;"><a href="' . makeUrlNS('Special', 'Memberlist', 'letter=chr&sort=' . $sortby . '&orderby=' . $target_order, true) . '">#</a></td>';
+  foreach ( $startletters as $letter )
+  {
+    echo '<td class="row1" style="width: 3.3714%;"><a href="' . makeUrlNS('Special', 'Memberlist', 'letter=' . $letter . '&sort=' . $sortby . '&orderby=' . $target_order, true) . '">' . strtoupper($letter) . '</a></td>';
+  }
+  echo '    </tr>
+          </table>
+        </div>';
+  
+  // formatter parameters
+  $formatter = new MemberlistFormatter();
+  $formatters = array(
+    'username' => array($formatter, 'username'),
+    'user_level' => array($formatter, 'user_level'),
+    'email' => array($formatter, 'email')
+    );
+  
+  // Column markers
+  $headings = '<tr>
+                 <th style="max-width: 50px;">
+                   <a href="' . makeUrlNS('Special', 'Memberlist', 'letter=' . $startletter . '&sort=uid&orderby=' . $sortorders['uid'], true) . '">#</a>
+                 </th>
+                 <th>
+                   <a href="' . makeUrlNS('Special', 'Memberlist', 'letter=' . $startletter . '&sort=username&orderby=' . $sortorders['username'], true) . '">Username</a>
+                 </th>
+                 <th>
+                   <a href="' . makeUrlNS('Special', 'Memberlist', 'letter=' . $startletter . '&sort=email&orderby=' . $sortorders['email'], true) . '">E-mail</a>
+                 </th>
+               </tr>';
+  
+  $q = $db->sql_unbuffered_query('SELECT u.user_id, u.username, u.reg_time, u.email, u.user_level, x.email_public FROM '.table_prefix.'users AS u
+                                    LEFT JOIN '.table_prefix.'users_extra AS x
+                                      ON ( u.user_id = x.user_id )
+                                    WHERE u.username REGEXP "^' . $startletter_sql . '" AND u.username != "Anonymous"
+                                    ORDER BY ' . $sort_sqllet . ' ' . $target_order . ';');
+  if ( !$q )
+    $db->_die();
+  
+  $html = paginate(
+            $q,                                                                                                       // MySQL result resource
+            '<tr>
+               <td class="{_css_class}">{user_id}</td>
+               <td class="{_css_class}" style="text-align: left;">{username}<br /><small>{user_level}</small></td>
+               <td class="{_css_class}">{email}</small></td>
+             </tr>
+             ',                                                                                                       // TPL code for rows
+             $num_rows,                                                                                               // Number of results
+             makeUrlNS('Special', 'Memberlist', 'letter=' . $startletter . '&offset=%s&sort=' . $sortby . '&orderby=' . $target_order ), // Result URL
+             $offset,                                                                                                 // Start at this number
+             25,                                                                                                      // Results per page
+             $formatters,                                                                                             // Formatting hooks
+             '<div class="tblholder">
+                <table border="0" cellspacing="1" cellpadding="4" style="text-align: center;">
+                  ' . $headings . '
+                 ',                                                                                                   // Header (printed before rows)
+             '  ' . $headings . '
+                 </table>
+              </div>
+              '                                                                                                       // Footer (printed after rows)
+          );
+  
+  if ( $num_rows < 1 )
+  {
+    echo '<p>Sorry - no users with usernames that start with that letter could be found.</p>';
+  }
+  else
+  {
+    echo $html;
+  }
+  
+  $template->footer();
+}
+
+/**
+ * Class for formatting results for the memberlist.
+ * @access private
+ */
+
+class MemberlistFormatter
+{
+  function username($username, $row)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    $userpage = $paths->nslist['User'] . sanitize_page_id($username);
+    $class = ( isPage($userpage) ) ? ' title="Click to view this user\'s userpage"' : ' class="wikilink-nonexistent" title="This user hasn\'t created a userpage yet, but you can still view profile details by clicking this link."';
+    $anchor = '<a href="' . makeUrlNS('User', sanitize_page_id($username)) . '"' . $class . '>' . htmlspecialchars($username) . '</a>';
+    if ( $session->user_level >= USER_LEVEL_ADMIN )
+    {
+      $anchor .= ' <small>- <a href="' . makeUrlNS('Special', 'Administration', 'module=' . $paths->nslist['Admin'] . 'UserManager&src=get&username=' . urlencode($username), true) . '"
+                               onclick="ajaxAdminUser(\'' . addslashes(htmlspecialchars($username)) . '\'); return false;">Administer user</a></small>';
+    }
+    return $anchor;
+  }
+  function user_level($level, $row)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    switch ( $level )
+    {
+      case USER_LEVEL_GUEST:
+        $s_level = 'Guest'; break;
+      case USER_LEVEL_MEMBER:
+      case USER_LEVEL_CHPREF:
+        $s_level = 'Member'; break;
+      case USER_LEVEL_MOD:
+        $s_level = 'Moderator'; break;
+      case USER_LEVEL_ADMIN:
+        $s_level = 'Site administrator'; break;
+      default:
+        $s_level = 'Unknown (level ' . $level . ')';
+    }
+    return $s_level;
+  }
+  function email($addy, $row)
+  {
+    if ( $row['email_public'] == '1' )
+    {
+      global $email;
+      $addy = $email->encryptEmail($addy);
+      return $addy;
+    }
+    else
+    {
+      return '<small>&lt;Non-public&gt;</small>';
+    }
+  }
+}
+
 ?>
\ No newline at end of file
--- a/plugins/SpecialUserPrefs.php	Sun Aug 26 16:48:15 2007 -0400
+++ b/plugins/SpecialUserPrefs.php	Sun Aug 26 20:45:33 2007 -0400
@@ -41,6 +41,28 @@
   }
 }
 
+$plugins->attachHook('compile_template', 'userprefs_jbox_setup($button, $tb, $menubtn);');
+
+function userprefs_jbox_setup(&$button, &$tb, &$menubtn)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  if ( $paths->namespace != 'Special' || $paths->cpage['urlname_nons'] != 'Preferences' )
+    return false;
+  
+  $tb .= "<ul>$template->toolbar_menu</ul>";
+  $template->toolbar_menu = '';
+  
+  $button->assign_vars(array(
+      'TEXT' => 'list of registered members',
+      'FLAGS' => '',
+      'PARENTFLAGS' => '',
+      'HREF' => makeUrlNS('Special', 'Memberlist')
+    ));
+  
+  $tb .= $button->run();
+}
+
 function userprefs_menu_html()
 {
   global $userprefs_menu;
--- a/themes/oxygen/css/bleu.css	Sun Aug 26 16:48:15 2007 -0400
+++ b/themes/oxygen/css/bleu.css	Sun Aug 26 20:45:33 2007 -0400
@@ -71,6 +71,16 @@
 div.tblholder th.subhead          { padding: 4px; background-color: #90A0B0; font-weight: bold; text-align: center; color: #FFFFFF; }
 div.tblholder table               { background-color: #FFFFFF; width: 100%; }
 
+div.tblholder th a {
+  color: #FFFFFF !important;
+  text-decoration: underline !important;
+}
+
+div.tblholder th a:hover {
+  color: #FFFF00 !important;
+  text-decoration: underline !important;
+}
+
 /* The "page tools" bar below the site logo but above the page content 
 div.pagebar                       { background-color: #B0D0F0; margin-top: 0px; padding: 3px; font-size: 7pt; }
 div.pagebar a                     { cursor: pointer; padding: 3px; margin-left: 3px; margin-right: 3px; text-decoration: none; color: #406080; }
@@ -222,6 +232,7 @@
 input[type ^="button"], input[type ^="submit"] {
   background-image: url(../images/buttonbg.gif);
   background-repeat: repeat-x;
+  color: #202020;
 }
 
 /* JWS window theming */