includes/log.php
changeset 1227 bdac73ed481e
parent 1212 db2edac4e5a7
--- a/includes/log.php	Sun Mar 28 21:49:26 2010 -0400
+++ b/includes/log.php	Sun Mar 28 23:10:46 2010 -0400
@@ -22,469 +22,469 @@
 
 class LogDisplay
 {
-  /**
-   * Criteria for the search.
-   * Structure:
-   <code>
-   array(
-       array( 'user', 'Dan' ),
-       array( 'within', 86400 ),
-       array( 'page', 'Main_Page' )
-     )
-   </code>
-   * @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':
-      case 'action':
-        $this->criteria[] = array($criterion, $value);
-        break;
-      case 'minor':
-        $this->criteria[] = array($criterion, intval($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(),
-        'action' => array()
-      );
-    foreach ( $this->criteria as $criterion )
-    {
-      list($type, $value) = $criterion;
-      switch($type)
-      {
-        case 'user':
-          $where_bits['user'][] = "author = '" . $db->escape(str_replace('_', ' ', $value)) . "'";
-          break;
-        case 'action':
-          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);
-          $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;
-        case 'minor':
-          if ( $value == 1 )
-            $where_extra .= "\n    AND ( minor_edit = 1 OR action != 'edit' )";
-          else
-            $where_extra .= "\n    AND minor_edit != 1";
-          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']) . ") )";
-    }
-    if ( !empty($where_bits['action']) )
-    {
-      $where_extra .= "\n    AND ( (" . implode(") OR (", $where_bits['action']) . ") )";
-    }
-    if ( ENANO_DBLAYER == 'PGSQL' )
-      $limit = ( $page_size > 0 ) ? "\n  LIMIT $page_size OFFSET $offset" : '';
-    else
-      $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, author_uid, u.username, time_id, edit_summary, minor_edit';
-    $sql = 'SELECT ' . $columns . ' FROM ' . table_prefix . "logs AS l\n"
-         . "  LEFT JOIN " . table_prefix . "users AS u\n"
-         . "    ON ( u.user_id = l.author_uid OR u.user_id IS NULL )\n"
-         . "  WHERE log_type = 'page' AND is_draft != 1$where_extra\n"
-         . "  GROUP BY log_id, action, page_id, namespace, page_text, author, author_uid, username, time_id, edit_summary, minor_edit\n"
-         . "  ORDER BY time_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 = 0, $page_size = 0)
-  {
-    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;
-  }
-  
-  /**
-   * 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()
-   * @param bool If true (default), shows action buttons.
-   * @param bool If true (default), shows page title; good for integrated displays
-   * @static
-   * @return string
-   */
-  
-  public static function render_row($row, $show_buttons = true, $show_pagetitle = true)
-  {
-    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 ( $show_buttons )
-    {
-      if ( $row['action'] == 'edit' && !empty($row['parent_revid']) )
-      {
-        $html .= '(';
-        $ispage = isPage($pagekey);
-        
-        if ( $ispage )
-          $html .= '<a href="' . makeUrlNS($row['namespace'], $row['page_id'], "do=diff&diff1={$row['parent_revid']}&diff2={$row['log_id']}", true) . '">';
-        
-        $html .= $lang->get('pagetools_rc_btn_diff');
-        
-        if ( $ispage )
-          $html .= '</a>';
-        
-        if ( $ispage )
-          $html .= ', <a href="' . makeUrlNS($row['namespace'], $row['page_id'], "oldid={$row['log_id']}", true) . '">';
-        
-        $html .= $lang->get('pagetools_rc_btn_view');
-        
-        if ( $ispage )
-          $html .= '</a>';
-        
-        if ( $row['parent_revid'] > 0 && isPage($pagekey) )
-        {
-          $html .= ', <a href="' . makeUrlNS($row['namespace'], $row['page_id'], false, true) . '#do:edit;rev:' . $row['parent_revid'] . '">' . $lang->get('pagetools_rc_btn_undo') . '</a>';
-        }
-        $html .= ') ';
-      }
-      else if ( $row['action'] != 'edit' && ( isPage($pagekey) || $row['action'] == 'delete' ) )
-      {
-        $html .= '(';
-        $html .= '<a href="' . makeUrlNS($row['namespace'], $row['page_id'], "do=rollback&id={$row['log_id']}", true). '">' . $lang->get('pagetools_rc_btn_undo') . '</a>';
-        $html .= ') ';
-      }
-      
-      // hist button
-      $html .= '(';
-      if ( isPage($pagekey) )
-      {
-        $html .= '<a href="' . makeUrlNS($row['namespace'], $row['page_id'], "do=history", true) . '">';
-      }
-      $html .= $lang->get('pagetools_rc_btn_hist');
-      if ( isPage($pagekey) )
-      {
-        $html .= '</a>';
-      }
-      $html .= ') . . ';
-    }
-    
-    if ( $show_pagetitle )
-    {
-      // new page?
-      if ( $row['action'] == 'edit' && empty($row['parent_revid']) )
-      {
-        $html .= '<b>N</b> ';
-      }
-      // minor edit?
-      if ( $row['action'] == 'edit' && $row['minor_edit'] )
-      {
-        $html .= '<b>m</b> ';
-      }
-      
-      // link to the page
-      $cls = ( isPage($pagekey) ) ? '' : ' class="wikilink-nonexistent"';
-      $html .= '<a href="' . makeUrlNS($row['namespace'], $row['page_id']) . '"' . $cls . '>' . htmlspecialchars(get_page_title_ns($row['page_id'], $row['namespace'])) . '</a>; ';
-    }
-    
-    // date
-    $today = time() - ( time() % 86400 );
-    $date = 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 .= "<span style=\"$css\">({$size_change})</span>";
-      $html .= ' . . ';
-    }
-    
-    // link to userpage
-    $real_username = $row['author_uid'] > 1 && !empty($row['username']) ? $row['username'] : $row['author'];
-    $cls = ( isPage($paths->nslist['User'] . $real_username) ) ? '' : ' class="wikilink-nonexistent"';
-    $rank_info = $session->get_user_rank($row['author_uid']);
-    $html .= '<a style="' . $rank_info['rank_style'] . '" href="' . makeUrlNS('User', sanitize_page_id($real_username), false, true) . '"' . $cls . '>' . htmlspecialchars($real_username) . '</a> ';
-    $html .= '(';
-    $html .= '<a href="' . makeUrlNS('Special', 'PrivateMessages/Compose/To/' . sanitize_page_id($real_username), false, true) . '">';
-    $html .= $lang->get('pagetools_rc_btn_pm');
-    $html .= '</a>, ';
-    $html .= '<a href="' . makeUrlNS('User', sanitize_page_id($real_username), false, true) . '#do:comments">';
-    $html .= $lang->get('pagetools_rc_btn_usertalk');
-    $html .= '</a>';
-    $html .= ') . . ';
-    
-    // Edit summary
-    
-    if ( $row['action'] == 'edit' )
-    {
-      $html .= '<i>(';
-      if ( empty($row['edit_summary']) )
-      {
-        $html .= '<span style="color: #808080;">' . $lang->get('history_summary_none_given') . '</span>';
-      }
-      else
-      {
-        $html .= RenderMan::parse_internal_links(htmlspecialchars($row['edit_summary']));
-      }
-      $html .= ')</i>';
-    }
-    else
-    {
-      switch($row['action'])
-      {
-        default:
-          $html .= $row['action'];
-          break;
-        case 'rename':
-          $html .= $lang->get('log_action_rename', array('old_name' => htmlspecialchars($row['edit_summary'])));
-          break;
-        case 'create':
-          $html .= $lang->get('log_action_create');
-          break;
-        case 'votereset':
-          $html .= $lang->get('log_action_votereset', array('num_votes' => $row['edit_summary'], 'plural' => ( intval($row['edit_summary']) == 1 ? '' : $lang->get('meta_plural'))));
-          break;
-        case 'prot':
-        case 'unprot':
-        case 'semiprot':
-        case 'delete':
-        case 'reupload':
-          $stringmap = array(
-            'prot' => 'log_action_protect_full',
-            'unprot' => 'log_action_protect_none',
-            'semiprot' => 'log_action_protect_semi',
-            'delete' => 'log_action_delete',
-            'reupload' => 'log_action_reupload'
-          );
-        
-        if ( $row['edit_summary'] === '__REVERSION__' )
-          $reason = '<span style="color: #808080;">' . $lang->get('log_msg_reversion') . '</span>';
-        else if ( $row['action'] == 'reupload' && $row['edit_summary'] === '__ROLLBACK__' )
-          $reason = '<span style="color: #808080;">' . $lang->get('log_msg_file_restored') . '</span>';
-        else
-          $reason = ( !empty($row['edit_summary']) ) ? htmlspecialchars($row['edit_summary']) : '<span style="color: #808080;">' . $lang->get('log_msg_no_reason_provided') . '</span>';
-        
-        $html .= $lang->get($stringmap[$row['action']], array('reason' => $reason));
-      }
-    }
-    
-    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;
-  }
+	/**
+ 	* Criteria for the search.
+ 	* Structure:
+ 	<code>
+ 	array(
+ 			array( 'user', 'Dan' ),
+ 			array( 'within', 86400 ),
+ 			array( 'page', 'Main_Page' )
+ 		)
+ 	</code>
+ 	* @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':
+			case 'action':
+				$this->criteria[] = array($criterion, $value);
+				break;
+			case 'minor':
+				$this->criteria[] = array($criterion, intval($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(),
+				'action' => array()
+			);
+		foreach ( $this->criteria as $criterion )
+		{
+			list($type, $value) = $criterion;
+			switch($type)
+			{
+				case 'user':
+					$where_bits['user'][] = "author = '" . $db->escape(str_replace('_', ' ', $value)) . "'";
+					break;
+				case 'action':
+					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);
+					$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;
+				case 'minor':
+					if ( $value == 1 )
+						$where_extra .= "\n    AND ( minor_edit = 1 OR action != 'edit' )";
+					else
+						$where_extra .= "\n    AND minor_edit != 1";
+					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']) . ") )";
+		}
+		if ( !empty($where_bits['action']) )
+		{
+			$where_extra .= "\n    AND ( (" . implode(") OR (", $where_bits['action']) . ") )";
+		}
+		if ( ENANO_DBLAYER == 'PGSQL' )
+			$limit = ( $page_size > 0 ) ? "\n  LIMIT $page_size OFFSET $offset" : '';
+		else
+			$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, author_uid, u.username, time_id, edit_summary, minor_edit';
+		$sql = 'SELECT ' . $columns . ' FROM ' . table_prefix . "logs AS l\n"
+ 				. "  LEFT JOIN " . table_prefix . "users AS u\n"
+ 				. "    ON ( u.user_id = l.author_uid OR u.user_id IS NULL )\n"
+ 				. "  WHERE log_type = 'page' AND is_draft != 1$where_extra\n"
+ 				. "  GROUP BY log_id, action, page_id, namespace, page_text, author, author_uid, username, time_id, edit_summary, minor_edit\n"
+ 				. "  ORDER BY time_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 = 0, $page_size = 0)
+	{
+		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;
+	}
+	
+	/**
+ 	* 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()
+ 	* @param bool If true (default), shows action buttons.
+ 	* @param bool If true (default), shows page title; good for integrated displays
+ 	* @static
+ 	* @return string
+ 	*/
+	
+	public static function render_row($row, $show_buttons = true, $show_pagetitle = true)
+	{
+		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 ( $show_buttons )
+		{
+			if ( $row['action'] == 'edit' && !empty($row['parent_revid']) )
+			{
+				$html .= '(';
+				$ispage = isPage($pagekey);
+				
+				if ( $ispage )
+					$html .= '<a href="' . makeUrlNS($row['namespace'], $row['page_id'], "do=diff&diff1={$row['parent_revid']}&diff2={$row['log_id']}", true) . '">';
+				
+				$html .= $lang->get('pagetools_rc_btn_diff');
+				
+				if ( $ispage )
+					$html .= '</a>';
+				
+				if ( $ispage )
+					$html .= ', <a href="' . makeUrlNS($row['namespace'], $row['page_id'], "oldid={$row['log_id']}", true) . '">';
+				
+				$html .= $lang->get('pagetools_rc_btn_view');
+				
+				if ( $ispage )
+					$html .= '</a>';
+				
+				if ( $row['parent_revid'] > 0 && isPage($pagekey) )
+				{
+					$html .= ', <a href="' . makeUrlNS($row['namespace'], $row['page_id'], false, true) . '#do:edit;rev:' . $row['parent_revid'] . '">' . $lang->get('pagetools_rc_btn_undo') . '</a>';
+				}
+				$html .= ') ';
+			}
+			else if ( $row['action'] != 'edit' && ( isPage($pagekey) || $row['action'] == 'delete' ) )
+			{
+				$html .= '(';
+				$html .= '<a href="' . makeUrlNS($row['namespace'], $row['page_id'], "do=rollback&id={$row['log_id']}", true). '">' . $lang->get('pagetools_rc_btn_undo') . '</a>';
+				$html .= ') ';
+			}
+			
+			// hist button
+			$html .= '(';
+			if ( isPage($pagekey) )
+			{
+				$html .= '<a href="' . makeUrlNS($row['namespace'], $row['page_id'], "do=history", true) . '">';
+			}
+			$html .= $lang->get('pagetools_rc_btn_hist');
+			if ( isPage($pagekey) )
+			{
+				$html .= '</a>';
+			}
+			$html .= ') . . ';
+		}
+		
+		if ( $show_pagetitle )
+		{
+			// new page?
+			if ( $row['action'] == 'edit' && empty($row['parent_revid']) )
+			{
+				$html .= '<b>N</b> ';
+			}
+			// minor edit?
+			if ( $row['action'] == 'edit' && $row['minor_edit'] )
+			{
+				$html .= '<b>m</b> ';
+			}
+			
+			// link to the page
+			$cls = ( isPage($pagekey) ) ? '' : ' class="wikilink-nonexistent"';
+			$html .= '<a href="' . makeUrlNS($row['namespace'], $row['page_id']) . '"' . $cls . '>' . htmlspecialchars(get_page_title_ns($row['page_id'], $row['namespace'])) . '</a>; ';
+		}
+		
+		// date
+		$today = time() - ( time() % 86400 );
+		$date = 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 .= "<span style=\"$css\">({$size_change})</span>";
+			$html .= ' . . ';
+		}
+		
+		// link to userpage
+		$real_username = $row['author_uid'] > 1 && !empty($row['username']) ? $row['username'] : $row['author'];
+		$cls = ( isPage($paths->nslist['User'] . $real_username) ) ? '' : ' class="wikilink-nonexistent"';
+		$rank_info = $session->get_user_rank($row['author_uid']);
+		$html .= '<a style="' . $rank_info['rank_style'] . '" href="' . makeUrlNS('User', sanitize_page_id($real_username), false, true) . '"' . $cls . '>' . htmlspecialchars($real_username) . '</a> ';
+		$html .= '(';
+		$html .= '<a href="' . makeUrlNS('Special', 'PrivateMessages/Compose/To/' . sanitize_page_id($real_username), false, true) . '">';
+		$html .= $lang->get('pagetools_rc_btn_pm');
+		$html .= '</a>, ';
+		$html .= '<a href="' . makeUrlNS('User', sanitize_page_id($real_username), false, true) . '#do:comments">';
+		$html .= $lang->get('pagetools_rc_btn_usertalk');
+		$html .= '</a>';
+		$html .= ') . . ';
+		
+		// Edit summary
+		
+		if ( $row['action'] == 'edit' )
+		{
+			$html .= '<i>(';
+			if ( empty($row['edit_summary']) )
+			{
+				$html .= '<span style="color: #808080;">' . $lang->get('history_summary_none_given') . '</span>';
+			}
+			else
+			{
+				$html .= RenderMan::parse_internal_links(htmlspecialchars($row['edit_summary']));
+			}
+			$html .= ')</i>';
+		}
+		else
+		{
+			switch($row['action'])
+			{
+				default:
+					$html .= $row['action'];
+					break;
+				case 'rename':
+					$html .= $lang->get('log_action_rename', array('old_name' => htmlspecialchars($row['edit_summary'])));
+					break;
+				case 'create':
+					$html .= $lang->get('log_action_create');
+					break;
+				case 'votereset':
+					$html .= $lang->get('log_action_votereset', array('num_votes' => $row['edit_summary'], 'plural' => ( intval($row['edit_summary']) == 1 ? '' : $lang->get('meta_plural'))));
+					break;
+				case 'prot':
+				case 'unprot':
+				case 'semiprot':
+				case 'delete':
+				case 'reupload':
+					$stringmap = array(
+						'prot' => 'log_action_protect_full',
+						'unprot' => 'log_action_protect_none',
+						'semiprot' => 'log_action_protect_semi',
+						'delete' => 'log_action_delete',
+						'reupload' => 'log_action_reupload'
+					);
+				
+				if ( $row['edit_summary'] === '__REVERSION__' )
+					$reason = '<span style="color: #808080;">' . $lang->get('log_msg_reversion') . '</span>';
+				else if ( $row['action'] == 'reupload' && $row['edit_summary'] === '__ROLLBACK__' )
+					$reason = '<span style="color: #808080;">' . $lang->get('log_msg_file_restored') . '</span>';
+				else
+					$reason = ( !empty($row['edit_summary']) ) ? htmlspecialchars($row['edit_summary']) : '<span style="color: #808080;">' . $lang->get('log_msg_no_reason_provided') . '</span>';
+				
+				$html .= $lang->get($stringmap[$row['action']], array('reason' => $reason));
+			}
+		}
+		
+		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;
+	}
 }
  
 ?>