diff -r c5409416b61b -r 99ea0b0ac4be includes/log.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/log.php Sun Apr 12 19:26:13 2009 -0400
@@ -0,0 +1,376 @@
+
+ * @license GNU General Public License
+ */
+
+class LogDisplay
+{
+ /**
+ * Criteria for the search.
+ * Structure:
+
+ array(
+ array( 'user', 'Dan' ),
+ array( 'within', 86400 ),
+ array( 'page', 'Main_Page' )
+ )
+
+ * @var array
+ */
+
+ var $criteria = array();
+
+ /**
+ * Adds a criterion for the log display.
+ * @param string Criterion type - user, page, or within
+ * @param string Value - username, page ID, or (int) within # seconds or (string) number + suffix (suffix: d = day, w = week, m = month, y = year) ex: "1w"
+ */
+
+ public function add_criterion($criterion, $value)
+ {
+ switch ( $criterion )
+ {
+ case 'user':
+ case 'page':
+ $this->criteria[] = array($criterion, $value);
+ break;
+ case 'within':
+ if ( is_int($value) )
+ {
+ $this->criteria[] = array($criterion, $value);
+ }
+ else if ( is_string($value) )
+ {
+ $lastchar = substr($value, -1);
+ $amt = intval($value);
+ 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;
+ }
+ $this->criteria[] = array($criterion, $amt);
+ }
+ else
+ {
+ throw new Exception('Invalid value type for within');
+ }
+ break;
+ default:
+ throw new Exception('Unknown criterion type');
+ break;
+ }
+ }
+
+ /**
+ * Build the necessary SQL query.
+ * @param int Optional: offset, defaults to 0
+ * @param int Optional: page size, defaults to 0; 0 = don't limit
+ */
+
+ public function build_sql($offset = 0, $page_size = 0, $just_page_count = false)
+ {
+ global $db, $session, $paths, $template, $plugins; // Common objects
+
+ $where_extra = '';
+ $where_bits = array(
+ 'user' => array(),
+ 'page' => array()
+ );
+ foreach ( $this->criteria as $criterion )
+ {
+ list($type, $value) = $criterion;
+ switch($type)
+ {
+ case 'user':
+ $where_bits['user'][] = "author = '" . $db->escape($value) . "'";
+ break;
+ case 'page':
+ list($page_id, $namespace) = RenderMan::strToPageId($value);
+ $where_bits['page'][] = "page_id = '" . $db->escape($page_id) . "' AND namespace = '" . $db->escape($namespace) . "'";
+ break;
+ case 'within':
+ $threshold = time() - $value;
+ $where_extra .= "\n AND time_id > $threshold";
+ break;
+ }
+ }
+ if ( !empty($where_bits['user']) )
+ {
+ $where_extra .= "\n AND ( " . implode(" OR ", $where_bits['user']) . " )";
+ }
+ if ( !empty($where_bits['page']) )
+ {
+ $where_extra .= "\n AND ( (" . implode(") OR (", $where_bits['page']) . ") )";
+ }
+ $limit = ( $page_size > 0 ) ? "\n LIMIT $offset, $page_size" : '';
+ $columns = ( $just_page_count ) ? 'COUNT(*)' : 'log_id, action, page_id, namespace, CHAR_LENGTH(page_text) AS revision_size, author, time_id, edit_summary, minor_edit';
+ $sql = 'SELECT ' . $columns . ' FROM ' . table_prefix . "logs\n"
+ . " WHERE log_type = 'page' AND is_draft != 1$where_extra\n"
+ . " ORDER BY log_id DESC $limit;";
+
+ return $sql;
+ }
+
+ /**
+ * Get data!
+ * @param int Offset, defaults to 0
+ * @param int Page size, if 0 (default) returns entire table (danger Will Robinson!)
+ * @return array
+ */
+
+ public function get_data($offset, $page_size)
+ {
+ global $db, $session, $paths, $session, $plugins; // Common objects
+ $sql = $this->build_sql($offset, $page_size);
+ if ( !$db->sql_query($sql) )
+ $db->_die();
+
+ $return = array();
+ $deplist = array();
+ $idlist = array();
+ while ( $row = $db->fetchrow() )
+ {
+ $return[ $row['log_id'] ] = $row;
+ if ( $row['action'] === 'edit' )
+ {
+ // This is a page revision; its parent needs to be found
+ $pagekey = serialize(array($row['page_id'], $row['namespace']));
+ $deplist[$pagekey] = "( page_id = '" . $db->escape($row['page_id']) . "' AND namespace = '" . $db->escape($row['namespace']) . "' AND log_id < {$row['log_id']} )";
+ // if we already have a revision from this page in the dataset, we've found its parent
+ if ( isset($idlist[$pagekey]) )
+ {
+ $child =& $return[ $idlist[$pagekey] ];
+ $child['parent_size'] = $row['revision_size'];
+ $child['parent_revid'] = $row['log_id'];
+ $child['parent_time'] = $row['time_id'];
+ unset($child);
+ }
+ $idlist[$pagekey] = $row['log_id'];
+ }
+ }
+
+ // Second query fetches all parent revision data
+ // (maybe we have no edits?? check deplist)
+
+ if ( !empty($deplist) )
+ {
+ // FIXME: inefficient. damn GROUP BY for not obeying ORDER BY, it means we can't group and instead have to select
+ // all previous revisions of page X and discard all but the first one we find.
+ $where_extra = implode("\n OR ", $deplist);
+ $sql = 'SELECT log_id, page_id, namespace, CHAR_LENGTH(page_text) AS revision_size, time_id FROM ' . table_prefix . "logs\n"
+ . " WHERE log_type = 'page' AND action = 'edit'\n AND ( $where_extra )\n"
+ // . " GROUP BY page_id, namespace\n"
+ . " ORDER BY log_id DESC;";
+ if ( !$db->sql_query($sql) )
+ $db->_die();
+
+ while ( $row = $db->fetchrow() )
+ {
+ $pagekey = serialize(array($row['page_id'], $row['namespace']));
+ if ( isset($idlist[$pagekey]) )
+ {
+ $child =& $return[ $idlist[$pagekey] ];
+ $child['parent_size'] = $row['revision_size'];
+ $child['parent_revid'] = $row['log_id'];
+ $child['parent_time'] = $row['time_id'];
+ unset($child, $idlist[$pagekey]);
+ }
+ }
+ }
+
+ // final iteration goes through all edits and if there's not info on the parent, sets to 0. It also calculates size change.
+ foreach ( $return as &$row )
+ {
+ if ( $row['action'] === 'edit' && !isset($row['parent_revid']) )
+ {
+ $row['parent_revid'] = 0;
+ $row['parent_size'] = 0;
+ }
+ if ( $row['action'] === 'edit' )
+ {
+ $row['size_delta'] = $row['revision_size'] - $row['parent_size'];
+ }
+ }
+
+ return array_values($return);
+ }
+
+ /**
+ * Get the number of rows that will be in the result set.
+ * @return int
+ */
+
+ public function get_row_count()
+ {
+ global $db, $session, $paths, $session, $plugins; // Common objects
+
+ if ( !$db->sql_query( $this->build_sql(0, 0, true) ) )
+ $db->_die();
+
+ list($count) = $db->fetchrow_num();
+ return $count;
+ }
+
+ /**
+ * Formats a result row into pretty HTML.
+ * @param array dataset from LogDisplay::get_data()
+ * @static
+ * @return string
+ */
+
+ public static function render_row($row)
+ {
+ global $db, $session, $paths, $session, $plugins; // Common objects
+ global $lang;
+
+ $html = '';
+
+ $pagekey = ( isset($paths->nslist[$row['namespace']]) ) ? $paths->nslist[$row['namespace']] . $row['page_id'] : $row['namespace'] . ':' . $row['page_id'];
+ $pagekey = sanitize_page_id($pagekey);
+
+ // diff button
+ if ( $row['action'] == 'edit' && !empty($row['parent_revid']) )
+ {
+ $html .= '(';
+ if ( isPage($pagekey) )
+ {
+ $html .= '';
+ }
+ $html .= $lang->get('pagetools_rc_btn_diff');
+ if ( isPage($pagekey) )
+ {
+ $html .= '';
+ }
+ $html .= ') ';
+ }
+
+ // hist button
+ $html .= '(';
+ if ( isPage($pagekey) )
+ {
+ $html .= '';
+ }
+ $html .= $lang->get('pagetools_rc_btn_hist');
+ if ( isPage($pagekey) )
+ {
+ $html .= '';
+ }
+ $html .= ') . . ';
+
+ // new page?
+ if ( $row['action'] == 'edit' && empty($row['parent_revid']) )
+ {
+ $html .= 'N ';
+ }
+
+ // link to the page
+ $cls = ( isPage($pagekey) ) ? '' : ' class="wikilink-nonexistent"';
+ $html .= '' . htmlspecialchars(get_page_title_ns($row['page_id'], $row['namespace'])) . '; ';
+
+ // date
+ $today = time() - ( time() % 86400 );
+ $date = ( $row['time_id'] > $today ) ? '' : MemberlistFormatter::format_date($row['time_id']) . ' ';
+ $date .= date('h:i:s', $row['time_id']);
+ $html .= "$date . . ";
+
+ // size counter
+ if ( $row['action'] == 'edit' )
+ {
+ $css = self::get_css($row['size_delta']);
+ $size_change = number_format($row['size_delta']);
+ if ( substr($size_change, 0, 1) != '-' )
+ $size_change = "+$size_change";
+
+ $html .= "({$size_change})";
+ $html .= ' . . ';
+ }
+ else
+ {
+ $html .= " FIXME {$row['action']} . . ";
+ }
+
+ // link to userpage
+ $cls = ( isPage($paths->nslist['User'] . $row['author']) ) ? '' : ' class="wikilink-nonexistent"';
+ $rank_info = $session->get_user_rank($row['author']);
+ $html .= '' . htmlspecialchars($row['author']) . ' ';
+ $html .= '(';
+ $html .= '';
+ $html .= $lang->get('pagetools_rc_btn_pm');
+ $html .= ', ';
+ $html .= '';
+ $html .= $lang->get('pagetools_rc_btn_usertalk');
+ $html .= '';
+ $html .= ') . . ';
+
+ // Edit summary
+ $html .= '(';
+ if ( empty($row['edit_summary']) )
+ {
+ $html .= '' . $lang->get('history_summary_none_given') . '';
+ }
+ else
+ {
+ $html .= RenderMan::parse_internal_links(htmlspecialchars($row['edit_summary']));
+ }
+ $html .= ')';
+
+ return $html;
+ }
+
+ /**
+ * Return CSS blurb for size delta
+ * @return string
+ * @static
+ * @access private
+ */
+
+ private static function get_css($change_size)
+ {
+ // Hardly changed at all? Return a gray
+ if ( $change_size <= 5 && $change_size >= -5 )
+ return 'color: #808080;';
+ // determine saturation based on size of change (1-500 bytes)
+ $change_abs = abs($change_size);
+ $index = 0x70 * ( $change_abs / 500 );
+ if ( $index > 0x70 )
+ $index = 0x70;
+ $index = $index + 0x40;
+ $index = dechex($index);
+ if ( strlen($index) < 2 )
+ $index = "0$index";
+ $css = ( $change_size > 0 ) ? "color: #00{$index}00;" : "color: #{$index}0000;";
+ if ( $change_abs > 500 )
+ $css .= ' font-weight: bold;';
+ return $css;
+ }
+}
+
+?>