# HG changeset patch # User Dan # Date 1239757333 14400 # Node ID 94c1ff9842869ec88fc10d3a07ae758dc71125ef # Parent 44302dd20d62bacebfeb7daf502eab574920cceb Finished core of log display interface including filter management. There is still a bit of a to-do list, especially regarding rollbacks and reuploads. diff -r 44302dd20d62 -r 94c1ff984286 includes/clientside/css/enano-shared.css --- a/includes/clientside/css/enano-shared.css Mon Apr 13 17:28:24 2009 -0400 +++ b/includes/clientside/css/enano-shared.css Tue Apr 14 21:02:13 2009 -0400 @@ -1018,3 +1018,15 @@ filter: alpha(opacity=60); } +.emptymessage { + line-height: 140px; + color: #a0a0a0; + border-bottom-width: 0 !important; + text-align: center; + font-size: xx-large; + font-weight: normal; +} + +.log_addfilter { + display: none; +} diff -r 44302dd20d62 -r 94c1ff984286 includes/clientside/static/enano-lib-basic.js --- a/includes/clientside/static/enano-lib-basic.js Mon Apr 13 17:28:24 2009 -0400 +++ b/includes/clientside/static/enano-lib-basic.js Tue Apr 14 21:02:13 2009 -0400 @@ -515,7 +515,8 @@ ajaxComments(); break; case 'edit': - ajaxEditor(); + var revid = ( $_REQUEST['rev'] ) ? parseInt($_REQUEST['rev']) : false; + ajaxEditor(revid); break; case 'login': ajaxStartLogin(); diff -r 44302dd20d62 -r 94c1ff984286 includes/clientside/static/paginate.js --- a/includes/clientside/static/paginate.js Mon Apr 13 17:28:24 2009 -0400 +++ b/includes/clientside/static/paginate.js Tue Apr 14 21:02:13 2009 -0400 @@ -268,7 +268,7 @@ } } -window.paginator_goto = function(parentobj, this_page, num_pages, perpage, url_string) +window.paginator_goto = function(parentobj, this_page, num_pages, perpage, additive, url_string) { load_component('flyin'); @@ -286,7 +286,7 @@ var vtmp = 'input_' + Math.floor(Math.random() * 1000000); var regex = new RegExp('\"', 'g'); var submit_target = ( typeof(url_string) == 'object' ) ? ( toJSONString(url_string) ).replace(regex, '\'') : 'unescape(\'' + escape(url_string) + '\')'; - var onclick = 'paginator_submit(this, '+num_pages+', '+perpage+', '+submit_target+'); return false;'; + var onclick = 'paginator_submit(this, '+num_pages+', '+perpage+', '+additive+', '+submit_target+'); return false;'; div.innerHTML = $lang.get('paginate_lbl_goto_page') + '
»×'; var body = document.getElementsByTagName('body')[0]; @@ -315,11 +315,11 @@ div.style.left = left_pos + 'px'; } -window.paginator_submit = function(obj, max, perpage, formatstring) +window.paginator_submit = function(obj, max, perpage, additive, formatstring) { var userinput = obj.previousSibling.previousSibling.value; userinput = parseInt(userinput); - var offset = ( userinput - 1 ) * perpage; + var offset = (( userinput - 1 ) * perpage) + additive; if ( userinput > max || isNaN(userinput) || userinput < 1 ) { load_component(['messagebox', 'fadefilter', 'flyin']); diff -r 44302dd20d62 -r 94c1ff984286 includes/functions.php --- a/includes/functions.php Mon Apr 13 17:28:24 2009 -0400 +++ b/includes/functions.php Tue Apr 14 21:02:13 2009 -0400 @@ -430,9 +430,10 @@ global $db, $session, $paths, $template, $plugins; // Common objects global $lang; - // POST check added in 1.1.x because Firefox asks us if we want to "resend the form + // POST check added in 1.1.x because Firefox 3.0 asks us if we want to "resend the form // data to the new location", which can be confusing for some users. - if ( $timeout == 0 && empty($_POST) ) + $is_firefox_3 = ( strstr(@$_SERVER['HTTP_USER_AGENT'], 'Firefox/3.') ) ? true : false; + if ( $timeout == 0 && ( empty($_POST) || !$is_firefox_3 ) ) { header('Location: ' . $url); header('Content-length: 0'); @@ -2288,7 +2289,7 @@ $list[] = $lower + $i; } } - $url = sprintf($result_url, '0'); + $url = sprintf($result_url, $start_add); $link = ( 0 == $current_page ) ? "" . $lang->get('paginate_btn_first') . "" : "« " . $lang->get('paginate_btn_first') . ""; $blk->assign_vars(array( 'CLASS'=>$cls, @@ -2330,7 +2331,7 @@ } - $inner .= '↓'; + $inner .= '↓'; $paginator = "\n$begin$inner$end\n"; return $paginator; diff -r 44302dd20d62 -r 94c1ff984286 includes/log.php --- a/includes/log.php Mon Apr 13 17:28:24 2009 -0400 +++ b/includes/log.php Tue Apr 14 21:02:13 2009 -0400 @@ -112,10 +112,19 @@ switch($type) { case 'user': - $where_bits['user'][] = "author = '" . $db->escape($value) . "'"; + $where_bits['user'][] = "author = '" . $db->escape(str_replace('_', ' ', $value)) . "'"; break; case 'action': - $where_bits['action'][] = "action = '" . $db->escape($value) . "'"; + if ( $value === 'protect' ) + { + $where_bits['action'][] = "action = 'prot'"; + $where_bits['action'][] = "action = 'unprot'"; + $where_bits['action'][] = "action = 'semiprot'"; + } + else + { + $where_bits['action'][] = "action = '" . $db->escape($value) . "'"; + } break; case 'page': list($page_id, $namespace) = RenderMan::strToPageId($value); @@ -249,6 +258,16 @@ } /** + * Returns the list of criteria + * @return array + */ + + public function get_criteria() + { + return $this->criteria; + } + + /** * Formats a result row into pretty HTML. * @param array dataset from LogDisplay::get_data() * @static @@ -278,6 +297,10 @@ { $html .= ''; } + if ( $row['parent_revid'] > 0 && isPage($pagekey) ) + { + $html .= ', ' . $lang->get('pagetools_rc_btn_undo') . ''; + } $html .= ') '; } @@ -299,6 +322,11 @@ { $html .= 'N '; } + // minor edit? + if ( $row['action'] == 'edit' && $row['minor_edit'] ) + { + $html .= 'm '; + } // link to the page $cls = ( isPage($pagekey) ) ? '' : ' class="wikilink-nonexistent"'; @@ -306,7 +334,7 @@ // date $today = time() - ( time() % 86400 ); - $date = ( $row['time_id'] > $today ) ? '' : MemberlistFormatter::format_date($row['time_id']) . ' '; + $date = MemberlistFormatter::format_date($row['time_id']) . ' '; $date .= date('h:i:s', $row['time_id']); $html .= "$date . . "; @@ -373,7 +401,11 @@ 'semiprot' => 'log_action_protect_semi', 'delete' => 'log_action_delete' ); - $reason = ( !empty($row['edit_summary']) ) ? htmlspecialchars($row['edit_summary']) : '' . $lang->get('log_msg_no_reason_provided') . ''; + + if ( $row['edit_summary'] === '__REVERSION__' ) + $reason = '' . $lang->get('log_msg_reversion') . ''; + else + $reason = ( !empty($row['edit_summary']) ) ? htmlspecialchars($row['edit_summary']) : '' . $lang->get('log_msg_no_reason_provided') . ''; $html .= $lang->get($stringmap[$row['action']], array('reason' => $reason)); } diff -r 44302dd20d62 -r 94c1ff984286 includes/pageutils.php --- a/includes/pageutils.php Mon Apr 13 17:28:24 2009 -0400 +++ b/includes/pageutils.php Tue Apr 14 21:02:13 2009 -0400 @@ -1658,7 +1658,17 @@ $db->free_result($q1); $row2 = $db->fetchrow($q2); $db->free_result($q2); - if(sizeof($row1) < 1 || sizeof($row2) < 2) return 'Couldn\'t find any rows that matched the query. The time ID probably doesn\'t exist in the logs table.'; + if(sizeof($row1) < 1 || sizeof($row2) < 2) + { + if ( !$q1 = $db->sql_query('SELECT time_id,page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE time_id = ' . $id1 . ' AND log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';')) return 'MySQL error: ' . $db->get_error(); + if ( !$q2 = $db->sql_query('SELECT time_id,page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE time_id = ' . $id2 . ' AND log_type=\'page\' AND action=\'edit\' AND page_id=\'' . $page_id . '\' AND namespace=\'' . $namespace . '\';')) return 'MySQL error: ' . $db->get_error(); + $row1 = $db->fetchrow($q1); + $db->free_result($q1); + $row2 = $db->fetchrow($q2); + $db->free_result($q2); + if(sizeof($row1) < 1 || sizeof($row2) < 2) + return 'Couldn\'t find any rows that matched the query. The time ID probably doesn\'t exist in the logs table.'; + } $text1 = $row1['page_text']; $text2 = $row2['page_text']; $time1 = enano_date('F d, Y h:i a', $row1['time_id']); diff -r 44302dd20d62 -r 94c1ff984286 language/english/core.json --- a/language/english/core.json Mon Apr 13 17:28:24 2009 -0400 +++ b/language/english/core.json Tue Apr 14 21:02:13 2009 -0400 @@ -781,10 +781,16 @@ unit_terabytes_short: 'TB', unit_pixels: 'pixels', unit_pixels_short: 'px', + unit_second: 'second', + unit_seconds: 'seconds', unit_day: 'day', unit_days: 'days', unit_week: 'week', - unit_weeks: 'weeks' + unit_weeks: 'weeks', + unit_month: 'month', + unit_months: 'months', + unit_year: 'year', + unit_years: 'years', } } }; diff -r 44302dd20d62 -r 94c1ff984286 language/english/tools.json --- a/language/english/tools.json Mon Apr 13 17:28:24 2009 -0400 +++ b/language/english/tools.json Tue Apr 14 21:02:13 2009 -0400 @@ -146,6 +146,7 @@ // Recent changes rc_btn_diff: 'diff', rc_btn_hist: 'hist', + rc_btn_undo: 'undo', rc_btn_pm: 'PM', rc_btn_usertalk: 'comment', }, @@ -156,8 +157,34 @@ action_protect_none: 'Unprotected page (%reason%)', action_protect_semi: 'Semiprotected page (%reason%)', action_protect_full: 'Protected page (%reason%)', + action_protect: 'Protect and unprotect', + action_edit: 'Edit', + + breadcrumb_author: 'Author: %user%', + breadcrumb_within: 'Newer than: %time%', + breadcrumb_page: 'Page: %page%', + breadcrumb_action: 'Action: %action%', msg_no_reason_provided: 'No reason provided', + msg_reversion: 'Reversion of previous action', + msg_no_results: 'No results', + msg_no_filters: 'All site logs', + + form_filtertype_user: 'Author', + form_filtertype_within: 'Within', + form_filtertype_page: 'Page', + form_filtertype_action: 'Action', + formaction_rename: 'Rename', + formaction_create: 'Create page', + formaction_delete: 'Delete page', + + heading_addfilter: 'Add a filter', + heading_logdisplay: 'Log filter results', + + btn_add_filter: 'Add filter', + err_addfilter_field_empty: 'The filter was not added because you didn\'t enter a valid value in the field.', + + err_access_denied: 'You don\'t have permission to view page logs.', } } }; diff -r 44302dd20d62 -r 94c1ff984286 plugins/SpecialLog.php --- a/plugins/SpecialLog.php Mon Apr 13 17:28:24 2009 -0400 +++ b/plugins/SpecialLog.php Tue Apr 14 21:02:13 2009 -0400 @@ -39,16 +39,52 @@ global $lang; global $output; + // FIXME: This doesn't currently prohibit viewing of aggregate logs that might include a page for which + // + + // FIXME: This is a real hack. We're trying to get permissions on a random non-existent article, which + // effectively forces calculation to occur based on site-wide permissions. + $pid = ''; + for ( $i = 0; $i < 32; $i++ ) + { + $pid .= chr(mt_rand(32, 126)); + } + $perms = $session->fetch_page_acl($pid, 'Article'); + $perms_changed = false; + require_once(ENANO_ROOT . '/includes/log.php'); $log = new LogDisplay(); $page = 1; $pagesize = 50; + $fmt = 'full'; - if ( $params = explode('/', $paths->getAllParams()) ) + if ( $params = $paths->getAllParams() ) { + if ( $params === 'AddFilter' && !empty($_POST['type']) && !empty($_POST['value']) ) + { + $type = $_POST['type']; + if ( $type == 'within' ) + $value = strval(intval($_POST['value']['within'])) . $_POST['value']['withinunits']; + else + $value = $_POST['value'][$type]; + + $value = str_replace('/', '.2f', sanitize_page_id($value)); + + if ( empty($value) || ( $type == 'within' && intval($value) == 0 ) ) + { + $adderror = $lang->get('log_err_addfilter_field_empty'); + } + + $append = ( !empty($_POST['existing_filters']) ) ? "{$_POST['existing_filters']}/" : ''; + $url = makeUrlNS('Special', "Log/{$append}{$type}={$value}"); + + redirect($url, '', '', 0); + } + $params = explode('/', $params); foreach ( $params as $param ) { - if ( preg_match('/^([a-z]+)=(.+?)$/', $param, $match) ) + $param = str_replace('.2f', '/', dirtify_page_id($param)); + if ( preg_match('/^([a-z!]+)=(.+?)$/', $param, $match) ) { $name =& $match[1]; $value =& $match[2]; @@ -60,27 +96,301 @@ case 'size': $pagesize = intval($value); break; + case 'fmt': + switch($value) + { + case 'barenaked': + case 'ajax': + $fmt = 'naked'; + $output = new Output_Naked(); + break; + } + break; + case 'page': + if ( get_class($perms) == 'sessionManager' ) + { + unset($perms); + list($pid, $ns) = RenderMan::strToPageID($value); + $perms = $session->fetch_page_acl($pid, $ns); + if ( !$perms->get_permissions('history_view') ) + { + die_friendly($lang->get('etc_access_denied_short'), '

' . $lang->get('log_err_access_denied') . '

'); + } + } + // no break here on purpose default: - $log->add_criterion($name, $value); + try + { + $log->add_criterion($name, $value); + } + catch ( Exception $e ) + { + } break; } } } } - + if ( !$perms->get_permissions('history_view') ) + { + die_friendly($lang->get('etc_access_denied_short'), '

' . $lang->get('log_err_access_denied') . '

'); + } + $page--; $rowcount = $log->get_row_count(); - $result_url = makeUrlNS('Special', 'Log/' . rtrim(preg_replace('|/?resultpage=(.+?)/?|', '/', $paths->getAllParams()), '/') . '/resultpage=%s', false, true); + $result_url = makeUrlNS('Special', 'Log/' . rtrim(preg_replace('|/?resultpage=([0-9]+)/?|', '/', $paths->getAllParams()), '/') . '/resultpage=%s', false, true); $paginator = generate_paginator($page, ceil($rowcount / $pagesize), $result_url); $dataset = $log->get_data($page * $pagesize, $pagesize); $output->header(); - echo $paginator; - foreach ( $dataset as $row ) + + // breadcrumbs + if ( $fmt != 'naked' ) { - echo LogDisplay::render_row($row) . '
'; + echo ''; + + // form + ?> + + + +
+ getAllParams()), '/'); + echo ''; + ?> + + ' . $adderror . ''; + } + ?> +
+ + + + + + + + + + + +
+ get('log_heading_addfilter'); ?> +
+ + +
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+ + + + '; + } + + if ( $rowcount > 0 ) + { + // we have some results, show pagination + result list + echo '

' . $lang->get('log_heading_logdisplay') . '

'; + + echo $paginator; + // padding + echo '
'; + foreach ( $dataset as $row ) + { + echo LogDisplay::render_row($row) . '
'; + } + echo $paginator; + } + else + { + // no results + echo '

' . $lang->get('log_msg_no_results') . '

'; + } + + if ( $fmt != 'naked' ) + echo ' '; + $output->footer(); } +function speciallog_generate_breadcrumbs($criteria) +{ + global $db, $session, $paths, $template, $plugins; // Common objects + global $lang; + + if ( count($criteria) == 0 ) + { + return $lang->get('log_msg_no_filters'); + } + + $html = array(); + foreach ( $criteria as $criterion ) + { + list($type, $value) = $criterion; + switch($type) + { + case 'user': + $rank_info = $session->get_user_rank($value); + $user_link = ''; + $user_link .= htmlspecialchars(str_replace('_', ' ', $value)) . ''; + + $crumb = $lang->get('log_breadcrumb_author', array('user' => $user_link)); + break; + case 'page': + $crumb = $lang->get('log_breadcrumb_page', array('page' => '' . htmlspecialchars(get_page_title($value)) . '')); + break; + case 'action': + $crumb = $lang->get('log_breadcrumb_action', array('action' => htmlspecialchars($lang->get("log_action_{$value}")))); + break; + case 'within': + $value = intval($value); + if ( $value % 31536000 == 0 ) + { + $n = $value / 31536000; + $value = "$n " . $lang->get( $n > 1 ? 'etc_unit_years' : 'etc_unit_year' ); + } + else if ( $value % 2592000 == 0 ) + { + $n = $value / 2592000; + $value = "$n " . $lang->get( $n > 1 ? 'etc_unit_months' : 'etc_unit_month' ); + } + else if ( $value % 604800 == 0 ) + { + $n = $value / 604800; + $value = "$n " . $lang->get( $n > 1 ? 'etc_unit_weeks' : 'etc_unit_week' ); + } + else if ( $value % 86400 == 0 ) + { + $n = $value / 86400; + $value = "$n " . $lang->get( $n > 1 ? 'etc_unit_days' : 'etc_unit_day' ); + } + else + { + $value = "$value " . $lang->get( $value > 1 ? 'etc_unit_seconds' : 'etc_unit_second' ); + } + $crumb = $lang->get('log_breadcrumb_within', array('time' => $value)); + break; + } + $html[] = $crumb . ' ' . speciallog_crumb_remove_link($criterion); + } + return implode(' » ', $html); +} + +function speciallog_crumb_remove_link($criterion) +{ + global $db, $session, $paths, $template, $plugins; // Common objects + global $lang; + + list($type, $value) = $criterion; + + $params = explode('/', dirtify_page_id($paths->getAllParams())); + foreach ( $params as $i => $param ) + { + if ( $param === "$type=$value" ) + { + unset($params[$i]); + break; + } + else if ( $type === 'within' ) + { + list($ptype, $pvalue) = explode('=', $param); + if ( $ptype !== 'within' ) + continue; + + $lastchar = substr($pvalue, -1); + $amt = intval($pvalue); + switch($lastchar) + { + case 'd': + $amt = $amt * 86400; + break; + case 'w': + $amt = $amt * 604800; + break; + case 'm': + $amt = $amt * 2592000; + break; + case 'y': + $amt = $amt * 31536000; + break; + } + if ( $amt === $value ) + { + unset($params[$i]); + break; + } + } + } + if ( count($params) > 0 ) + { + $params = implode('/', $params); + $url = makeUrlNS('Special', "Log/$params", false, true); + } + else + { + $url = makeUrlNS('Special', "Log", false, true); + } + + return '(x)'; +} diff -r 44302dd20d62 -r 94c1ff984286 plugins/SpecialUserFuncs.php --- a/plugins/SpecialUserFuncs.php Mon Apr 13 17:28:24 2009 -0400 +++ b/plugins/SpecialUserFuncs.php Tue Apr 14 21:02:13 2009 -0400 @@ -1255,7 +1255,8 @@ $template->footer(); } -function page_Special_Contributions() { +function page_Special_Contributions() +{ global $db, $session, $paths, $template, $plugins; // Common objects global $lang; @@ -1274,158 +1275,8 @@ return; } - $user = $db->escape($user); - $q = 'SELECT log_type, time_id, action, date_string, page_id, namespace, author, edit_summary, minor_edit, page_id, namespace, ( action = \'edit\' ) AS is_edit FROM '.table_prefix.'logs WHERE author=\''.$user.'\' AND log_type=\'page\' AND is_draft != 1 ORDER BY is_edit DESC, time_id DESC;'; - $q = $db->sql_query($q); - if ( !$q ) - $db->_die('SpecialUserFuncs selecting contribution data'); - - echo '

' . $lang->get('userfuncs_contribs_heading_edits') . '

'; - - $cnt_edits = 0; - $cnt_other = 0; - $current = 'cnt_edits'; - $cls = 'row2'; - - while ( $row = $db->fetchrow($q) ) - { - if ( $current == 'cnt_edits' && $row['is_edit'] != 1 ) - { - // No longer processing page edits - split the table - if ( $cnt_edits == 0 ) - { - echo '

' . $lang->get('userfuncs_contribs_msg_no_edits') . '

'; - } - else - { - echo ''; - echo '

' . $lang->get('userfuncs_contribs_heading_other') . '

'; - } - $current = 'cnt_other'; - $cls = 'row2'; - } - if ( $$current == 0 ) - { - echo '
- '; - echo ' - '; - echo ' '; - if ( $current == 'cnt_edits' ) - { - echo ' '; - } - echo ' '; - if ( $current == 'cnt_other' ) - { - echo ' - - '; - } - echo ' - '; - } - $$current++; - $cls = ( $cls == 'row1' ) ? 'row2' : 'row1'; - - echo ''; - - // date & time - echo ' '; - - // page & link to said page - echo ' '; - - switch ( $row['action'] ) - { - case 'edit': - if ( $row['edit_summary'] == 'Automatic backup created when logs were purged' ) - { - $row['edit_summary'] = $lang->get('history_summary_clearlogs'); - } - else if ( empty($row['edit_summary']) ) - { - $row['edit_summary'] = '' . $lang->get('history_summary_none_given') . ''; - } - echo ' '; - if ( $row['minor_edit'] == 1 ) - { - echo ''; - } - else - { - echo ''; - } - break; - case 'prot': - echo ' '; - echo ' '; - echo ' '; - break; - case 'unprot': - echo ' '; - echo ' '; - echo ' '; - break; - case 'semiprot': - echo ' '; - echo ' '; - echo ' '; - break; - case 'rename': - echo ' '; - echo ' '; - echo ' '; - break; - case 'create': - echo ' '; - echo ' '; - echo ' '; - break; - case 'delete': - echo ' '; - echo ' '; - echo ' '; - break; - case 'reupload': - echo ' '; - echo ' '; - echo ' '; - break; - } - - // actions column - echo ' '; - - if ( $current == 'cnt_other' && $cnt_edits + $cnt_other >= $db->numrows($q) ) - { - echo '
' . $lang->get('history_col_datetime') . '' . $lang->get('history_col_page') . '' . $lang->get('history_col_summary') . '' . $lang->get('history_col_minor') . '' . $lang->get('history_col_action_taken') . '' . $lang->get('history_col_extra') . '' . $lang->get('history_col_actions') . '
' . enano_date('d M Y h:i a', $row['time_id']) . '' . get_page_title_ns($row['page_id'], $row['namespace']) . '' . $row['edit_summary'] . 'M' . $lang->get('history_log_protect') . '' . $lang->get('history_extra_reason') . ' ' . $row['edit_summary'] . '' . $lang->get('history_log_unprotect') . '' . $lang->get('history_extra_reason') . ' ' . $row['edit_summary'] . '' . $lang->get('history_log_semiprotect') . '' . $lang->get('history_extra_reason') . ' ' . $row['edit_summary'] . '' . $lang->get('history_log_rename') . '' . $lang->get('history_extra_oldtitle') . ' ' . htmlspecialchars($row['edit_summary']) . '' . $lang->get('history_log_create') . '' . $lang->get('history_log_delete') . '' . $lang->get('history_extra_reason') . ' ' . $row['edit_summary'] . '' . $lang->get('history_log_uploadnew') . '' . $lang->get('history_extra_reason') . ' ' . $row['edit_summary'] . ''; - if ( $row['is_edit'] == 1 ) - { - echo ' ' . $lang->get('history_action_view') . ' | '; - echo ' ' . $lang->get('history_action_restore') . ''; - } - else - { - echo ' ' . $lang->get('history_action_revert') . ''; - } - echo '
'; - } - } - - if ( $current == 'cnt_edits' ) - { - // no "other" edits, close the table - if ( $cnt_edits > 0 ) - echo ''; - else - echo '

' . $lang->get('userfuncs_contribs_msg_no_edits') . '

'; - echo '

' . $lang->get('userfuncs_contribs_heading_other') . '

'; - echo '

' . $lang->get('userfuncs_contribs_msg_no_other') . '

'; - } - - $db->free_result(); - $template->footer(); + $url = makeUrlNS("Special", "Log/user={$user}"); + redirect($url, '', '', 0); } function page_Special_ChangeStyle()