includes/log.php
changeset 901 99ea0b0ac4be
child 905 1e40b33f2e3e
equal deleted inserted replaced
900:c5409416b61b 901:99ea0b0ac4be
       
     1 <?php
       
     2 
       
     3 /*
       
     4  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
       
     5  * Version 1.1.6 (Caoineag beta 1)
       
     6  * Copyright (C) 2006-2008 Dan Fuhry
       
     7  * log.php - Logs table parsing and displaying
       
     8  *
       
     9  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
       
    10  * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
       
    11  *
       
    12  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
       
    13  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
       
    14  */
       
    15 
       
    16 /**
       
    17  * Front-end for showing page revisions and actions in the logs table.
       
    18  * @package Enano
       
    19  * @subpackage Frontend
       
    20  * @author Dan Fuhry <dan@enanocms.org>
       
    21  * @license GNU General Public License
       
    22  */
       
    23 
       
    24 class LogDisplay
       
    25 {
       
    26   /**
       
    27    * Criteria for the search.
       
    28    * Structure:
       
    29    <code>
       
    30    array(
       
    31        array( 'user', 'Dan' ),
       
    32        array( 'within', 86400 ),
       
    33        array( 'page', 'Main_Page' )
       
    34      )
       
    35    </code>
       
    36    * @var array
       
    37    */
       
    38   
       
    39   var $criteria = array();
       
    40   
       
    41   /**
       
    42    * Adds a criterion for the log display.
       
    43    * @param string Criterion type - user, page, or within
       
    44    * @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"
       
    45    */
       
    46   
       
    47   public function add_criterion($criterion, $value)
       
    48   {
       
    49     switch ( $criterion )
       
    50     {
       
    51       case 'user':
       
    52       case 'page':
       
    53         $this->criteria[] = array($criterion, $value);
       
    54         break;
       
    55       case 'within':
       
    56         if ( is_int($value) )
       
    57         {
       
    58           $this->criteria[] = array($criterion, $value);
       
    59         }
       
    60         else if ( is_string($value) )
       
    61         {
       
    62           $lastchar = substr($value, -1);
       
    63           $amt = intval($value);
       
    64           switch($lastchar)
       
    65           {
       
    66             case 'd':
       
    67               $amt = $amt * 86400;
       
    68               break;
       
    69             case 'w':
       
    70               $amt = $amt * 604800;
       
    71               break;
       
    72             case 'm':
       
    73               $amt = $amt * 2592000;
       
    74               break;
       
    75             case 'y':
       
    76               $amt = $amt * 31536000;
       
    77               break;
       
    78           }
       
    79           $this->criteria[] = array($criterion, $amt);
       
    80         }
       
    81         else
       
    82         {
       
    83           throw new Exception('Invalid value type for within');
       
    84         }
       
    85         break;
       
    86       default:
       
    87         throw new Exception('Unknown criterion type');
       
    88         break;
       
    89     }
       
    90   }
       
    91   
       
    92   /**
       
    93    * Build the necessary SQL query.
       
    94    * @param int Optional: offset, defaults to 0
       
    95    * @param int Optional: page size, defaults to 0; 0 = don't limit
       
    96    */
       
    97   
       
    98   public function build_sql($offset = 0, $page_size = 0, $just_page_count = false)
       
    99   {
       
   100     global $db, $session, $paths, $template, $plugins; // Common objects
       
   101     
       
   102     $where_extra = '';
       
   103     $where_bits = array(
       
   104         'user' => array(),
       
   105         'page' => array()
       
   106       );
       
   107     foreach ( $this->criteria as $criterion )
       
   108     {
       
   109       list($type, $value) = $criterion;
       
   110       switch($type)
       
   111       {
       
   112         case 'user':
       
   113           $where_bits['user'][] = "author = '" . $db->escape($value) . "'";
       
   114           break;
       
   115         case 'page':
       
   116           list($page_id, $namespace) = RenderMan::strToPageId($value);
       
   117           $where_bits['page'][] = "page_id = '" . $db->escape($page_id) . "' AND namespace = '" . $db->escape($namespace) . "'";
       
   118           break;
       
   119         case 'within':
       
   120           $threshold = time() - $value;
       
   121           $where_extra .= "\n    AND time_id > $threshold";
       
   122           break;
       
   123       }
       
   124     }
       
   125     if ( !empty($where_bits['user']) )
       
   126     {
       
   127       $where_extra .= "\n    AND ( " . implode(" OR ", $where_bits['user']) . " )";
       
   128     }
       
   129     if ( !empty($where_bits['page']) )
       
   130     {
       
   131       $where_extra .= "\n    AND ( (" . implode(") OR (", $where_bits['page']) . ") )";
       
   132     }
       
   133     $limit = ( $page_size > 0 ) ? "\n  LIMIT $offset, $page_size" : '';
       
   134     $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';
       
   135     $sql = 'SELECT ' . $columns . ' FROM ' . table_prefix . "logs\n"
       
   136          . "  WHERE log_type = 'page' AND is_draft != 1$where_extra\n"
       
   137          . "  ORDER BY log_id DESC $limit;";
       
   138     
       
   139     return $sql;
       
   140   }
       
   141   
       
   142   /**
       
   143    * Get data!
       
   144    * @param int Offset, defaults to 0
       
   145    * @param int Page size, if 0 (default) returns entire table (danger Will Robinson!)
       
   146    * @return array
       
   147    */
       
   148   
       
   149   public function get_data($offset, $page_size)
       
   150   {
       
   151     global $db, $session, $paths, $session, $plugins; // Common objects
       
   152     $sql = $this->build_sql($offset, $page_size);
       
   153     if ( !$db->sql_query($sql) )
       
   154       $db->_die();
       
   155     
       
   156     $return = array();
       
   157     $deplist = array();
       
   158     $idlist = array();
       
   159     while ( $row = $db->fetchrow() )
       
   160     {
       
   161       $return[ $row['log_id'] ] = $row;
       
   162       if ( $row['action'] === 'edit' )
       
   163       {
       
   164         // This is a page revision; its parent needs to be found
       
   165         $pagekey = serialize(array($row['page_id'], $row['namespace']));
       
   166         $deplist[$pagekey] = "( page_id = '" . $db->escape($row['page_id']) . "' AND namespace = '" . $db->escape($row['namespace']) . "' AND log_id < {$row['log_id']} )";
       
   167         // if we already have a revision from this page in the dataset, we've found its parent
       
   168         if ( isset($idlist[$pagekey]) )
       
   169         {
       
   170           $child =& $return[ $idlist[$pagekey] ];
       
   171           $child['parent_size'] = $row['revision_size'];
       
   172           $child['parent_revid'] = $row['log_id'];
       
   173           $child['parent_time'] = $row['time_id'];
       
   174           unset($child);
       
   175         }
       
   176         $idlist[$pagekey] = $row['log_id'];
       
   177       }
       
   178     }
       
   179     
       
   180     // Second query fetches all parent revision data
       
   181     // (maybe we have no edits?? check deplist)
       
   182     
       
   183     if ( !empty($deplist) )
       
   184     {
       
   185       // FIXME: inefficient. damn GROUP BY for not obeying ORDER BY, it means we can't group and instead have to select
       
   186       // all previous revisions of page X and discard all but the first one we find.
       
   187       $where_extra = implode("\n    OR ", $deplist);
       
   188       $sql = 'SELECT log_id, page_id, namespace, CHAR_LENGTH(page_text) AS revision_size, time_id FROM ' . table_prefix . "logs\n"
       
   189            . "  WHERE log_type = 'page' AND action = 'edit'\n  AND ( $where_extra )\n"
       
   190            // . "  GROUP BY page_id, namespace\n"
       
   191            . "  ORDER BY log_id DESC;";
       
   192       if ( !$db->sql_query($sql) )
       
   193         $db->_die();
       
   194       
       
   195       while ( $row = $db->fetchrow() )
       
   196       {
       
   197         $pagekey = serialize(array($row['page_id'], $row['namespace']));
       
   198         if ( isset($idlist[$pagekey]) )
       
   199         {
       
   200           $child =& $return[ $idlist[$pagekey] ];
       
   201           $child['parent_size'] = $row['revision_size'];
       
   202           $child['parent_revid'] = $row['log_id'];
       
   203           $child['parent_time'] = $row['time_id'];
       
   204           unset($child, $idlist[$pagekey]);
       
   205         }
       
   206       }
       
   207     }
       
   208     
       
   209     // final iteration goes through all edits and if there's not info on the parent, sets to 0. It also calculates size change.
       
   210     foreach ( $return as &$row )
       
   211     {
       
   212       if ( $row['action'] === 'edit' && !isset($row['parent_revid']) )
       
   213       {
       
   214         $row['parent_revid'] = 0;
       
   215         $row['parent_size'] = 0;
       
   216       }
       
   217       if ( $row['action'] === 'edit' )
       
   218       {
       
   219         $row['size_delta'] = $row['revision_size'] - $row['parent_size'];
       
   220       }
       
   221     }
       
   222     
       
   223     return array_values($return);
       
   224   }
       
   225   
       
   226   /**
       
   227    * Get the number of rows that will be in the result set.
       
   228    * @return int
       
   229    */
       
   230   
       
   231   public function get_row_count()
       
   232   {
       
   233     global $db, $session, $paths, $session, $plugins; // Common objects
       
   234     
       
   235     if ( !$db->sql_query( $this->build_sql(0, 0, true) ) )
       
   236       $db->_die();
       
   237     
       
   238     list($count) = $db->fetchrow_num();
       
   239     return $count;
       
   240   }
       
   241   
       
   242   /**
       
   243    * Formats a result row into pretty HTML.
       
   244    * @param array dataset from LogDisplay::get_data()
       
   245    * @static
       
   246    * @return string
       
   247    */
       
   248   
       
   249   public static function render_row($row)
       
   250   {
       
   251     global $db, $session, $paths, $session, $plugins; // Common objects
       
   252     global $lang;
       
   253     
       
   254     $html = '';
       
   255     
       
   256     $pagekey = ( isset($paths->nslist[$row['namespace']]) ) ? $paths->nslist[$row['namespace']] . $row['page_id'] : $row['namespace'] . ':' . $row['page_id'];
       
   257     $pagekey = sanitize_page_id($pagekey);
       
   258     
       
   259     // diff button
       
   260     if ( $row['action'] == 'edit' && !empty($row['parent_revid']) )
       
   261     {
       
   262       $html .= '(';
       
   263       if ( isPage($pagekey) )
       
   264       {
       
   265         $html .= '<a href="' . makeUrlNS($row['namespace'], $row['page_id'], "do=diff&diff1={$row['parent_revid']}&diff2={$row['log_id']}", true) . '">';
       
   266       }
       
   267       $html .= $lang->get('pagetools_rc_btn_diff');
       
   268       if ( isPage($pagekey) )
       
   269       {
       
   270         $html .= '</a>';
       
   271       }
       
   272       $html .= ') ';
       
   273     }
       
   274     
       
   275     // hist button
       
   276     $html .= '(';
       
   277     if ( isPage($pagekey) )
       
   278     {
       
   279       $html .= '<a href="' . makeUrlNS($row['namespace'], $row['page_id'], "do=history", true) . '">';
       
   280     }
       
   281     $html .= $lang->get('pagetools_rc_btn_hist');
       
   282     if ( isPage($pagekey) )
       
   283     {
       
   284       $html .= '</a>';
       
   285     }
       
   286     $html .= ') . . ';
       
   287     
       
   288     // new page?
       
   289     if ( $row['action'] == 'edit' && empty($row['parent_revid']) )
       
   290     {
       
   291       $html .= '<b>N</b> ';
       
   292     }
       
   293     
       
   294     // link to the page
       
   295     $cls = ( isPage($pagekey) ) ? '' : ' class="wikilink-nonexistent"';
       
   296     $html .= '<a href="' . makeUrlNS($row['namespace'], $row['page_id']) . '"' . $cls . '>' . htmlspecialchars(get_page_title_ns($row['page_id'], $row['namespace'])) . '</a>; ';
       
   297     
       
   298     // date
       
   299     $today = time() - ( time() % 86400 );
       
   300     $date = ( $row['time_id'] > $today ) ? '' : MemberlistFormatter::format_date($row['time_id']) . ' ';
       
   301     $date .= date('h:i:s', $row['time_id']);
       
   302     $html .= "$date . . ";
       
   303     
       
   304     // size counter
       
   305     if ( $row['action'] == 'edit' )
       
   306     {
       
   307       $css = self::get_css($row['size_delta']);
       
   308       $size_change = number_format($row['size_delta']);
       
   309       if ( substr($size_change, 0, 1) != '-' )
       
   310         $size_change = "+$size_change";
       
   311       
       
   312       $html .= "<span style=\"$css\">({$size_change})</span>";
       
   313       $html .= ' . . ';
       
   314     }
       
   315     else
       
   316     {
       
   317       $html .= " FIXME {$row['action']} . . ";
       
   318     }
       
   319     
       
   320     // link to userpage
       
   321     $cls = ( isPage($paths->nslist['User'] . $row['author']) ) ? '' : ' class="wikilink-nonexistent"';
       
   322     $rank_info = $session->get_user_rank($row['author']);
       
   323     $html .= '<a style="' . $rank_info['rank_style'] . '" href="' . makeUrlNS('User', sanitize_page_id($row['author']), false, true) . '"' . $cls . '>' . htmlspecialchars($row['author']) . '</a> ';
       
   324     $html .= '(';
       
   325     $html .= '<a href="' . makeUrlNS('Special', 'PrivateMessages/Compose/To/' . sanitize_page_id($row['author']), false, true) . '">';
       
   326     $html .= $lang->get('pagetools_rc_btn_pm');
       
   327     $html .= '</a>, ';
       
   328     $html .= '<a href="' . makeUrlNS('User', sanitize_page_id($row['author']), false, true) . '#do:comments">';
       
   329     $html .= $lang->get('pagetools_rc_btn_usertalk');
       
   330     $html .= '</a>';
       
   331     $html .= ') . . ';
       
   332     
       
   333     // Edit summary
       
   334     $html .= '<i>(';
       
   335     if ( empty($row['edit_summary']) )
       
   336     {
       
   337       $html .= '<span style="color: #808080;">' . $lang->get('history_summary_none_given') . '</span>';
       
   338     }
       
   339     else
       
   340     {
       
   341       $html .= RenderMan::parse_internal_links(htmlspecialchars($row['edit_summary']));
       
   342     }
       
   343     $html .= ')</i>';
       
   344     
       
   345     return $html;
       
   346   }
       
   347   
       
   348   /**
       
   349    * Return CSS blurb for size delta
       
   350    * @return string
       
   351    * @static
       
   352    * @access private
       
   353    */
       
   354   
       
   355   private static function get_css($change_size)
       
   356   {
       
   357     // Hardly changed at all? Return a gray
       
   358     if ( $change_size <= 5 && $change_size >= -5 )
       
   359       return 'color: #808080;';
       
   360     // determine saturation based on size of change (1-500 bytes)
       
   361     $change_abs = abs($change_size);
       
   362     $index = 0x70 * ( $change_abs / 500 );
       
   363     if ( $index > 0x70 )
       
   364       $index = 0x70;
       
   365     $index = $index + 0x40;
       
   366     $index = dechex($index);
       
   367     if ( strlen($index) < 2 )
       
   368       $index = "0$index";
       
   369     $css = ( $change_size > 0 ) ? "color: #00{$index}00;" : "color: #{$index}0000;";
       
   370     if ( $change_abs > 500 )
       
   371       $css .= ' font-weight: bold;';
       
   372     return $css;
       
   373   }
       
   374 }
       
   375  
       
   376 ?>