Index: /trunk/admin.php
===================================================================
--- /trunk/admin.php	(revision 10510)
+++ /trunk/admin.php	(revision 10511)
@@ -169,4 +169,5 @@
     'U_CHANGE_THEME' => PHPWG_ROOT_PATH.'admin.php?change_theme=1',
     'U_PENDING_COMMENTS' => $link_start.'comments',
+    'U_UPDATES' => $link_start.'updates',
     )
   );
Index: /trunk/admin/include/functions.php
===================================================================
--- /trunk/admin/include/functions.php	(revision 10510)
+++ /trunk/admin/include/functions.php	(revision 10511)
@@ -2086,4 +2086,5 @@
     case 'thumbnail':
     case 'comments':
+    case 'updates':
       return 4;
 
Index: /trunk/admin/include/mysqldump.php
===================================================================
--- /trunk/admin/include/mysqldump.php	(revision 10511)
+++ /trunk/admin/include/mysqldump.php	(revision 10511)
@@ -0,0 +1,421 @@
+<?php
+/**
+* Dump MySQL database
+*
+* Here is an inline example:
+* <code>
+* $connection = @mysql_connect($dbhost,$dbuser,$dbpsw);
+* $dumper = new MySQLDump($dbname,'filename.sql',false,false);
+* $dumper->doDump();
+* </code>
+*
+* Special thanks to:
+* - Andrea Ingaglio <andrea@coders4fun.com> helping in development of all class code
+* - Dylan Pugh for precious advices halfing the size of the output file and for helping in debug
+*
+* @name    MySQLDump
+* @author  Daniele Viganò - CreativeFactory.it <daniele.vigano@creativefactory.it>
+* @version 2.20 - 02/11/2007
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License
+*/
+
+class MySQLDump {
+	/**
+	* @access private
+	*/
+	var $database = null;
+
+	/**
+	* @access private
+	*/
+	var $compress = false;
+
+	/**
+	* @access private
+	*/
+	var $hexValue = false;
+
+  /**
+	* The output filename
+	* @access private
+	*/
+	var $filename = null;
+
+	/**
+	* The pointer of the output file
+	* @access private
+	*/
+	var $file = null;
+
+	/**
+	* @access private
+	*/
+	var $isWritten = false;
+
+	/**
+	* Class constructor
+	* @param string $db The database name
+	* @param string $filepath The file where the dump will be written
+	* @param boolean $compress It defines if the output file is compress (gzip) or not
+	* @param boolean $hexValue It defines if the outup values are base-16 or not
+	*/
+	function MYSQLDump($db = null, $filepath = 'dump.sql', $compress = false, $hexValue = false){
+		$this->compress = $compress;
+		if ( !$this->setOutputFile($filepath) )
+			return false;
+		return $this->setDatabase($db);
+	}
+
+	/**
+	* Sets the database to work on
+	* @param string $db The database name
+	*/
+	function setDatabase($db){
+		$this->database = $db;
+		if ( !@mysql_select_db($this->database) )
+			return false;
+		return true;
+  }
+
+	/**
+	* Returns the database where the class is working on
+	* @return string
+	*/
+  function getDatabase(){
+		return $this->database;
+	}
+
+	/**
+	* Sets the output file type (It can be made only if the file hasn't been already written)
+	* @param boolean $compress If it's true, the output file will be compressed
+	*/
+	function setCompress($compress){
+		if ( $this->isWritten )
+			return false;
+		$this->compress = $compress;
+		$this->openFile($this->filename);
+		return true;
+  }
+
+	/**
+	* Returns if the output file is or not compressed
+	* @return boolean
+	*/
+  function getCompress(){
+		return $this->compress;
+	}
+
+	/**
+	* Sets the output file
+	* @param string $filepath The file where the dump will be written
+	*/
+	function setOutputFile($filepath){
+		if ( $this->isWritten )
+			return false;
+		$this->filename = $filepath;
+		$this->file = $this->openFile($this->filename);
+		return $this->file;
+  }
+
+  /**
+	* Returns the output filename
+	* @return string
+	*/
+  function getOutputFile(){
+		return $this->filename;
+	}
+
+	/**
+	* Writes to file the $table's structure
+	* @param string $table The table name
+	*/
+  function getTableStructure($table){
+		if ( !$this->setDatabase($this->database) )
+			return false;
+		// Structure Header
+		$structure = "-- \n";
+		$structure .= "-- Table structure for table `{$table}` \n";
+		$structure .= "-- \n\n";
+		// Dump Structure
+		$structure .= 'DROP TABLE IF EXISTS `'.$table.'`;'."\n";
+		$structure .= "CREATE TABLE `".$table."` (\n";
+		$records = @mysql_query('SHOW FIELDS FROM `'.$table.'`');
+		if ( @mysql_num_rows($records) == 0 )
+			return false;
+		while ( $record = mysql_fetch_assoc($records) ) {
+			$structure .= '`'.$record['Field'].'` '.$record['Type'];
+			if ( isset($record['Default']) )
+				$structure .= ' DEFAULT \''.$record['Default'].'\'';
+			if ( @strcmp($record['Null'],'YES') != 0 )
+				$structure .= ' NOT NULL';
+			elseif ( is_null($record['Default']) )
+				$structure .= ' DEFAULT NULL';
+			if ( !empty($record['Extra']) )
+				$structure .= ' '.$record['Extra'];
+			$structure .= ",\n";
+		}
+		$structure = @ereg_replace(",\n$", null, $structure);
+
+		// Save all Column Indexes
+		$structure .= $this->getSqlKeysTable($table);
+		$structure .= "\n)";
+
+		//Save table engine
+		$records = @mysql_query("SHOW TABLE STATUS LIKE '".$table."'");
+
+		if ( $record = @mysql_fetch_assoc($records) ) {
+			if ( !empty($record['Engine']) )
+				$structure .= ' ENGINE='.$record['Engine'];
+			if ( !empty($record['Auto_increment']) )
+				$structure .= ' AUTO_INCREMENT='.$record['Auto_increment'];
+		}
+
+		$structure .= ";\n\n-- --------------------------------------------------------\n\n";
+		$this->saveToFile($this->file,$structure);
+	}
+
+	/**
+	* Writes to file the $table's data
+	* @param string $table The table name
+	* @param boolean $hexValue It defines if the output is base 16 or not
+	*/
+	function getTableData($table,$hexValue = true) {
+		if ( !$this->setDatabase($this->database) )
+			return false;
+		// Header
+		$data = "-- \n";
+		$data .= "-- Dumping data for table `$table` \n";
+		$data .= "-- \n\n";
+
+		$records = mysql_query('SHOW FIELDS FROM `'.$table.'`');
+		$num_fields = @mysql_num_rows($records);
+		if ( $num_fields == 0 )
+			return false;
+		// Field names
+		$selectStatement = "SELECT ";
+		$insertStatement = "INSERT INTO `$table` (";
+		$hexField = array();
+		for ($x = 0; $x < $num_fields; $x++) {
+			$record = @mysql_fetch_assoc($records);
+			if ( ($hexValue) && ($this->isTextValue($record['Type'])) ) {
+				$selectStatement .= 'HEX(`'.$record['Field'].'`)';
+				$hexField [$x] = true;
+			}
+			else
+				$selectStatement .= '`'.$record['Field'].'`';
+			$insertStatement .= '`'.$record['Field'].'`';
+			$insertStatement .= ", ";
+			$selectStatement .= ", ";
+		}
+		$insertStatement = @substr($insertStatement,0,-2).') VALUES'."\n";
+		$selectStatement = @substr($selectStatement,0,-2).' FROM `'.$table.'`';
+
+		$records = @mysql_query($selectStatement);
+		$num_rows = @mysql_num_rows($records);
+		$num_fields = @mysql_num_fields($records);
+		// Dump data
+		if ( $num_rows > 0 ) {
+			$data .= $insertStatement;
+			for ($i = 0; $i < $num_rows; $i++) {
+				$record = @mysql_fetch_assoc($records);
+				$data .= ' (';
+				for ($j = 0; $j < $num_fields; $j++) {
+					$field_name = @mysql_field_name($records, $j);
+					if ( @$hexField[$j] && (@strlen($record[$field_name]) > 0) )
+						$data .= "0x".$record[$field_name];
+					elseif (is_null($record[$field_name]))
+						$data .= "NULL";
+					else
+						$data .= "'".@str_replace('\"','"',@mysql_real_escape_string($record[$field_name]))."'";
+					$data .= ',';
+				}
+				$data = @substr($data,0,-1).")";
+				$data .= ( $i < ($num_rows-1) ) ? ',' : ';';
+				$data .= "\n";
+				//if data in greather than 1MB save
+				if (strlen($data) > 1048576) {
+					$this->saveToFile($this->file,$data);
+					$data = '';
+				}
+			}
+			$data .= "\n-- --------------------------------------------------------\n\n";
+			$this->saveToFile($this->file,$data);
+		}
+	}
+
+  /**
+	* Writes to file all the selected database tables structure
+	* @return boolean
+	*/
+	function getDatabaseStructure(){
+		$records = @mysql_query('SHOW TABLES');
+		if ( @mysql_num_rows($records) == 0 )
+			return false;
+    $structure = '';
+		while ( $record = @mysql_fetch_row($records) ) {
+			$structure .= $this->getTableStructure($record[0]);
+		}
+		return true;
+  }
+
+	/**
+	* Writes to file all the selected database tables data
+	* @param boolean $hexValue It defines if the output is base-16 or not
+	*/
+	function getDatabaseData($hexValue = true){
+		$records = @mysql_query('SHOW TABLES');
+		if ( @mysql_num_rows($records) == 0 )
+			return false;
+		while ( $record = @mysql_fetch_row($records) ) {
+			$this->getTableData($record[0],$hexValue);
+		}
+  }
+
+	/**
+	* Writes to file the selected database dump
+	*/
+	function doDump() {
+		$this->saveToFile($this->file,"SET FOREIGN_KEY_CHECKS = 0;\n\n");
+		$this->getDatabaseStructure();
+		$this->getDatabaseData($this->hexValue);
+		$this->saveToFile($this->file,"SET FOREIGN_KEY_CHECKS = 1;\n\n");
+		$this->closeFile($this->file);
+		return true;
+	}
+	
+	/**
+	* @deprecated Look at the doDump() method
+	*/
+	function writeDump($filename) {
+		if ( !$this->setOutputFile($filename) )
+			return false;
+		$this->doDump();
+    $this->closeFile($this->file);
+    return true;
+	}
+
+	/**
+	* @access private
+	*/
+	function getSqlKeysTable ($table) {
+		$primary = "";
+		$unique = array();
+		$index = array();
+		$fulltext = array();
+		$results = mysql_query("SHOW KEYS FROM `{$table}`");
+		if ( @mysql_num_rows($results) == 0 )
+			return false;
+		while($row = mysql_fetch_object($results)) {
+			if (($row->Key_name == 'PRIMARY') AND ($row->Index_type == 'BTREE')) {
+				if ( $primary == "" )
+					$primary = "  PRIMARY KEY  (`{$row->Column_name}`";
+				else
+					$primary .= ", `{$row->Column_name}`";
+			}
+			if (($row->Key_name != 'PRIMARY') AND ($row->Non_unique == '0') AND ($row->Index_type == 'BTREE')) {
+				if ( (empty($unique)) OR (empty($unique[$row->Key_name])) )
+					$unique[$row->Key_name] = "  UNIQUE KEY `{$row->Key_name}` (`{$row->Column_name}`";
+				else
+					$unique[$row->Key_name] .= ", `{$row->Column_name}`";
+			}
+			if (($row->Key_name != 'PRIMARY') AND ($row->Non_unique == '1') AND ($row->Index_type == 'BTREE')) {
+				if ( (empty($index)) OR (empty($index[$row->Key_name])) )
+					$index[$row->Key_name] = "  KEY `{$row->Key_name}` (`{$row->Column_name}`";
+				else
+					$index[$row->Key_name] .= ", `{$row->Column_name}`";
+			}
+			if (($row->Key_name != 'PRIMARY') AND ($row->Non_unique == '1') AND ($row->Index_type == 'FULLTEXT')) {
+				if ( (empty($fulltext)) OR (empty($fulltext[$row->Key_name])) )
+					$fulltext[$row->Key_name] = "  FULLTEXT `{$row->Key_name}` (`{$row->Column_name}`";
+				else
+					$fulltext[$row->Key_name] .= ", `{$row->Column_name}`";
+			}
+		}
+		$sqlKeyStatement = '';
+		// generate primary, unique, key and fulltext
+		if ( $primary != "" ) {
+			$sqlKeyStatement .= ",\n";
+			$primary .= ")";
+			$sqlKeyStatement .= $primary;
+		}
+		if (!empty($unique)) {
+			foreach ($unique as $keyName => $keyDef) {
+				$sqlKeyStatement .= ",\n";
+				$keyDef .= ")";
+				$sqlKeyStatement .= $keyDef;
+
+			}
+		}
+		if (!empty($index)) {
+			foreach ($index as $keyName => $keyDef) {
+				$sqlKeyStatement .= ",\n";
+				$keyDef .= ")";
+				$sqlKeyStatement .= $keyDef;
+			}
+		}
+		if (!empty($fulltext)) {
+			foreach ($fulltext as $keyName => $keyDef) {
+				$sqlKeyStatement .= ",\n";
+				$keyDef .= ")";
+				$sqlKeyStatement .= $keyDef;
+			}
+		}
+		return $sqlKeyStatement;
+	}
+
+  /**
+	* @access private
+	*/
+	function isTextValue($field_type) {
+		switch ($field_type) {
+			case "tinytext":
+			case "text":
+			case "mediumtext":
+			case "longtext":
+			case "binary":
+			case "varbinary":
+			case "tinyblob":
+			case "blob":
+			case "mediumblob":
+			case "longblob":
+				return True;
+				break;
+			default:
+				return False;
+		}
+	}
+	
+	/**
+	* @access private
+	*/
+	function openFile($filename) {
+		$file = false;
+		if ( $this->compress )
+			$file = @gzopen($filename, "w9");
+		else
+			$file = @fopen($filename, "w");
+		return $file;
+	}
+
+  /**
+	* @access private
+	*/
+	function saveToFile($file, $data) {
+		if ( $this->compress )
+			@gzwrite($file, $data);
+		else
+			@fwrite($file, $data);
+		$this->isWritten = true;
+	}
+
+  /**
+	* @access private
+	*/
+	function closeFile($file) {
+		if ( $this->compress )
+			@gzclose($file);
+		else
+			@fclose($file);
+	}
+}
+?>
Index: /trunk/admin/include/updates.class.php
===================================================================
--- /trunk/admin/include/updates.class.php	(revision 10511)
+++ /trunk/admin/include/updates.class.php	(revision 10511)
@@ -0,0 +1,481 @@
+<?php
+
+if (!defined('PHPWG_ROOT_PATH')) die('Hacking attempt!');
+
+class updates
+{
+  var $types = array();
+  var $plugins;
+  var $themes;
+  var $languages;
+  var $missing = array();
+  var $default_plugins = array();
+  var $default_themes = array();
+  var $default_languages = array();
+  var $merged_extensions = array();
+  var $merged_extension_url = 'http://piwigo.org/download/merged_extensions.txt';
+
+  function __construct()
+  {
+    $this->types = array('plugins', 'themes', 'languages');
+    $this->default_themes = array('clear', 'dark', 'Sylvia');
+    $this->default_plugins = array('admin_multi_view', 'c13y_upgrade', 'language_switch', 'LocalFilesEditor');
+
+    foreach ($this->types as $type)
+    {
+      include_once(PHPWG_ROOT_PATH.'admin/include/'.$type.'.class.php');
+      $this->$type = new $type();
+    }
+  }
+
+  function check_piwigo_upgrade()
+  {
+    $_SESSION['need_update'] = null;
+
+    if (preg_match('/(\d+\.\d+)\.(\d+)/', PHPWG_VERSION, $matches)
+      and @fetchRemote(PHPWG_URL.'/download/all_versions.php', $result))
+    {
+      $all_versions = @explode("\n", $result);
+      $new_version = trim($all_versions[0]);
+      $_SESSION['need_update'] = version_compare(PHPWG_VERSION, $new_version, '<');
+    }
+  }
+
+  function get_server_extensions($version=PHPWG_VERSION)
+  {
+    global $user;
+
+    $get_data = array(
+      'format' => 'php',
+    );
+
+    // Retrieve PEM versions
+    $versions_to_check = array();
+    $url = PEM_URL . '/api/get_version_list.php';
+    if (fetchRemote($url, $result, $get_data) and $pem_versions = @unserialize($result))
+    {
+      if (!preg_match('/^\d+\.\d+\.\d+/', $version))
+      {
+        $version = $pem_versions[0]['name'];
+      }
+      $branch = substr($version, 0, strrpos($version, '.'));
+      foreach ($pem_versions as $pem_version)
+      {
+        if (strpos($pem_version['name'], $branch) === 0)
+        {
+          $versions_to_check[] = $pem_version['id'];
+        }
+      }
+    }
+    if (empty($versions_to_check))
+    {
+      return false;
+    }
+
+    // Extensions to check
+    $ext_to_check = array();
+    foreach ($this->types as $type)
+    {
+      $fs = 'fs_'.$type;
+      foreach ($this->$type->$fs as $ext)
+      {
+        if (isset($ext['extension']))
+        {
+          $ext_to_check[$ext['extension']] = $type;
+        }
+      }
+    }
+
+    // Retrieve PEM plugins infos
+    $url = PEM_URL . '/api/get_revision_list.php';
+    $get_data = array_merge($get_data, array(
+      'last_revision_only' => 'true',
+      'version' => implode(',', $versions_to_check),
+      'lang' => substr($user['language'], 0, 2),
+      'get_nb_downloads' => 'true',
+      )
+    );
+
+    $post_data = array();
+    if (!empty($ext_to_check))
+    {
+      $post_data['extension_include'] = implode(',', array_keys($ext_to_check));
+    }
+
+    if (fetchRemote($url, $result, $get_data, $post_data))
+    {
+      $pem_exts = @unserialize($result);
+      if (!is_array($pem_exts))
+      {
+        return false;
+      }
+      foreach ($pem_exts as $ext)
+      {
+        if (isset($ext_to_check[$ext['extension_id']]))
+        {
+          $server = 'server_'.$ext_to_check[$ext['extension_id']];
+          $this->$ext_to_check[$ext['extension_id']]->$server += array($ext['extension_id'] => $ext);
+          unset($ext_to_check[$ext['extension_id']]);
+        }
+      }
+      $this->check_missing_extensions($ext_to_check);
+      return true;
+    }
+    return false;
+  }
+
+  // Check all extensions upgrades
+  function check_extensions()
+  {
+    global $conf;
+
+    if (!$this->get_server_extensions())
+    {
+      autoupdate_error();
+    }
+
+    $_SESSION['extensions_need_update'] = array();
+
+    foreach ($this->types as $type)
+    {
+      $fs = 'fs_'.$type;
+      $server = 'server_'.$type;
+      $server_ext = $this->$type->$server;
+      $fs_ext = $this->$type->$fs;
+
+      $ignore_list = array();
+      $need_upgrade = array();
+
+      foreach($fs_ext as $ext_id => $fs_ext)
+      {
+        if (isset($fs_ext['extension']) and isset($server_ext[$fs_ext['extension']]))
+        {
+          $ext_info = $server_ext[$fs_ext['extension']];
+
+          if (!$this->version_compare($fs_ext['version'], $ext_info['revision_name'], $type))
+          {
+            if (in_array($ext_id, $conf['updates_ignored'][$type]))
+            {
+              array_push($ignore_list, $ext_id);
+            }
+            else
+            {
+              $_SESSION['extensions_need_update'][$type][$ext_id] = $ext_info['revision_name'];
+            }
+          }
+        }
+      }
+      $conf['updates_ignored'][$type] = $ignore_list;
+    }
+    conf_update_param('updates_ignored', pwg_db_real_escape_string(serialize($conf['updates_ignored'])));
+  }
+
+  // Check if extension have been upgraded since last check
+  function check_updated_extensions()
+  {
+    foreach ($this->types as $type)
+    {
+      if (!empty($_SESSION['extensions_need_update'][$type]))
+      {
+        $fs = 'fs_'.$type;
+        foreach($this->$type->$fs as $ext_id => $fs_ext)
+        {
+          if (isset($_SESSION['extensions_need_update'][$type][$ext_id])
+            and $this->version_compare($fs_ext['version'], $_SESSION['extensions_need_update'][$type][$ext_id], $type))
+          {
+            // Extension have been upgraded
+            $this->check_extensions();
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  function check_missing_extensions($missing)
+  {
+    foreach ($missing as $id => $type)
+    {
+      $fs = 'fs_'.$type;
+      $default = 'default_'.$type;
+      foreach ($this->$type->$fs as $ext_id => $ext)
+      {
+        if (isset($ext['extension']) and $id == $ext['extension']
+          and !in_array($ext_id, $this->$default)
+          and !in_array($ext['extension'], $this->merged_extensions))
+        {
+          $this->missing[$type][] = $ext;
+          break;
+        }
+      }
+    }
+  }
+
+  function get_merged_extensions($version)
+  {
+    if (fetchRemote($this->merged_extension_url, $result))
+    {
+      $rows = explode("\n", $result);
+      foreach ($rows as $row)
+      {
+        if (preg_match('/^(\d+\.\d+): *(.*)$/', $row, $match))
+        {
+          if (version_compare($version, $match[1], '>='))
+          {
+            $extensions = explode(',', trim($match[2]));
+            $this->merged_extensions = array_merge($this->merged_extensions, $extensions);
+          }
+        }
+      }
+    }
+  }
+
+  function version_compare($a, $b, $type)
+  {
+    $version_compare = rtrim($type, 's').'_version_compare';
+
+    return $this->$type->$version_compare($a, $b);
+  }
+
+  function deltree($path, $move_to_trash=false)
+  {
+    if (is_dir($path))
+    {
+      $fh = opendir($path);
+      while ($file = readdir($fh))
+      {
+        if ($file != '.' and $file != '..')
+        {
+          $pathfile = $path . '/' . $file;
+          if (is_dir($pathfile))
+          {
+            self::deltree($pathfile, $move_to_trash);
+          }
+          else
+          {
+            @unlink($pathfile);
+          }
+        }
+      }
+      closedir($fh);
+      if (@rmdir($path))
+      {
+        return true;
+      }
+      elseif ($move_to_trash)
+      {
+        $trash = PHPWG_ROOT_PATH.'_trash';
+        if (!is_dir($trash))
+        {
+          @mkgetdir($trash);
+        }
+        return @rename($path, $trash . '/'.md5(uniqid(rand(), true)));
+      }
+      else
+      {
+        return false;
+      }
+    }
+  }
+
+  function process_obsolete_list($file)
+  {
+    if (file_exists(PHPWG_ROOT_PATH.$file)
+      and $old_files = file(PHPWG_ROOT_PATH.$file, FILE_IGNORE_NEW_LINES)
+      and !empty($old_files))
+    {
+      array_push($old_files, $file);
+      foreach($old_files as $old_file)
+      {
+        $path = PHPWG_ROOT_PATH.$old_file;
+        if (is_file($path))
+        {
+          @unlink($path);
+        }
+        elseif (is_dir($path))
+        {
+          self::deltree($path, true);
+        }
+      }
+    }
+  }
+
+  function dump_database($include_history=false)
+  {
+    global $page, $conf, $cfgBase;
+
+    if (version_compare(PHPWG_VERSION, '2.1', '<'))
+    {
+      $conf['db_base'] = $cfgBase;
+    }
+
+    include(PHPWG_ROOT_PATH.'admin/include/mysqldump.php');
+
+    $path = $conf['local_data_dir'].'/update';
+
+    if (@mkgetdir($path)
+      and ($backupFile = tempnam($path, 'sql'))
+      and ($dumper = new MySQLDump($conf['db_base'],$backupFile,false,false)))
+    {
+      foreach (get_defined_constants() as $constant => $value)
+      {
+        if (preg_match('/_TABLE$/', $constant))
+        {
+          $dumper->getTableStructure($value);
+          if ($constant == 'HISTORY_TABLE' and !$include_history)
+          {
+            continue;
+          }
+          $dumper->getTableData($value);
+        }
+      }
+    }
+
+    if (@filesize($backupFile))
+    {
+      $http_headers = array(
+        'Content-Length: '.@filesize($backupFile),
+        'Content-Type: text/x-sql',
+        'Content-Disposition: attachment; filename="database.sql";',
+        'Content-Transfer-Encoding: binary',
+        );
+
+      foreach ($http_headers as $header) {
+        header($header);
+      }
+
+      @readfile($backupFile);
+      self::deltree($conf['local_data_dir'].'/update');
+      exit();
+    }
+    else
+    {
+      array_push($page['errors'], l10n('Unable to dump database.'));
+    }
+  }
+
+  function upgrade_to($upgrade_to, &$step)
+  {
+    global $page, $conf, $template;
+
+    if (!version_compare($_POST['upgrade_to'], PHPWG_VERSION, '>'))
+    {
+      redirect(get_root_url().'admin.php?page=plugin-'.basename(dirname(__FILE__)));
+    }
+
+    if ($step == 2)
+    {
+      preg_match('/(\d+\.\d+)\.(\d+)/', PHPWG_VERSION, $matches);
+      $code =  $matches[1].'.x_to_'.$_POST['upgrade_to'];
+      $dl_code = str_replace(array('.', '_'), '', $code);
+      $remove_path = $code;
+      $obsolete_list = 'obsolete.list';
+    }
+    else
+    {
+      $code = $_POST['upgrade_to'];
+      $dl_code = $code;
+      $remove_path = version_compare($code, '2.0.8', '>=') ? 'piwigo' : 'piwigo-'.$code;
+      $obsolete_list = PHPWG_ROOT_PATH.'install/obsolete.list';
+    }
+
+    if (empty($page['errors']))
+    {
+      $path = $conf['local_data_dir'].'/update';
+      $filename = $path.'/'.$code.'.zip';
+      @mkgetdir($path);
+
+      $chunk_num = 0;
+      $end = false;
+      $zip = @fopen($filename, 'w');
+      while (!$end)
+      {
+        $chunk_num++;
+        if (@fetchRemote(PHPWG_URL.'/download/dlcounter.php?code='.$dl_code.'&chunk_num='.$chunk_num, $result)
+          and $input = @unserialize($result))
+        {
+          if (0 == $input['remaining'])
+          {
+            $end = true;
+          }
+          @fwrite($zip, base64_decode($input['data']));
+        }
+        else
+        {
+          $end = true;
+        }
+      }
+      @fclose($zip);
+
+      if (@filesize($filename))
+      {
+        $zip = new PclZip($filename);
+        if ($result = $zip->extract(PCLZIP_OPT_PATH, PHPWG_ROOT_PATH,
+                                    PCLZIP_OPT_REMOVE_PATH, $remove_path,
+                                    PCLZIP_OPT_SET_CHMOD, 0755,
+                                    PCLZIP_OPT_REPLACE_NEWER))
+        {
+          //Check if all files were extracted
+          $error = '';
+          foreach($result as $extract)
+          {
+            if (!in_array($extract['status'], array('ok', 'filtered', 'already_a_directory')))
+            {
+              // Try to change chmod and extract
+              if (@chmod(PHPWG_ROOT_PATH.$extract['filename'], 0777)
+                and ($res = $zip->extract(PCLZIP_OPT_BY_NAME, $remove_path.'/'.$extract['filename'],
+                                          PCLZIP_OPT_PATH, PHPWG_ROOT_PATH,
+                                          PCLZIP_OPT_REMOVE_PATH, $remove_path,
+                                          PCLZIP_OPT_SET_CHMOD, 0755,
+                                          PCLZIP_OPT_REPLACE_NEWER))
+                and isset($res[0]['status'])
+                and $res[0]['status'] == 'ok')
+              {
+                continue;
+              }
+              else
+              {
+                $error .= $extract['filename'].': '.$extract['status']."\n";
+              }
+            }
+          }
+
+          if (empty($error))
+          {
+            self::obsolete_list($obsolete_list);
+            self::deltree($conf['local_data_dir'].'/update');
+            invalidate_user_cache(true);
+            $template->delete_compiled_templates();
+            unset($_SESSION['need_update']);
+            if ($step == 2)
+            {
+              array_push($page['infos'], sprintf(l10n('autoupdate_success'), $upgrade_to));
+              $step = -1;
+            }
+            else
+            {
+              redirect(PHPWG_ROOT_PATH.'upgrade.php?now=');
+            }
+          }
+          else
+          {
+            file_put_contents($conf['local_data_dir'].'/update/log_error.txt', $error);
+            $relative_path = trim(str_replace(dirname(dirname(dirname(dirname(__FILE__)))), '', $conf['local_data_dir']), '/\\');
+            array_push($page['errors'], sprintf(l10n('autoupdate_extract_fail'), PHPWG_ROOT_PATH.$relative_path.'/update/log_error.txt'));
+          }
+        }
+        else
+        {
+          self::deltree($conf['local_data_dir'].'/update');
+          array_push($page['errors'], l10n('autoupdate_fail'));
+        }
+      }
+      else
+      {
+        array_push($page['errors'], l10n('Piwigo cannot retrieve upgrade file from server'));
+      }
+    }
+  }
+}
+
+?>
Index: /trunk/admin/themes/default/template/admin.tpl
===================================================================
--- /trunk/admin/themes/default/template/admin.tpl	(revision 10510)
+++ /trunk/admin/themes/default/template/admin.tpl	(revision 10511)
@@ -76,4 +76,5 @@
         <li><a href="{$U_MAINTENANCE}">{'Maintenance'|@translate}</a></li>
         <li><a href="{$U_PENDING_COMMENTS}">{'Pending Comments'|@translate}</a></li>
+        <li><a href="{$U_UPDATES}">{'Updates'|@translate}</a></li>
       </ul>
     </dd>
Index: /trunk/admin/themes/default/template/updates_ext.tpl
===================================================================
--- /trunk/admin/themes/default/template/updates_ext.tpl	(revision 10511)
+++ /trunk/admin/themes/default/template/updates_ext.tpl	(revision 10511)
@@ -0,0 +1,272 @@
+{combine_script id='jquery.ajaxmanager' load='footer' require='jquery' path='themes/default/js/plugins/jquery.ajaxmanager.js'}
+{combine_script id='jquery.jgrowl' load='footer' require='jquery' path='themes/default/js/plugins/jquery.jgrowl_minimized.js'}
+{combine_css path="admin/themes/default/uploadify.jGrowl.css"}
+
+{footer_script require='jquery.effects.blind,jquery.ajaxmanager,jquery.jgrowl'}
+var pwg_token = '{$PWG_TOKEN}';
+var extList = new Array();
+var confirmMsg  = '{'Are you sure?'|@translate|@escape:'javascript'}';
+var errorHead   = '{'ERROR'|@translate|@escape:'javascript'}';
+var successHead = '{'Update Complete'|@translate|@escape:'javascript'}';
+var errorMsg    = '{'an error happened'|@translate|@escape:'javascript'}';
+var restoreMsg  = '{'Reset ignored updates'|@translate|@escape:'javascript'}';
+
+{literal}
+var todo = 0;
+var queuedManager = $.manageAjax.create('queued', { 
+	queue: true,  
+	maxRequests: 1,
+  beforeSend: function() { autoupdate_bar_toggle(1); },
+  complete: function() { autoupdate_bar_toggle(-1); }
+});
+
+function updateAll() {
+  if (confirm(confirmMsg)) {
+    jQuery('.updateExtension').each( function() {
+      if (jQuery(this).parents('div').css('display') == 'block')
+        jQuery(this).click();
+    });
+  }
+};
+
+function resetIgnored() {
+  jQuery.ajax({
+    type: 'GET',
+    url: 'ws.php',
+    dataType: 'json',
+    data: { method: 'pwg.extensions.ignoreUpdate', reset: true, pwg_token: pwg_token, format: 'json' },
+    success: function(data) {
+      if (data['stat'] == 'ok') {
+        jQuery(".pluginBox, fieldset").show();
+        jQuery("#update_all").show();
+        jQuery("#up_to_date").hide();
+        jQuery("#reset_ignore").hide();
+        jQuery("#ignored").hide();
+        checkFieldsets();
+      }
+    }
+  });
+};
+
+function checkFieldsets() {
+  var types = new Array('plugins', 'themes', 'languages');
+  var total = 0;
+  var ignored = 0;
+  for (i=0;i<3;i++) {
+    nbExtensions = 0;
+    jQuery("div[id^='"+types[i]+"_']").each(function(index) {
+      if (jQuery(this).css('display') == 'block')
+        nbExtensions++;
+      else
+        ignored++;
+    });
+    total = total + nbExtensions;
+    if (nbExtensions == 0)
+      jQuery("#"+types[i]).hide();
+  }
+
+  if (total == 0) {
+    jQuery("#update_all").hide();
+    jQuery("#up_to_date").show();
+  }
+  if (ignored > 0) {
+    jQuery("#reset_ignore").val(restoreMsg + ' (' + ignored + ')');
+  }
+};
+
+function updateExtension(type, id, revision) {
+  queuedManager.add({
+    type: 'GET',
+    dataType: 'json',
+    url: 'ws.php',
+    data: { method: 'pwg.extensions.update', type: type, id: id, revision: revision, pwg_token: pwg_token, format: 'json' },
+    success: function(data) {
+      if (data['stat'] == 'ok') {
+        jQuery.jGrowl( data['result'], { theme: 'success', header: successHead, life: 4000, sticky: false });
+        jQuery("#"+type+"_"+id).remove();
+        checkFieldsets();
+      } else {
+        jQuery.jGrowl( data['result'], { theme: 'error', header: errorHead, sticky: true });
+      }
+    },
+    error: function(data) {
+      jQuery.jGrowl( errorMsg, { theme: 'error', header: errorHead, sticky: true });
+    }
+  });
+};
+
+function ignoreExtension(type, id) {
+  jQuery.ajax({
+    type: 'GET',
+    url: 'ws.php',
+    dataType: 'json',
+    data: { method: 'pwg.extensions.ignoreUpdate', type: type, id: id, pwg_token: pwg_token, format: 'json' },
+    success: function(data) {
+      if (data['stat'] == 'ok') {
+        jQuery("#"+type+"_"+id).hide();
+        jQuery("#reset_ignore").show();
+        checkFieldsets();
+      }
+    }
+  });
+};
+
+function autoupdate_bar_toggle(i) {
+  todo = todo + i;
+  if ((i == 1 && todo == 1) || (i == -1 && todo == 0))
+    jQuery('.autoupdate_bar').toggle();
+}
+
+jQuery(document).ready(function() {
+	jQuery("td[id^='desc_'], p[id^='revdesc_']").click(function() {
+		id = this.id.split('_');
+		jQuery("#revdesc_"+id[1]).toggle('blind');
+    jQuery(".button_"+id[1]).toggle();
+		return false;
+	});
+});
+
+checkFieldsets();
+{/literal}
+{/footer_script}
+
+<div class="titrePage">
+  <h2>{'Updates'|@translate}</h2>
+</div>
+
+<div class="autoupdate_bar">
+<br>
+<input type="submit" id="update_all" value="{'Update All'|@translate}" onClick="updateAll(); return false;">
+<input type="submit" id="reset_ignore" value="{'Reset ignored updates'|@translate}" onClick="resetIgnored(); return false;" {if !$SHOW_RESET}style="display:none;"{/if}>
+</div>
+<div class="autoupdate_bar" style="display:none;">
+{'Update in progress... Please wait.'|@translate}<br><img src="admin/themes/default/images/ajax-loader-bar.gif">
+</div>
+
+<p id="up_to_date" style="display:none; text-align:left; margin-left:20px;">{'All extensions are up to date.'|@translate}</p>
+
+{if not empty($update_plugins)}
+<div>
+<fieldset id="plugins">
+<legend>{'Plugins'|@translate}</legend>
+{foreach from=$update_plugins item=plugin name=plugins_loop}
+<div class="pluginBox" id="plugins_{$plugin.EXT_ID}" {if $plugin.IGNORED}style="display:none;"{/if}>
+  <table>
+    <tr>
+      <td class="pluginBoxNameCell">
+        {$plugin.EXT_NAME}
+      </td>
+      <td>
+        <a href="#" onClick="updateExtension('plugins', '{$plugin.EXT_ID}', {$plugin.REVISION_ID});" class="updateExtension">{'Install'|@translate}</a>
+        | <a href="{$plugin.URL_DOWNLOAD}">{'Download'|@translate}</a>
+        | <a href="#" onClick="ignoreExtension('plugins', '{$plugin.EXT_ID}'); return false;">{'Ignore this update'|@translate}</a>
+      </td>
+    </tr>
+    <tr>
+      <td>
+        {'Version'|@translate} {$plugin.CURRENT_VERSION}
+      </td>
+      <td class="pluginDesc" id="desc_{$plugin.ID}">
+        <em>{'Downloads'|@translate}: {$plugin.DOWNLOADS}</em>
+        <img src="{$ROOT_URL}{$themeconf.admin_icon_dir}/plus.gif" alt="" class="button_{$plugin.ID}">
+        <img src="{$ROOT_URL}{$themeconf.admin_icon_dir}/minus.gif" alt="" class="button_{$plugin.ID}" style="display:none;">
+        {'New Version'|@translate} : {$plugin.NEW_VERSION}
+        | {'By %s'|@translate|@sprintf:$plugin.AUTHOR}
+      </td>
+    </tr>
+    <tr>
+      <td></td>
+      <td class="pluginDesc">
+        <p id="revdesc_{$plugin.ID}" style="display:none;">{$plugin.REV_DESC|htmlspecialchars|nl2br}</p>
+      </td>
+    </tr>
+  </table>
+</div>
+{/foreach}
+</fieldset>
+</div>
+{/if}
+
+{if not empty($update_themes)}
+<div>
+<fieldset id="themes">
+<legend>{'Themes'|@translate}</legend>
+{foreach from=$update_themes item=theme name=themes_loop}
+<div class="pluginBox" id="themes_{$theme.EXT_ID}" {if $theme.IGNORED}style="display:none;"{/if}>
+  <table>
+    <tr>
+      <td class="pluginBoxNameCell">
+        {$theme.EXT_NAME}
+      </td>
+      <td>
+        <a href="#" onClick="updateExtension('themes', '{$theme.EXT_ID}', {$theme.REVISION_ID});" class="updateExtension">{'Install'|@translate}</a>
+        | <a href="{$theme.URL_DOWNLOAD}">{'Download'|@translate}</a>
+        | <a href="#" onClick="ignoreExtension('themes', '{$theme.EXT_ID}'); return false;">{'autoupdate_ignore'|@translate}</a>
+      </td>
+    </tr>
+    <tr>
+      <td>
+        {'Version'|@translate} {$theme.CURRENT_VERSION}
+      </td>
+      <td class="pluginDesc" id="desc_{$theme.ID}">
+        <em>{'Downloads'|@translate}: {$theme.DOWNLOADS}</em>
+        <img src="{$ROOT_URL}{$themeconf.admin_icon_dir}/plus.gif" alt="" class="button_{$theme.ID}">
+        <img src="{$ROOT_URL}{$themeconf.admin_icon_dir}/minus.gif" alt="" class="button_{$theme.ID}" style="display:none;">
+        {'New Version'|@translate} : {$theme.NEW_VERSION}
+        | {'By %s'|@translate|@sprintf:$theme.AUTHOR}
+      </td>
+    </tr>
+    <tr>
+      <td></td>
+      <td class="pluginDesc">
+        <p id="revdesc_{$theme.ID}" style="display:none;">{$theme.REV_DESC|htmlspecialchars|nl2br}</p>
+      </td>
+    </tr>
+  </table>
+</div>
+{/foreach}
+</fieldset>
+</div>
+{/if}
+
+{if not empty($update_languages)}
+<div>
+<fieldset id="languages">
+<legend>{'Languages'|@translate}</legend>
+{foreach from=$update_languages item=language name=languages_loop}
+<div class="pluginBox" id="languages_{$language.EXT_ID}" {if $language.IGNORED}style="display:none;"{/if}>
+  <table>
+    <tr>
+      <td class="pluginBoxNameCell">
+        {$language.EXT_NAME}
+      </td>
+      <td>
+        <a href="#" onClick="updateExtension('languages', '{$language.EXT_ID}', {$language.REVISION_ID});" class="updateExtension">{'Install'|@translate}</a>
+        | <a href="{$language.URL_DOWNLOAD}">{'Download'|@translate}</a>
+        | <a href="#" onClick="ignoreExtension('languages', '{$language.EXT_ID}'); return false;">{'autoupdate_ignore'|@translate}</a>
+      </td>
+    </tr>
+    <tr>
+      <td>
+        {'Version'|@translate} {$language.CURRENT_VERSION}
+      </td>
+      <td class="pluginDesc" id="desc_{$language.ID}">
+        <em>{'Downloads'|@translate}: {$language.DOWNLOADS}</em>
+        <img src="{$ROOT_URL}{$themeconf.admin_icon_dir}/plus.gif" alt="" class="button_{$language.ID}">
+        <img src="{$ROOT_URL}{$themeconf.admin_icon_dir}/minus.gif" alt="" class="button_{$language.ID}" style="display:none;">
+        {'New Version'|@translate} : {$language.NEW_VERSION}
+        | {'By %s'|@translate|@sprintf:$language.AUTHOR}
+      </td>
+    </tr>
+    <tr>
+      <td></td>
+      <td class="pluginDesc">
+        <p id="revdesc_{$language.ID}" style="display:none;">{$language.REV_DESC|htmlspecialchars|nl2br}</p>
+      </td>
+    </tr>
+  </table>
+</div>
+{/foreach}
+</fieldset>
+</div>
+{/if}
Index: /trunk/admin/themes/default/template/updates_pwg.tpl
===================================================================
--- /trunk/admin/themes/default/template/updates_pwg.tpl	(revision 10511)
+++ /trunk/admin/themes/default/template/updates_pwg.tpl	(revision 10511)
@@ -0,0 +1,99 @@
+{footer_script}
+jQuery(document).ready(function() {ldelim}
+	jQuery('input[name="submit"]').click(function() {ldelim}
+    if(!confirm('{'autoupdate_alert'|@translate}'))
+      return false;
+    jQuery(this).hide();
+    jQuery('.autoupdate_bar').show();
+	});
+  jQuery('[name="understand"]').click(function() {ldelim}
+    jQuery('[name="submit"]').attr('disabled', !this.checked);
+  });
+});
+{/footer_script}
+
+{html_head}
+{literal}
+<style type="text/css">
+form { width: 750px; }
+fieldset { padding-bottom: 30px; }
+p, form p { text-align: left; margin-left:20px; }
+li { margin: 5px; }
+</style>
+{/literal}
+{/html_head}
+
+<div class="titrePage">
+<h2>{'Updates'|@translate}</h2>
+</div>
+
+{if $STEP == 0}
+  {if $CHECK_VERSION}
+    <p>{'You are running the latest version of Piwigo.'|@translate}</p>
+  {elseif $DEV_VERSION} 
+    <p>{'You are running on development sources, no check possible.'|@translate}</p>
+  {else}
+    <p>{'Check for update failed for unknown reasons.'|@translate}</p>
+  {/if}
+{/if}
+
+{if $STEP == 1}
+<h4>{'Two updates are available'|@translate}:</h4>
+<p>
+<ul>
+  <li><a href="admin.php?page=updates&amp;step=2&amp;to={$MINOR_VERSION}"><strong>{'Update to Piwigo %s'|@translate|@sprintf:$MINOR_VERSION}</strong></a>: {'This is a minor update, with only bug corrections.'|@translate}</li>
+  <li><a href="admin.php?page=updates&amp;step=3&amp;to={$MAJOR_VERSION}"><strong>{'Update to Piwigo %s'|@translate|@sprintf:$MAJOR_VERSION}</strong></a>: {'This is a major update, with <a href="%s">new exciting features</a>.'|@translate|@sprintf:$RELEASE_URL} {'Some themes and plugins may be not available yet.'|@translate}</li>
+</ul>
+</p>
+<p>{'You can update to Piwigo %s directly, without upgrading to Piwigo %s (recommended).'|@translate|@sprintf:$MAJOR_VERSION:$MINOR_VERSION}</p>
+{/if}
+
+{if $STEP == 2}
+<p>
+  {'A new version of Piwigo is available.'|@translate}<br>
+  {'This is a minor update, with only bug corrections.'|@translate}
+</p>
+<form action="" method="post">
+<p><input type="submit" name="submit" value="{'Update to Piwigo %s'|@translate|@sprintf:$UPGRADE_TO}"></p>
+<p class="autoupdate_bar" style="display:none;">&nbsp; {'Update in progress...'|@translate}<br><img src="admin/themes/default/images/ajax-loader-bar.gif"></p>
+<p><input type="hidden" name="upgrade_to" value="{$UPGRADE_TO}"></p>
+</form>
+{/if}
+
+{if $STEP == 3}
+<p>
+  {'A new version of Piwigo is available.'|@translate}<br>
+  {'This is a major update, with <a href="%s">new exciting features</a>.'|@translate|@sprintf:$RELEASE_URL} {'Some themes and plugins may be not available yet.'|@translate}
+</p>
+<form action="" method="post">
+
+{counter assign=i}
+<fieldset>
+  <legend>{'Dump Database'|@translate}</legend>
+  <p><input type="checkbox" name="includeHistory"> &nbsp; {'Include history data (Warning: server memory limit may be exceeded)'|@translate}</p>
+  <p><input type="submit" name="dumpDatabase" value="{'Dump Database'|@translate}"></p>
+</fieldset>
+
+{counter assign=i}
+<fieldset>
+  <legend>{'Update to Piwigo %s'|@translate|@sprintf:$UPGRADE_TO}</legend>
+  {if !empty($missing.plugins)}
+  <p><i>{'Following plugins may not be compatible with the new version of Piwigo:'|@translate}</i></p>
+  <p><ul>{foreach from=$missing.plugins item=plugin}<li><a href="{$plugin.uri}" class="externalLink">{$plugin.name}</a></li>{/foreach}</ul><br></p>
+  {/if}
+  {if !empty($missing.themes)}
+  <p><i>{'Following themes may not be compatible with the new version of Piwigo:'|@translate}</i></p>
+  <p><ul>{foreach from=$missing.themes item=theme}<li><a href="{$theme.uri}" class="externalLink">{$theme.name}</a></li>{/foreach}</ul><br></p>
+  {/if}
+  <p>
+  {if !empty($missing.plugins) or !empty($missing.themes)}
+  <p><label><input type="checkbox" name="understand"> &nbsp;{'I decide to update anyway'|@translate}</label></p>
+  {/if}
+  <p><input type="submit" name="submit" value="{'Update to Piwigo %s'|@translate|@sprintf:$UPGRADE_TO}" {if !empty($missing.plugins) or !empty($missing.themes)}disabled="disabled"{/if}>
+  </p>
+  <p class="autoupdate_bar" style="display:none;">&nbsp; {'Update in progress...'|@translate}<br><img src="admin/themes/default/images/ajax-loader-bar.gif"></p>
+</fieldset>
+
+<p><input type="hidden" name="upgrade_to" value="{$UPGRADE_TO}"></p>
+</form>
+{/if}
Index: /trunk/admin/updates.php
===================================================================
--- /trunk/admin/updates.php	(revision 10511)
+++ /trunk/admin/updates.php	(revision 10511)
@@ -0,0 +1,44 @@
+<?php
+// +-----------------------------------------------------------------------+
+// | Piwigo - a PHP based photo gallery                                    |
+// +-----------------------------------------------------------------------+
+// | Copyright(C) 2010      Pierrick LE GALL             http://piwigo.org |
+// +-----------------------------------------------------------------------+
+// | This program is free software; you can redistribute it and/or modify  |
+// | it under the terms of the GNU General Public License as published by  |
+// | the Free Software Foundation                                          |
+// |                                                                       |
+// | This program is distributed in the hope that it will be useful, but   |
+// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
+// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
+// | General Public License for more details.                              |
+// |                                                                       |
+// | You should have received a copy of the GNU General Public License     |
+// | along with this program; if not, write to the Free Software           |
+// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
+// | USA.                                                                  |
+// +-----------------------------------------------------------------------+
+
+if( !defined("PHPWG_ROOT_PATH") )
+{
+  die ("Hacking attempt!");
+}
+
+include_once(PHPWG_ROOT_PATH.'admin/include/tabsheet.class.php');
+
+$my_base_url = get_root_url().'admin.php?page=updates';
+
+if (isset($_GET['tab']))
+  $page['tab'] = $_GET['tab'];
+else
+  $page['tab'] = 'pwg';
+
+$tabsheet = new tabsheet();
+$tabsheet->add('pwg', l10n('Piwigo Update'), $my_base_url);
+$tabsheet->add('ext', l10n('Extensions Update'), $my_base_url.'&amp;tab=ext');
+$tabsheet->select($page['tab']);
+$tabsheet->assign();
+
+include(PHPWG_ROOT_PATH.'admin/updates_'.$page['tab'].'.php');
+
+?>
Index: /trunk/admin/updates_ext.php
===================================================================
--- /trunk/admin/updates_ext.php	(revision 10511)
+++ /trunk/admin/updates_ext.php	(revision 10511)
@@ -0,0 +1,86 @@
+<?php
+// +-----------------------------------------------------------------------+
+// | Piwigo - a PHP based photo gallery                                    |
+// +-----------------------------------------------------------------------+
+// | Copyright(C) 2010      Pierrick LE GALL             http://piwigo.org |
+// +-----------------------------------------------------------------------+
+// | This program is free software; you can redistribute it and/or modify  |
+// | it under the terms of the GNU General Public License as published by  |
+// | the Free Software Foundation                                          |
+// |                                                                       |
+// | This program is distributed in the hope that it will be useful, but   |
+// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
+// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
+// | General Public License for more details.                              |
+// |                                                                       |
+// | You should have received a copy of the GNU General Public License     |
+// | along with this program; if not, write to the Free Software           |
+// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
+// | USA.                                                                  |
+// +-----------------------------------------------------------------------+
+
+if( !defined("PHPWG_ROOT_PATH") )
+{
+  die ("Hacking attempt!");
+}
+
+$conf['updates_ignored'] = unserialize($conf['updates_ignored']);
+
+include_once(PHPWG_ROOT_PATH.'admin/include/updates.class.php');
+$autoupdate = new updates();
+
+if (!$autoupdate->get_server_extensions())
+{
+  array_push($page['errors'], l10n('Can\'t connect to server.'));
+  return;
+}
+
+foreach ($autoupdate->types as $type)
+{
+  $fs = 'fs_'.$type;
+  $server = 'server_'.$type;
+  $server_ext = $autoupdate->$type->$server;
+  $fs_ext = $autoupdate->$type->$fs;
+
+  if (empty($server_ext))
+  {
+    continue;
+  }
+
+  foreach($fs_ext as $ext_id => $fs_ext)
+  {
+    if (!isset($fs_ext['extension']) or !isset($server_ext[$fs_ext['extension']]))
+    {
+      continue;
+    }
+
+    $ext_info = $server_ext[$fs_ext['extension']];
+
+    if (!$autoupdate->version_compare($fs_ext['version'], $ext_info['revision_name'], $type))
+    {
+      $template->append('update_'.$type, array(
+        'ID' => $ext_info['extension_id'],
+        'REVISION_ID' => $ext_info['revision_id'],
+        'EXT_ID' => $ext_id,
+        'EXT_NAME' => $fs_ext['name'],
+        'EXT_URL' => PEM_URL.'/extension_view.php?eid='.$ext_info['extension_id'],
+        'EXT_DESC' => trim($ext_info['extension_description'], " \n\r"),
+        'REV_DESC' => trim($ext_info['revision_description'], " \n\r"),
+        'CURRENT_VERSION' => $fs_ext['version'],
+        'NEW_VERSION' => $ext_info['revision_name'],
+        'AUTHOR' => $ext_info['author_name'],
+        'DOWNLOADS' => $ext_info['extension_nb_downloads'],
+        'URL_DOWNLOAD' => $ext_info['download_url'] . '&amp;origin=piwigo_download',
+        'IGNORED' => in_array($ext_id, $conf['updates_ignored'][$type]),
+        )
+      );
+    }
+  }
+}
+
+$template->assign('SHOW_RESET', (!empty($conf['updates_ignored']['plugins']) or !empty($conf['updates_ignored']['themes']) or !empty($conf['updates_ignored']['languages'])));
+$template->assign('PWG_TOKEN', get_pwg_token());
+$template->set_filename('plugin_admin_content', 'updates_ext.tpl');
+$template->assign_var_from_handle('ADMIN_CONTENT', 'plugin_admin_content');
+
+?>
Index: /trunk/admin/updates_pwg.php
===================================================================
--- /trunk/admin/updates_pwg.php	(revision 10511)
+++ /trunk/admin/updates_pwg.php	(revision 10511)
@@ -0,0 +1,166 @@
+<?php
+// +-----------------------------------------------------------------------+
+// | Piwigo - a PHP based photo gallery                                    |
+// +-----------------------------------------------------------------------+
+// | Copyright(C) 2010      Pierrick LE GALL             http://piwigo.org |
+// +-----------------------------------------------------------------------+
+// | This program is free software; you can redistribute it and/or modify  |
+// | it under the terms of the GNU General Public License as published by  |
+// | the Free Software Foundation                                          |
+// |                                                                       |
+// | This program is distributed in the hope that it will be useful, but   |
+// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
+// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
+// | General Public License for more details.                              |
+// |                                                                       |
+// | You should have received a copy of the GNU General Public License     |
+// | along with this program; if not, write to the Free Software           |
+// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
+// | USA.                                                                  |
+// +-----------------------------------------------------------------------+
+
+if( !defined("PHPWG_ROOT_PATH") )
+{
+  die ("Hacking attempt!");
+}
+
+include_once(PHPWG_ROOT_PATH.'admin/include/updates.class.php');
+include_once(PHPWG_ROOT_PATH.'admin/include/pclzip.lib.php');
+
+/*
+STEP:
+0 = check is needed. If version is latest or check fail, we stay on step 0
+1 = new version on same branch AND new branch are available => user may choose upgrade.
+2 = upgrade on same branch
+3 = upgrade on different branch
+*/
+$step = isset($_GET['step']) ? $_GET['step'] : 0;
+$upgrade_to = isset($_GET['to']) ? $_GET['to'] : '';
+
+// +-----------------------------------------------------------------------+
+// |                                Step 0                                 |
+// +-----------------------------------------------------------------------+
+if ($step == 0)
+{
+  $template->assign(array(
+    'CHECK_VERSION' => false,
+    'DEV_VERSION' => false,
+    )
+  );
+
+  if (preg_match('/(\d+\.\d+)\.(\d+)/', PHPWG_VERSION, $matches))
+  {
+    $url = PHPWG_URL.'/download/all_versions.php';
+    $url .= '?rand='.md5(uniqid(rand(), true)); // Avoid server cache
+
+    if (@fetchRemote($url, $result)
+      and $all_versions = @explode("\n", $result)
+      and is_array($all_versions))
+    {
+      $template->assign('CHECK_VERSION', true);
+
+      $last_version = trim($all_versions[0]);
+      $upgrade_to = $last_version;
+
+      if (version_compare(PHPWG_VERSION, $last_version, '<'))
+      {
+        $new_branch = preg_replace('/(\d+\.\d+)\.\d+/', '$1', $last_version);
+        $actual_branch = $matches[1];
+
+        if ($new_branch == $actual_branch)
+        {
+          $step = 2;
+        }
+        else
+        {
+          $step = 3;
+
+          // Check if new version exists in same branch
+          foreach ($all_versions as $version)
+          {
+            $new_branch = preg_replace('/(\d+\.\d+)\.\d+/', '$1', $version);
+
+            if ($new_branch == $actual_branch)
+            {
+              if (version_compare(PHPWG_VERSION, $version, '<'))
+              {
+                $step = 1;
+              }
+              break;
+            }
+          }
+        }
+      }
+    }
+  }
+  else
+  {
+    $template->assign('DEV_VERSION', true);
+  }
+}
+
+// +-----------------------------------------------------------------------+
+// |                                Step 1                                 |
+// +-----------------------------------------------------------------------+
+if ($step == 1)
+{
+  $template->assign(array(
+    'MINOR_VERSION' => $version,
+    'MAJOR_VERSION' => $last_version,
+    )
+  );
+}
+
+// +-----------------------------------------------------------------------+
+// |                                Step 2                                 |
+// +-----------------------------------------------------------------------+
+if ($step == 2 and is_webmaster())
+{
+  if (isset($_POST['submit']) and isset($_POST['upgrade_to']))
+  {
+    updates::upgrade_to($_POST['upgrade_to'], $step);
+  }
+}
+
+// +-----------------------------------------------------------------------+
+// |                                Step 3                                 |
+// +-----------------------------------------------------------------------+
+if ($step == 3 and is_webmaster())
+{
+  if (isset($_POST['dumpDatabase']))
+  {
+    updates::dump_database(isset($_POST['includeHistory']));
+  }
+
+  if (isset($_POST['submit']) and isset($_POST['upgrade_to']))
+  {
+    updates::upgrade_to($_POST['upgrade_to'], $step);
+  }
+
+  $updates = new updates();
+  $updates->get_merged_extensions($upgrade_to);
+  $updates->get_server_extensions($upgrade_to);
+  $template->assign('missing', $updates->missing);
+}
+
+// +-----------------------------------------------------------------------+
+// |                        Process template                               |
+// +-----------------------------------------------------------------------+
+
+if (!is_webmaster())
+{
+  array_push($page['errors'], l10n('Webmaster status is required.'));
+}
+
+$template->assign(array(
+  'STEP'          => $step,
+  'PHPWG_VERSION' => PHPWG_VERSION,
+  'UPGRADE_TO'    => $upgrade_to,
+  'RELEASE_URL'   => PHPWG_URL.'/releases/'.$upgrade_to,
+  )
+);
+
+$template->set_filename('plugin_admin_content', 'updates_pwg.tpl');
+$template->assign_var_from_handle('ADMIN_CONTENT', 'plugin_admin_content');
+
+?>
Index: /trunk/include/ws_functions.inc.php
===================================================================
--- /trunk/include/ws_functions.inc.php	(revision 10510)
+++ /trunk/include/ws_functions.inc.php	(revision 10511)
@@ -2736,3 +2736,139 @@
   return false;
 }
+
+function ws_extensions_update($params, &$service)
+{
+  if (!is_webmaster())
+  {
+    return new PwgError(401, l10n('Webmaster status is required.'));
+  }
+
+  if (empty($params['pwg_token']) or get_pwg_token() != $params['pwg_token'])
+  {
+    return new PwgError(403, 'Invalid security token');
+  }
+
+  if (empty($params['type']) or !in_array($params['type'], array('plugins', 'themes', 'languages')))
+  {
+    return new PwgError(403, "invalid extension type");
+  }
+
+  if (empty($params['id']) or empty($params['revision']))
+  {
+    return new PwgError(null, 'Wrong parameters');
+  }
+
+  include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
+  include_once(PHPWG_ROOT_PATH.'admin/include/'.$params['type'].'.class.php');
+
+  $type = $params['type'];
+  $extension_id = $params['id'];
+  $revision = $params['revision'];
+
+  $extension = new $type();
+
+  if ($type == 'plugins')
+  {
+    if (isset($extension->db_plugins_by_id[$extension_id]) and $extension->db_plugins_by_id[$extension_id]['state'] == 'active')
+    {
+      $extension->perform_action('deactivate', $extension_id);
+
+      redirect(PHPWG_ROOT_PATH
+        . 'ws.php'
+        . '?method=pwg.extensions.update'
+        . '&type=plugins'
+        . '&id=' . $extension_id
+        . '&revision=' . $revision
+        . '&reactivate=true'
+        . '&pwg_token=' . get_pwg_token()
+        . '&format=json'
+      );
+    }
+    
+    $upgrade_status = $extension->extract_plugin_files('upgrade', $revision, $extension_id);
+    $extension_name = $extension->fs_plugins[$extension_id]['name'];
+
+    if (isset($params['reactivate']))
+    {
+      $extension->perform_action('activate', $extension_id);
+    }
+  }
+  elseif ($type == 'themes')
+  {
+    $upgrade_status = $extension->extract_theme_files('upgrade', $revision, $extension_id);
+    $extension_name = $extension->fs_themes[$extension_id]['name'];
+  }
+  elseif ($type == 'languages')
+  {
+    $upgrade_status = $extension->extract_language_files('upgrade', $revision, $extension_id);
+    $extension_name = $extension->fs_languages[$extension_id]['name'];
+  }
+
+  global $template;
+  $template->delete_compiled_templates();
+
+  switch ($upgrade_status)
+  {
+    case 'ok':
+      return sprintf(l10n('%s has been successfully updated.'), $extension_name);
+
+    case 'temp_path_error':
+      return new PwgError(null, l10n('Can\'t create temporary file.'));
+
+    case 'dl_archive_error':
+      return new PwgError(null, l10n('Can\'t download archive.'));
+
+    case 'archive_error':
+      return new PwgError(null, l10n('Can\'t read or extract archive.'));
+
+    default:
+      return new PwgError(null, sprintf(l10n('An error occured during extraction (%s).'), $upgrade_status));
+  }
+}
+
+function ws_extensions_ignoreupdate($params, &$service)
+{
+  global $conf;
+
+  define('IN_ADMIN', true);
+  include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
+
+  if (!is_webmaster())
+  {
+    return new PwgError(401, 'Access denied');
+  }
+
+  if (empty($params['pwg_token']) or get_pwg_token() != $params['pwg_token'])
+  {
+    return new PwgError(403, 'Invalid security token');
+  }
+
+  $conf['updates_ignored'] = unserialize($conf['updates_ignored']);
+
+  if ($params['reset'])
+  {
+    $conf['updates_ignored'] = array(
+      'plugins'=>array(),
+      'themes'=>array(),
+      'languages'=>array()
+    );
+    conf_update_param('updates_ignored', pwg_db_real_escape_string(serialize($conf['updates_ignored'])));
+    unset($_SESSION['extensions_need_update']);
+    return true;
+  }
+
+  if (empty($params['id']) or empty($params['type']) or !in_array($params['type'], array('plugins', 'themes', 'languages')))
+  {
+    return new PwgError(403, 'Invalid parameters');
+  }
+
+  // Add or remove extension from ignore list
+  if (!in_array($params['id'], $conf['updates_ignored'][$params['type']]))
+  {
+    array_push($conf['updates_ignored'][$params['type']], $params['id']);
+  }
+  conf_update_param('updates_ignored', pwg_db_real_escape_string(serialize($conf['updates_ignored'])));
+  unset($_SESSION['extensions_need_update']);
+  return true;
+}
 ?>
Index: /trunk/install/config.sql
===================================================================
--- /trunk/install/config.sql	(revision 10510)
+++ /trunk/install/config.sql	(revision 10511)
@@ -50,2 +50,3 @@
   );
 INSERT INTO piwigo_config (param,value,comment) VALUES ('week_starts_on','monday','Monday may not be the first day of the week');
+INSERT INTO piwigo_config (param,value,comment) VALUES ('updates_ignored','a:3:{s:7:"plugins";a:0:{}s:6:"themes";a:0:{}s:9:"languages";a:0:{}}','Extensions ignored for update');
Index: /trunk/install/db/103-database.php
===================================================================
--- /trunk/install/db/103-database.php	(revision 10511)
+++ /trunk/install/db/103-database.php	(revision 10511)
@@ -0,0 +1,42 @@
+<?php
+// +-----------------------------------------------------------------------+
+// | Piwigo - a PHP based photo gallery                                    |
+// +-----------------------------------------------------------------------+
+// | Copyright(C) 2008-2011 Piwigo Team                  http://piwigo.org |
+// | Copyright(C) 2003-2008 PhpWebGallery Team    http://phpwebgallery.net |
+// | Copyright(C) 2002-2003 Pierrick LE GALL   http://le-gall.net/pierrick |
+// +-----------------------------------------------------------------------+
+// | This program is free software; you can redistribute it and/or modify  |
+// | it under the terms of the GNU General Public License as published by  |
+// | the Free Software Foundation                                          |
+// |                                                                       |
+// | This program is distributed in the hope that it will be useful, but   |
+// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
+// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
+// | General Public License for more details.                              |
+// |                                                                       |
+// | You should have received a copy of the GNU General Public License     |
+// | along with this program; if not, write to the Free Software           |
+// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
+// | USA.                                                                  |
+// +-----------------------------------------------------------------------+
+
+if (!defined('PHPWG_ROOT_PATH'))
+{
+  die('Hacking attempt!');
+}
+
+$upgrade_description = 'Extensions ignored for update';
+
+$query = '
+INSERT INTO '.PREFIX_TABLE.'config (param,value,comment)
+  VALUES (\'updates_ignored\',\'a:3:{s:7:"plugins";a:0:{}s:6:"themes";a:0:{}s:9:"languages";a:0:{}}\', \''.$upgrade_description.'\')
+;';
+pwg_query($query);
+
+echo
+"\n"
+. $upgrade_description
+."\n"
+;
+?>
Index: /trunk/language/en_UK/admin.lang.php
===================================================================
--- /trunk/language/en_UK/admin.lang.php	(revision 10510)
+++ /trunk/language/en_UK/admin.lang.php	(revision 10511)
@@ -797,3 +797,29 @@
 $lang['%s photos can not be regenerated'] = '%s photos can not be regenerated';
 $lang['Only photos with HD can be regenerated!'] = 'Only photos with HD can be regenerated!';
+
+$lang['Updates'] = 'Updates';
+$lang['Update in progress... Please wait.'] = 'Update in progress... Please wait.';
+$lang['Ignore this update'] = 'Ignore this update';
+$lang['Reset ignored updates'] = 'Reset ignored updates';
+$lang['Update All'] = 'Update All';
+$lang['ERROR'] = 'ERROR';
+$lang['Update Complete'] = 'Update Complete';
+$lang['Piwigo Update'] = 'Piwigo Update';
+$lang['Extensions Update'] = 'Extensions Update';
+$lang['All extensions are up to date.'] = 'All extensions are up to date.';
+$lang['Following plugins may not be compatible with the new version of Piwigo:'] = 'Following plugins may not be compatible with the new version of Piwigo:';
+$lang['Following themes may not be compatible with the new version of Piwigo:'] = 'Following themes may not be compatible with the new version of Piwigo:';
+$lang['I decide to update anyway'] = 'I decide to update anyway';
+$lang['Update to Piwigo %s'] = 'Update to Piwigo %s';
+$lang['Two updates are available'] = 'Two updates are available';
+$lang['This is a minor update, with only bug corrections.'] = 'This is a minor update, with only bug corrections.';
+$lang['This is a major update, with <a href="%s">new exciting features</a>.'] = 'This is a major update, with <a href="%s">new exciting features</a>.';
+$lang['Some themes and plugins may be not available yet.'] = 'Some themes and plugins may be not available yet.';
+$lang['You can update to Piwigo %s directly, without upgrading to Piwigo %s (recommended).'] = 'You can update to Piwigo %s directly, without upgrading to Piwigo %s (recommended).';
+$lang['Save Template Directory'] = 'Save template directory';
+$lang['Dump Database'] = 'Dump Database';
+$lang['Include history data (Warning: server memory limit may be exceeded)'] = 'Include history data (Warning: server memory limit may be exceeded)';
+$lang['Unable to write new local directory.'] = 'Unable to write new local directory.';
+$lang['Unable to send template directory.'] = 'Unable to send template directory.';
+$lang['Unable to dump database.'] = 'Unable to dump database.';
 ?>
Index: /trunk/language/fr_FR/admin.lang.php
===================================================================
--- /trunk/language/fr_FR/admin.lang.php	(revision 10510)
+++ /trunk/language/fr_FR/admin.lang.php	(revision 10511)
@@ -808,3 +808,29 @@
 $lang['%s photos can not be regenerated'] = '%s photos ne peuvent pas être régénérées';
 $lang['Only photos with HD can be regenerated!'] = 'Seules les photos avec HD peuvent être régénérées';
+
+$lang['Updates'] = 'Mises à jour';
+$lang['Update in progress... Please wait.'] = 'Mise à jour en cours... Veuillez patienter.';
+$lang['Ignore this update'] = 'Ignorer cette mise à jour';
+$lang['Reset ignored updates'] = 'Réinitialiser les mises à jour ignorées';
+$lang['Update All'] = 'Tout mettre à jour';
+$lang['ERROR'] = 'ERREUR';
+$lang['Update Complete'] = 'Mise à jour effectuée';
+$lang['Piwigo Update'] = 'Mise à jour de Piwigo';
+$lang['Extensions Update'] = 'Mise à jour des extensions';
+$lang['All extensions are up to date.'] = 'Toutes les extensions sont à jour.';
+$lang['Following plugins may not be compatible with the new version of Piwigo:'] = 'Les plugins suivants ne seront peut-être pas compatibles avec la nouvelle version de Piwigo:';
+$lang['Following themes may not be compatible with the new version of Piwigo:'] = 'Les thèmes suivants ne seront peut-être pas compatibles avec la nouvelle version de Piwigo:';
+$lang['I decide to update anyway'] = 'Je décide de migrer quand même';
+$lang['Update to Piwigo %s'] = 'Mettre à jour vers Piwigo %s';
+$lang['Two updates are available'] = 'Deux mises à jour sont disponibles';
+$lang['This is a minor update, with only bug corrections.'] = 'Ceci est une mise à jour mineure, avec uniquement des corrections de bugs.';
+$lang['This is a major update, with <a href="%s">new exciting features</a>.'] = 'Ceci est une mise à jour majeure, qui contient <a href="%s">un tas de nouveautés</a>.';
+$lang['Some themes and plugins may be not available yet.'] = 'Certains thèmes ou plugins ne sont peut-être pas encore disponibles.';
+$lang['You can update to Piwigo %s directly, without upgrading to Piwigo %s (recommended).'] = 'Vous pouvez mettre à jour vers Piwigo %s directement, sans passer par Piwigo %s (recommandé).';
+$lang['Save Template Directory'] = 'Sauvegarder le dossier template';
+$lang['Dump Database'] = 'Sauvegarder la base de données';
+$lang['Include history data (Warning: server memory limit may be exceeded)'] = 'Inclure les données de l\'historique (Attention: risque de dépassement de la limite mémoire du serveur)';
+$lang['Unable to write new local directory.'] = 'Impossible d\'écrire le nouveau dossier local.';
+$lang['Unable to send template directory.'] = 'Impossible d\'envoyer le dossier template.';
+$lang['Unable to dump database.'] = 'Impossible de sauvegarder la base de données.';
 ?>
Index: /trunk/ws.php
===================================================================
--- /trunk/ws.php	(revision 10510)
+++ /trunk/ws.php	(revision 10511)
@@ -419,4 +419,31 @@
 <br>If maxwidth, maxheight or quality are missing, default parameters of upload will be used.'
 );
+
+  $service->addMethod(
+    'pwg.extensions.update',
+    'ws_extensions_update',
+    array(
+      'type' => array(),
+      'id' => array(),
+      'revision'=> array(),
+      'pwg_token' => array(),
+    ),
+    'Update an extension. Webmaster only.
+<br>Parameter type must be "plugins", "languages" or "themes".'
+  );
+
+  $service->addMethod(
+    'pwg.extensions.ignoreUpdate',
+    'ws_extensions_ignoreupdate',
+    array(
+      'type' => array('default'=>null),
+      'id' => array('default'=>null),
+      'reset' => array('default'=>null),
+      'pwg_token' => array(),
+    ),
+    'Ignore an extension if it need update.
+<br>Parameter type must be "plugins", "languages" or "themes".
+<br>If reset parameter is true, all ignored extensions will be reinitilized.'
+  );
 }
 
