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.
--- 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" /> <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" /> <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><Non-public></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 */