Hopefully completed rewrite and localization of rollback backend and interface
authorDan
Thu, 06 Mar 2008 22:45:41 -0500
changeset 481 07bf15b066bc
parent 480 d5376271f96b
child 482 647d11c5e9c1
Hopefully completed rewrite and localization of rollback backend and interface
ajax.php
includes/clientside/static/ajax.js
includes/functions.php
includes/pageprocess.php
includes/pageutils.php
index.php
language/english/core.json
plugins/SpecialUpdownload.php
--- a/ajax.php	Thu Mar 06 20:53:26 2008 -0500
+++ b/ajax.php	Thu Mar 06 22:45:41 2008 -0500
@@ -398,6 +398,13 @@
       break;
     case "protect":
       // echo PageUtils::protect($paths->page_id, $paths->namespace, (int)$_POST['level'], $_POST['reason']);
+      
+      if ( @$_POST['reason'] === '__ROLLBACK__' )
+      {
+        // __ROLLBACK__ is a keyword for log entries.
+        die('"__ROLLBACK__" ain\'t gonna do it, buddy. Try to _not_ use reserved keywords next time, ok?');
+      }
+      
       $page = new PageProcessor($paths->page_id, $paths->namespace);
       header('Content-type: application/json');
       
--- a/includes/clientside/static/ajax.js	Thu Mar 06 20:53:26 2008 -0500
+++ b/includes/clientside/static/ajax.js	Thu Mar 06 22:45:41 2008 -0500
@@ -216,8 +216,20 @@
   ajaxPost(stdAjaxPrefix+'&_mode=protect', 'reason='+ajaxEscape(r)+'&level='+l, function() {
     if ( ajax.readyState == 4 && ajax.status == 200 ) {
       unsetAjaxLoading();
-      if(ajax.responseText != 'good')
-        alert(ajax.responseText);
+      if(ajax.responseText == 'good')
+        return true;
+      // check for JSON error response
+      var response = String(ajax.responseText + '');
+      if ( response.substr(0, 1) == '{' )
+      {
+        response = parseJSON(response);
+        if ( response.mode == 'error' )
+        {
+          alert(response.error);
+          return true;
+        }
+      }
+      alert(ajax.responseText);
     }
   }, true);
 }
@@ -403,13 +415,13 @@
   });
 }
 
-function ajaxHistView(oldid, tit) {
+function ajaxHistView(oldid, ttl) {
   // IE <6 pseudo-compatibility
   if ( KILL_SWITCH )
     return true;
-  if(!tit) tit=title;
+  if(!ttl) ttl=title;
   setAjaxLoading();
-  ajaxGet(append_sid(scriptPath+'/ajax.php?title='+tit+'&_mode=getpage&oldid='+oldid), function() {
+  ajaxGet(append_sid(scriptPath+'/ajax.php?title='+ttl+'&_mode=getpage&oldid='+oldid), function() {
     if ( ajax.readyState == 4 && ajax.status == 200 ) {
       unsetAjaxLoading();
       edit_open = false;
@@ -426,7 +438,30 @@
   ajaxGet(stdAjaxPrefix+'&_mode=rollback&id='+id, function() {
     if ( ajax.readyState == 4 && ajax.status == 200 ) {
       unsetAjaxLoading();
-      alert(ajax.responseText);
+      
+      var response = String(ajax.responseText + '');
+      if ( response.substr(0, 1) != '{' )
+      {
+        handle_invalid_json(response);
+        return false;
+      }
+      
+      response = parseJSON(response);
+      if ( response.success )
+      {
+        alert( $lang.get('page_msg_rb_success_' + response.action, { dateline: response.dateline }) )
+      }
+      else
+      {
+        if ( response.action )
+        {
+          alert( $lang.get('page_err_' + response.error, { action: response.action }) );
+        }
+        else
+        {
+          alert( $lang.get('page_err_' + response.error) );
+        }
+      }
     }
   });
 }
--- a/includes/functions.php	Thu Mar 06 20:53:26 2008 -0500
+++ b/includes/functions.php	Thu Mar 06 22:45:41 2008 -0500
@@ -814,7 +814,12 @@
   {
     $selfn = substr($paths->page_id, strlen($paths->nslist['File']), strlen($paths->page_id));
   }
-  $q = $db->sql_query('SELECT mimetype,time_id,size FROM '.table_prefix.'files WHERE page_id=\''.$selfn.'\' ORDER BY time_id DESC;');
+  $selfn = $db->escape($selfn);
+  $q = $db->sql_query('SELECT f.mimetype,f.time_id,f.size,l.log_id FROM ' . table_prefix . "files AS f\n"
+                    . "  LEFT JOIN " . table_prefix . "logs AS l\n"
+                    . "    ON ( l.time_id = f.time_id AND ( l.action = 'reupload' OR l.action IS NULL ) )\n"
+                    . "  WHERE f.page_id = '$selfn'\n"
+                    . "    ORDER BY f.time_id DESC;");
   if ( !$q )
   {
     $db->_die('The file type could not be fetched.');
@@ -845,7 +850,7 @@
   {
     $size .= ' (' . ( round($r['size'] / 1024, 1) ) . ' ' . $lang->get('etc_unit_kilobytes_short') . ')';
   }
-          
+  
   echo $lang->get('onpage_filebox_lbl_size', array('size' => $size));
   
   echo '<br />' . $lang->get('onpage_filebox_lbl_uploaded') . ' ' . $datestring . '</p>';
@@ -876,12 +881,26 @@
   echo '</p>';
   if ( $db->numrows() > 1 )
   {
+    // requery, sql_result_seek() doesn't work on postgres
+    $db->free_result();
+    $q = $db->sql_query('SELECT f.mimetype,f.time_id,f.size,l.log_id FROM ' . table_prefix . "files AS f\n"
+                    . "  LEFT JOIN " . table_prefix . "logs AS l\n"
+                    . "    ON ( l.time_id = f.time_id AND ( l.action = 'reupload' OR l.action IS NULL ) )\n"
+                    . "  WHERE f.page_id = '$selfn'\n"
+                    . "    ORDER BY f.time_id DESC;");
+    if ( !$q )
+      $db->_die();
+    
     echo '<h3>' . $lang->get('onpage_filebox_heading_history') . '</h3><p>';
+    $last_rollback_id = false;
     while ( $r = $db->fetchrow() )
     {
       echo '(<a href="'.makeUrlNS('Special', 'DownloadFile'.'/'.$selfn.'/'.$r['time_id'].htmlspecialchars(urlSeparator).'download').'">' . $lang->get('onpage_filebox_btn_this_version') . '</a>) ';
-      if ( $session->get_permissions('history_rollback') )
-        echo ' (<a href="#" onclick="ajaxRollback(\''.$r['time_id'].'\'); return false;">' . $lang->get('onpage_filebox_btn_revert') . '</a>) ';
+      if ( $session->get_permissions('history_rollback') && $last_rollback_id )
+        echo ' (<a href="#rollback:' . $last_rollback_id . '" onclick="ajaxRollback(\''.$last_rollback_id.'\'); return false;">' . $lang->get('onpage_filebox_btn_revert') . '</a>) ';
+      else if ( $session->get_permissions('history_rollback') && !$last_rollback_id )
+        echo ' (' . $lang->get('onpage_filebox_btn_current') . ') ';
+      $last_rollback_id = $r['log_id'];
       $mimetype = $r['mimetype'];
       $datestring = enano_date('F d, Y h:i a', (int)$r['time_id']);
       
@@ -4189,6 +4208,51 @@
   return ( get_char_count($string, "\n") ) + 1;
 }
 
+if ( !function_exists('sys_get_temp_dir') )
+{
+    // Based on http://www.phpit.net/
+    // article/creating-zip-tar-archives-dynamically-php/2/
+    /**
+     * Attempt to get the system's temp directory.
+     * @return string or bool false on failure
+     */
+    
+    function sys_get_temp_dir()
+    {
+        // Try to get from environment variable
+        if ( !empty($_ENV['TMP']) )
+        {
+            return realpath( $_ENV['TMP'] );
+        }
+        else if ( !empty($_ENV['TMPDIR']) )
+        {
+            return realpath( $_ENV['TMPDIR'] );
+        }
+        else if ( !empty($_ENV['TEMP']) )
+        {
+            return realpath( $_ENV['TEMP'] );
+        }
+
+        // Detect by creating a temporary file
+        else
+        {
+            // Try to use system's temporary directory
+            // as random name shouldn't exist
+            $temp_file = tempnam( md5(uniqid(rand(), TRUE)), '' );
+            if ( $temp_file )
+            {
+                $temp_dir = realpath( dirname($temp_file) );
+                unlink( $temp_file );
+                return $temp_dir;
+            }
+            else
+            {
+                return FALSE;
+            }
+        }
+    }
+}
+
 //die('<pre>Original:  01010101010100101010100101010101011010'."\nProcessed: ".uncompress_bitfield(compress_bitfield('01010101010100101010100101010101011010')).'</pre>');
 
 ?>
--- a/includes/pageprocess.php	Thu Mar 06 20:53:26 2008 -0500
+++ b/includes/pageprocess.php	Thu Mar 06 22:45:41 2008 -0500
@@ -221,7 +221,8 @@
       if ( !$this->page_exists )
       {
         $func_name = "page_{$this->namespace}_{$this->page_id}";
-        die_semicritical($lang->get('page_msg_admin_404_title'), $lang->get('page_msg_admin_404_body', array('func_name' => $func_name)));
+        
+        die_semicritical($lang->get('page_msg_admin_404_title'), $lang->get('page_msg_admin_404_body', array('func_name' => $func_name)), (!$this->send_headers));
       }
       $func_name = "page_{$this->namespace}_{$this->page_id}";
       if ( function_exists($func_name) )
@@ -519,7 +520,7 @@
     }
     
     // Guess the proper title
-    $name = ( !empty($title) ) ? $title : dirtify_page_id($this->page_id);
+    $name = ( !empty($title) ) ? $title : str_replace('_', ' ', dirtify_page_id($this->page_id));
     
     // Check for the restricted Project: prefix
     if ( substr($this->page_id, 0, 8) == 'Project:' )
@@ -621,19 +622,134 @@
     $log_entry = $db->fetchrow();
     $db->free_result();
     
+    $dateline = enano_date('d M Y h:i a', $log_entry['time_id']);
+    
     // Let's see, what do we have here...
     switch ( $log_entry['action'] )
     {
       case 'rename':
         // Page was renamed, let the rename method handle this
-        return $this->rename($log_entry['edit_summary']);
+        return array_merge($this->rename($log_entry['edit_summary']), array('dateline' => $dateline, 'action' => $log_entry['action']));
         break;
       case 'prot':
       case 'unprot':
       case 'semiprot':
-        return $this->protect_page(intval($log_entry['page_text']), '__REVERSION__');
+        return array_merge($this->protect_page(intval($log_entry['page_text']), '__REVERSION__'), array('dateline' => $dateline, 'action' => $log_entry['action']));
+        break;
+      case 'delete':
+        
+        // Raising a previously dead page has implications...
+        
+        // FIXME: l10n
+        // rollback_extra is required because usually only moderators can undo page deletion AND restore the content.
+        if ( !$this->perms->get_permissions('history_rollback_extra') )
+          return 'Administrative privileges are required for page undeletion.';
+        
+        // Rolling back the deletion of a page that was since created?
+        $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
+        if ( isset($paths->pages[$pathskey]) )
+          return array(
+              'success' => false,
+              // This is a clean Christian in-joke.
+              'error' => 'seeking_living_among_dead'
+            );
+        
+        // Generate a crappy page name
+        $name = $db->escape( str_replace('_', ' ', dirtify_page_id($this->page_id)) );
+        
+        // Stage 1 - re-insert page
+        $e = $db->sql_query('INSERT INTO ' . table_prefix.'pages(name,urlname,namespace) VALUES( \'' . $name . '\', \'' . $this->page_id . '\',\'' . $this->namespace . '\' )');
+        if ( !$e )
+          $db->die_json();
+        
+        // Select the latest published revision
+        $q = $db->sql_query('SELECT page_text FROM ' . table_prefix . "logs WHERE\n"
+                          . "      log_type  = 'page'\n"
+                          . "  AND action    = 'edit'\n"
+                          . "  AND page_id   = '$this->page_id'\n"
+                          . "  AND namespace = '$this->namespace'\n"
+                          . "  AND is_draft != 1\n"
+                          . "ORDER BY time_id DESC LIMIT 1;");
+        if ( !$q )
+          $db->die_json();
+        list($page_text) = $db->fetchrow_num();
+        $db->free_result($q);
+        
+        // Apply the latest revision as the current page text
+        $page_text = $db->escape($page_text);
+        $e = $db->sql_query('INSERT INTO ' . table_prefix."page_text(page_id, namespace, page_text) VALUES\n"
+                          . "  ( '$this->page_id', '$this->namespace', '$page_text' );");
+        if ( !$e )
+          $db->die_json();
+        
+        return array(
+            'success' => true,
+            'dateline' => $dateline,
+            'action' => $log_entry['action']
+          );
+        
+        break;
+      case 'reupload':
+        
+        // given a log id and some revision info, restore the old file.
+        // get the timestamp of the file before this one
+        $q = $db->sql_query('SELECT time_id, file_key, file_extension, filename, size, mimetype FROM ' . table_prefix . "files WHERE time_id < {$log_entry['time_id']} ORDER BY time_id DESC LIMIT 1;");
+        if ( !$q )
+          $db->_die();
+        
+        $row = $db->fetchrow();
+        $db->free_result();
+        
+        // If the file hasn't been renamed to the new format (omitting timestamp), do that now.
+        $fname = ENANO_ROOT . "/files/{$row['file_key']}_{$row['time_id']}{$row['file_extension']}";
+        if ( @file_exists($fname) )
+        {
+          // it's stored in the old format - rename
+          $fname_new = ENANO_ROOT . "/files/{$row['file_key']}{$row['file_extension']}";
+          if ( !@rename($fname, $fname_new) )
+          {
+            return array(
+              'success' => false,
+              'error' => 'rb_file_rename_failed',
+              'action' => $log_entry['action']
+              );
+          }
+        }
+        
+        // Insert a new file entry
+        $time = time();
+        $filename = $db->escape($row['filename']);
+        $mimetype = $db->escape($row['mimetype']);
+        $ext = $db->escape($row['file_extension']);
+        $key = $db->escape($row['file_key']);
+        
+        $q = $db->sql_query('INSERT INTO ' . table_prefix . "files ( time_id, page_id, filename, size, mimetype, file_extension, file_key ) VALUES\n"
+              . "  ( $time, '$this->page_id', '$filename', {$row['size']}, '$mimetype', '$ext', '$key' );");
+        if ( !$q )
+          $db->die_json();
+        
+        // add reupload log entry
+        $username = $db->escape($session->username);
+        $q = $db->sql_query('INSERT INTO ' . table_prefix . "logs ( log_type, action, time_id, page_id, namespace, author, edit_summary ) VALUES\n"
+                          . "  ( 'page', 'reupload', $time, '$this->page_id', '$this->namespace', '$username', '__ROLLBACK__' )");
+        if ( !$q )
+          $db->die_json();
+        
+        return array(
+            'success' => true,
+            'dateline' => $dateline,
+            'action' => $log_entry['action']
+          );
+        
         break;
       default:
+        
+        return array(
+            'success' => false,
+            'error' => 'rb_action_not_supported',
+            'action' => $log_entry['action']
+          );
+        
         break;
     }
   }
@@ -743,6 +859,14 @@
     $existing_protection = intval($metadata['protected']);
     $reason = $db->escape($reason);
     
+    if ( $existing_protection == $protection_level )
+    {
+      return array(
+        'success' => false,
+        'error' => 'protection_already_there'
+        );
+    }
+    
     $action = '[ insanity ]';
     switch($protection_level)
     {
@@ -755,13 +879,13 @@
          . "  ( 'page', '$action', '{$this->page_id}', '{$this->namespace}', '$username', '$reason', '$time', '$existing_protection', 'DATE_STRING COLUMN OBSOLETE, USE time_id' );";
     if ( !$db->sql_query($sql) )
     {
-      $db->_die();
+      $db->die_json();
     }
     
     // Perform the actual protection
     $q = $db->sql_query('UPDATE ' . table_prefix . "pages SET protected = $protection_level WHERE urlname = '{$this->page_id}' AND namespace = '{$this->namespace}';");
     if ( !$q )
-      $db->_die();
+      $db->die_json();
     
     return array(
       'success' => true
@@ -1666,7 +1790,7 @@
           echo '<p>' . $lang->get('page_msg_404_was_deleted', array(
                     'delete_time' => enano_date('d M Y h:i a', $r['time_id']),
                     'delete_reason' => htmlspecialchars($r['edit_summary']),
-                    'rollback_flags' => 'href="'.makeUrl($paths->page, 'do=rollback&amp;id='.$r['time_id']).'" onclick="ajaxRollback(\''.$r['time_id'].'\'); return false;"'
+                    'rollback_flags' => 'href="'.makeUrl($paths->page, 'do=rollback&amp;id='.$r['log_id']).'" onclick="ajaxRollback(\''.$r['log_id'].'\'); return false;"'
                   ))
                 . '</p>';
           if ( $session->user_level >= USER_LEVEL_ADMIN )
--- a/includes/pageutils.php	Thu Mar 06 20:53:26 2008 -0500
+++ b/includes/pageutils.php	Thu Mar 06 22:45:41 2008 -0500
@@ -498,7 +498,7 @@
         elseif($r['action']=='rename')   echo $lang->get('history_log_rename') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_oldtitle') . ' '.htmlspecialchars($r['edit_summary']);
         elseif($r['action']=='create')   echo $lang->get('history_log_create') . '</td><td class="' . $cls . '">';
         elseif($r['action']=='delete')   echo $lang->get('history_log_delete') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_reason') . ' ' . $r['edit_summary'];
-        elseif($r['action']=='reupload') echo $lang->get('history_log_uploadnew') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_reason') . ' '.htmlspecialchars($r['edit_summary']);
+        elseif($r['action']=='reupload') echo $lang->get('history_log_uploadnew') . '</td><td class="' . $cls . '">' . $lang->get('history_extra_reason') . ' ' . ( $r['edit_summary'] === '__ROLLBACK__' ? $lang->get('history_extra_upload_reversion') : htmlspecialchars($r['edit_summary']) );
         echo '</td>';
         
         // Actions!
@@ -526,157 +526,8 @@
     global $db, $session, $paths, $template, $plugins; // Common objects
     global $lang;
     
-    // FIXME: l10n
-    
-    if ( !$session->get_permissions('history_rollback') )
-    {
-      return('You are not authorized to perform rollbacks.');
-    }
-    if ( !preg_match('#^([0-9]+)$#', (string)$id) )
-    {
-      return('The value "id" on the query string must be an integer.');
-    }
-    $e = $db->sql_query('SELECT time_id,log_type,action,date_string,page_id,namespace,page_text,char_tag,author,edit_summary FROM ' . table_prefix.'logs WHERE log_id=' . $id . ';');
-    if ( !$e )
-    {
-      $db->_die('The rollback data could not be selected.');
-    }
-    $rb = $db->fetchrow();
-    $db->free_result();
-    
-    if ( $rb['log_type'] == 'page' && $rb['action'] != 'delete' )
-    {
-      $pagekey = $paths->nslist[$rb['namespace']] . $rb['page_id'];
-      if ( !isset($paths->pages[$pagekey]) )
-      {
-        return "Page doesn't exist";
-      }
-      $pagedata =& $paths->pages[$pagekey];
-      $protected = false;
-      // Special case: is the page protected? if so, check for even_when_protected permissions
-      if($pagedata['protected'] == 2)
-      {
-        // The page is semi-protected, determine permissions
-        if($session->user_logged_in && $session->reg_time + 60*60*24*4 < time()) 
-        {
-          $protected = false;
-        }
-        else
-        {
-          $protected = true;
-        }
-      }
-      else
-      {
-        $protected = ( $pagedata['protected'] == 1 );
-      }
-      
-      $perms = $session->fetch_page_acl($rb['page_id'], $rb['namespace']);
-      
-      if ( $protected && !$perms->get_permissions('even_when_protected') )
-      {
-        return "Because this page is protected, you need moderator rights to roll back changes.";
-      }
-    }
-    else
-    {
-      $perms =& $session;
-    }
-    
-    switch($rb['log_type'])
-    {
-      case "page":
-        switch($rb['action'])
-        {
-          // Support for rolling back edits removed in 1.1.2 - moved to page editor system
-          case "rename":
-            if ( !$perms->get_permissions('rename') )
-              return "You don't have permission to rename pages, so rolling back renames can't be allowed either.";
-            
-            $t = $rb['edit_summary'];
-            // result prediction
-            $subst = array(
-              'page_name_old' => get_page_title_ns($rb['page_id'], $rb['namespace']),
-              'page_name_new' => $t
-              );
-            
-            $e = PageUtils::rename($rb['page_id'], $rb['namespace'], $t);
-            
-            $e = ( $e == $lang->get('ajax_rename_success', $subst) );
-            
-            if ( !$e )
-            {
-              return "An error occurred during the rollback operation.\nMySQL said: ".$db->get_error()."\n\nSQL backtrace:\n".$db->sql_backtrace();
-            }
-            else
-            {
-              return 'The page "' . htmlspecialchars($paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name']) . '" has been rolled back to the name it had ("' . htmlspecialchars($rb['edit_summary']) . '") before ' . enano_date('d M Y h:i a', intval($rb['time_id'])) . '.';
-            }
-            break;
-          case "prot":
-            if ( !$perms->get_permissions('protect') )
-              return "You don't have permission to protect pages, so rolling back protection can't be allowed either.";
-            $e = $db->sql_query('UPDATE ' . table_prefix.'pages SET protected=0 WHERE urlname=\'' . $rb['page_id'] . '\' AND namespace=\'' . $rb['namespace'] . '\'');
-            if ( !$e )
-              return "An error occurred during the rollback operation.\nMySQL said: ".$db->get_error()."\n\nSQL backtrace:\n".$db->sql_backtrace();
-            else
-              return 'The page "' . $paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been unprotected according to the log created at ' . enano_date('d M Y h:i a', intval($rb['time_id'])) . '.';
-            break;
-          case "semiprot":
-            if ( !$perms->get_permissions('protect') )
-              return "You don't have permission to protect pages, so rolling back protection can't be allowed either.";
-            $e = $db->sql_query('UPDATE ' . table_prefix.'pages SET protected=0 WHERE urlname=\'' . $rb['page_id'] . '\' AND namespace=\'' . $rb['namespace'] . '\'');
-            if ( !$e )
-              return "An error occurred during the rollback operation.\nMySQL said: ".$db->get_error()."\n\nSQL backtrace:\n".$db->sql_backtrace();
-            else
-              return 'The page "' . $paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been unprotected according to the log created at ' . enano_date('d M Y h:i a', intval($rb['time_id'])) . '.';
-            break;
-          case "unprot":
-            if ( !$perms->get_permissions('protect') )
-              return "You don't have permission to protect pages, so rolling back protection can't be allowed either.";
-            $e = $db->sql_query('UPDATE ' . table_prefix.'pages SET protected=1 WHERE urlname=\'' . $rb['page_id'] . '\' AND namespace=\'' . $rb['namespace'] . '\'');
-            if ( !$e )
-              return "An error occurred during the rollback operation.\nMySQL said: ".$db->get_error()."\n\nSQL backtrace:\n".$db->sql_backtrace();
-            else
-              return 'The page "' . $paths->pages[$paths->nslist[$rb['namespace']].$rb['page_id']]['name'].'" has been protected according to the log created at ' . enano_date('d M Y h:i a', intval($rb['time_id'])) . '.';
-            break;
-          case "delete":
-            if ( !$perms->get_permissions('history_rollback_extra') )
-              return 'Administrative privileges are required for page undeletion.';
-            if ( isset($paths->pages[$paths->cpage['urlname']]) )
-              return 'You cannot raise a dead page that is alive.';
-            $name = str_replace('_', ' ', $rb['page_id']);
-            $e = $db->sql_query('INSERT INTO ' . table_prefix.'pages(name,urlname,namespace) VALUES( \'' . $name . '\', \'' . $rb['page_id'] . '\',\'' . $rb['namespace'] . '\' )');if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".$db->get_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
-            $e = $db->sql_query('SELECT page_text,char_tag FROM ' . table_prefix.'logs WHERE page_id=\'' . $rb['page_id'] . '\' AND namespace=\'' . $rb['namespace'] . '\' AND log_type=\'page\' AND action=\'edit\' ORDER BY time_id DESC;'); if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".$db->get_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
-            $r = $db->fetchrow();
-            $e = $db->sql_query('INSERT INTO ' . table_prefix.'page_text(page_id,namespace,page_text,char_tag) VALUES(\'' . $rb['page_id'] . '\',\'' . $rb['namespace'] . '\',\'' . $db->escape($r['page_text']) . '\',\'' . $r['char_tag'] . '\')'); if(!$e) return("An error occurred during the rollback operation.\nMySQL said: ".$db->get_error()."\n\nSQL backtrace:\n".$db->sql_backtrace());
-            return 'The page "' . $name . '" has been undeleted according to the log created at ' . enano_date('d M Y h:i a', intval($rb['time_id'])) . '.';
-            break;
-          case "reupload":
-            if ( !$session->get_permissions('history_rollback_extra') )
-            {
-              return 'Administrative privileges are required for file rollbacks.';
-            }
-            $newtime = time();
-            $newdate = enano_date('d M Y h:i a');
-            if(!$db->sql_query('UPDATE ' . table_prefix.'logs SET time_id=' . $newtime . ',date_string=\'' . $newdate . '\' WHERE time_id=' . $id))
-              return 'Error during query: '.$db->get_error();
-            if(!$db->sql_query('UPDATE ' . table_prefix.'files SET time_id=' . $newtime . ' WHERE time_id=' . $id))
-              return 'Error during query: '.$db->get_error();
-            return 'The file has been rolled back to the version uploaded on '.enano_date('d M Y h:i a', (int)$id).'.';
-            break;
-          default:
-            return('Rollback of the action "' . $rb['action'] . '" is not yet supported.');
-            break;
-        }
-        break;
-      case "security":
-      case "login":
-        return('A ' . $rb['log_type'] . '-related log entry cannot be rolled back.');
-        break;
-      default:
-        return('Unknown log entry type: "' . $rb['log_type'] . '"');
-    }
+    // placeholder
+    return 'PageUtils->rollback() is deprecated - use PageProcessor instead.';
   }
   
   /**
--- a/index.php	Thu Mar 06 20:53:26 2008 -0500
+++ b/index.php	Thu Mar 06 22:45:41 2008 -0500
@@ -267,9 +267,23 @@
     case 'rollback':
       $id = (isset($_GET['id'])) ? $_GET['id'] : false;
       if(!$id || !preg_match('#^([0-9]+)$#', $id)) die_friendly('Invalid action ID', '<p>The URL parameter "id" is not an integer. Exiting to prevent nasties like SQL injection, etc.</p>');
-      $rb = PageUtils::rollback( (int) $id );
+      
+      $id = intval($id);
+      
+      $page = new PageProcessor($paths->page_id, $paths->namespace);
+      $result = $page->rollback_log_entry($id);
+      
+      if ( $result['success'] )
+      {
+        $result = $lang->get("page_msg_rb_success_{$result['action']}", array('dateline' => $result['dateline']));
+      }
+      else
+      {
+        $result = $lang->get("page_err_{$result['error']}", array('action' => @$result['action']));
+      }
+      
       $template->header();
-      echo '<p>'.$rb.' <a href="'.makeUrl($paths->page).'">Return to the page</a>.</p>';
+      echo '<p>'.$result.' <a href="'.makeUrl($paths->page).'">' . $lang->get('etc_return_to_page') . '</a></p>';
       $template->footer();
       break;
     case 'catedit':
--- a/language/english/core.json	Thu Mar 06 20:53:26 2008 -0500
+++ b/language/english/core.json	Thu Mar 06 22:45:41 2008 -0500
@@ -128,6 +128,13 @@
                                </ul>
                                <p>If you would like to inquire further about this message, you may contact the %site_administration%.</p>',
       err_access_denied_siteadmin: 'site administrator',
+      err_seeking_living_among_dead: 'You are trying to un-delete a page that has since been restored.\n\n"But the men said to them, \'Why do you look for the living among the dead?\'" (Luke 24:5b/NIV)',
+      err_access_denied: 'Access to that action is denied.',
+      err_invalid_parameter: 'An invalid value (parameter) was sent to this action.',
+      err_rb_action_not_supported: 'Rolling back actions of type "%action%" isn\'t supported.',
+      err_rb_file_rename_failed: 'Could not rename the file to its new name (1.1.x format)',
+      err_protection_already_there: 'The protection level you selected is already in effect for this page.',
+      
       msg_this_is_a_redirector: '<b>This page is a <i>redirector</i>.</b><br />
                     This means that this page will not show its own content by default. Instead it will display the contents of the page it redirects to.<br /><br />
                     To create a redirect page, make the <i>first characters</i> in the page content <tt>#redirect [[Page_ID]]</tt>. For more information, see the
@@ -135,6 +142,14 @@
                     This page redirects to %redirect_target%.',
       msg_redirected_from: '(Redirected from %from%)',
       msg_redirected_from_to: '(Redirected from %from% to %to%)',
+      msg_rb_success_rename: 'The page has been restored the name it had on %dateline%.',
+      // next 3 are mostly identical
+      msg_rb_success_prot: 'The page\'s protection has been undone and replaced with the previous level it had as of %dateline%.',
+      msg_rb_success_unprot: 'The page\'s protection has been undone and replaced with the previous level it had as of %dateline%.',
+      msg_rb_success_semiprot: 'The page\'s semi-protection has been undone and replaced with the previous level it had as of %dateline%.',
+      
+      msg_rb_success_delete: 'The deletion of this page, which occurred on %dateline%, has been undone. This page has been restored, but comments and categorization data may have been lost.',
+      msg_rb_success_reupload: 'The file has been restored to the version uploaded on %dateline%.',
       
       msg_passrequired: 'Access to this page requires a password. Please enter the password for this page below:',
       msg_pass_wrong: 'The password you entered for this page was incorrect. Please enter the password for this page below:',
@@ -292,8 +307,8 @@
       filebox_btn_upload_new: 'Upload new version',
       filebox_heading_history: 'File history',
       filebox_btn_this_version: 'this ver',
-      filebox_btn_revert: 'revert',
-      
+      filebox_btn_revert: 'restore',
+      filebox_btn_current: 'current',
     },
     editor: {
       err_server: 'There was a problem starting the editor',
@@ -373,6 +388,7 @@
       extra_reason: 'Reason:',
       extra_oldtitle: 'Old title:',
       extra_protection_reversion: '(Reversion of previous protection)',
+      extra_upload_reversion: '(Restoration of previous upload)',
       tip_rdns: 'Click cell background for reverse DNS info',
       action_view: 'View',
       action_contrib: 'User contribs',
--- a/plugins/SpecialUpdownload.php	Thu Mar 06 20:53:26 2008 -0500
+++ b/plugins/SpecialUpdownload.php	Thu Mar 06 22:45:41 2008 -0500
@@ -120,8 +120,8 @@
     $chartag = sha1(microtime());
     $urln = str_replace(' ', '_', $filename);
     
-    $key = md5($filename . '_' . file_get_contents($file['tmp_name']));
-    $targetname = ENANO_ROOT . '/files/' . $key . '_' . $utime . $ext;
+    $key = md5($filename . '_' . ( function_exists('md5_file') ? md5_file($file['tmp_name']) : file_get_contents($file['tmp_name'])));
+    $targetname = ENANO_ROOT . '/files/' . $key . $ext;
     
     if(!@move_uploaded_file($file['tmp_name'], $targetname))
     {
@@ -233,7 +233,15 @@
     die_friendly($lang->get('etc_access_denied_short'), '<p>' . $lang->get('etc_access_denied') . '</p>');
   }
   
-  $fname = ENANO_ROOT . '/files/' . $row['file_key'] . '_' . $row['time_id'] . $row['file_extension'];
+  $fname = ENANO_ROOT . '/files/' . $row['file_key'] . $row['file_extension'];
+  if ( !file_exists($fname) )
+  {
+    $fname = ENANO_ROOT . '/files/' . $row['file_key'] . '_' . $row['time_id'] . $row['file_extension'];
+  }
+  if ( !file_exists($fname) )
+  {
+    die("Uploaded file $fname not found.");
+  }
   
   if ( isset($_GET['preview']) && substr($row['mimetype'], 0, 6) == 'image/' )
   {
@@ -262,7 +270,7 @@
       {
         // Get a temporary file
         // In this case, the file will not be cached and will be scaled each time it's requested
-        $temp_dir = ( is_dir('/tmp') ) ? '/tmp' : ( isset($_ENV['TEMP']) ) ? $_ENV['TEMP'] : 'SOME RANDOM NAME';
+        $temp_dir = sys_get_temp_dir();
         // if tempnam() cannot use the specified directory name, it will fall back on the system default
         $tempname = tempnam($temp_dir, $filename);
         if ( $tempname && is_writeable($tempname) )