Index: plugins/maintenance/_admin.php
===================================================================
--- plugins/maintenance/_admin.php	(revision 1179)
+++ plugins/maintenance/_admin.php	(revision 1925)
@@ -12,15 +12,66 @@
 if (!defined('DC_CONTEXT_ADMIN')) { return; }
 
-$_menu['Plugins']->addItem(__('Maintenance'),'plugin.php?p=maintenance','index.php?pf=maintenance/icon.png',
-		preg_match('/plugin.php\?p=maintenance(&.*)?$/',$_SERVER['REQUEST_URI']),
-		$core->auth->isSuperAdmin());
+// Sidebar menu
+$_menu['Plugins']->addItem(
+	__('Maintenance'),
+	'plugin.php?p=maintenance',
+	'index.php?pf=maintenance/icon.png',
+	preg_match('/plugin.php\?p=maintenance(&.*)?$/', $_SERVER['REQUEST_URI']),
+	$core->auth->isSuperAdmin()
+);
 
-$core->addBehavior('adminDashboardFavs','maintenanceDashboardFavs');
+// Admin behaviors
+$core->addBehavior('dcMaintenanceRegister', array('dcMaintenanceAdmin', 'register'));
+$core->addBehavior('adminDashboardFavs', array('dcMaintenanceAdmin', 'favs'));
 
-function maintenanceDashboardFavs($core,$favs)
+/**
+@ingroup PLUGIN_MAINTENANCE
+@nosubgrouping
+@brief Maintenance plugin admin class.
+
+Group of methods used on behaviors.
+*/
+class dcMaintenanceAdmin
 {
-	$favs['maintenance'] = new ArrayObject(array('maintenance','Maintenance','plugin.php?p=maintenance',
-		'index.php?pf=maintenance/icon.png','index.php?pf=maintenance/icon-big.png',
-		null,null,null));
+	/**
+	 * Register default tasks
+	 *
+	 * @param	$core	<b>dcCore</b>	dcCore instance
+	 * @param	$tasks	<b>arrayObject</b>	Array of tasks to register
+	 * @param	$groups	<b>arrayObject</b>	Array of groups to register
+	 */
+	 public static function register($core, $tasks, $groups)
+	{
+		$groups['optimize'] = __('Optimize');
+		$groups['index'] = __('Count and index');
+		$groups['purge'] = __('Purge');
+		$groups['other'] = __('Other');
+
+		$tasks[] = 'dcMaintenanceCache';
+		$tasks[] = 'dcMaintenanceCountcomments';
+		$tasks[] = 'dcMaintenanceIndexcomments';
+		$tasks[] = 'dcMaintenanceIndexposts';
+		$tasks[] = 'dcMaintenanceLogs';
+		$tasks[] = 'dcMaintenanceVacuum';
+	}
+
+	/**
+	 * Dashboard favs
+	 *
+	 * @param	$core	<b>dcCore</b>	dcCore instance
+	 * @param	$favs	<b>arrayObject</b>	Array of favs
+	 */
+	public static function favs($core, $favs)
+	{
+		$favs['maintenance'] = new ArrayObject(array(
+			'maintenance',
+			'Maintenance',
+			'plugin.php?p=maintenance',
+			'index.php?pf=maintenance/icon.png',
+			'index.php?pf=maintenance/icon-big.png',
+			null,null,null
+		));
+	}
+
+	/** @todo Rminder*/
 }
-?>
Index: plugins/maintenance/_define.php
===================================================================
--- plugins/maintenance/_define.php	(revision 1179)
+++ plugins/maintenance/_define.php	(revision 1925)
@@ -14,7 +14,6 @@
 $this->registerModule(
 	/* Name */			"Maintenance",
-	/* Description*/		"Maintain your database",
-	/* Author */			"Olivier Meunier",
-	/* Version */			'1.2'
+	/* Description*/		"Maintain your installation",
+	/* Author */			"Olivier Meunier & Association Dotclear",
+	/* Version */			'1.3'
 );
-?>
Index: plugins/maintenance/_prepend.php
===================================================================
--- plugins/maintenance/_prepend.php	(revision 1925)
+++ plugins/maintenance/_prepend.php	(revision 1925)
@@ -0,0 +1,27 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+if (!defined('DC_RC_PATH')) { return; }
+
+global $__autoload;
+
+$__autoload['dcMaintenance'] = dirname(__FILE__).'/inc/class.dc.maintenance.php';
+$__autoload['dcMaintenanceTask'] = dirname(__FILE__).'/inc/class.dc.maintenance.task.php';
+$__autoload['dcMaintenanceRest'] = dirname(__FILE__).'/_services.php';
+
+$__autoload['dcMaintenanceCache'] = dirname(__FILE__).'/inc/tasks/class.dc.maintenance.cache.php';
+$__autoload['dcMaintenanceCountcomments'] = dirname(__FILE__).'/inc/tasks/class.dc.maintenance.countcomments.php';
+$__autoload['dcMaintenanceIndexcomments'] = dirname(__FILE__).'/inc/tasks/class.dc.maintenance.indexcomments.php';
+$__autoload['dcMaintenanceIndexposts'] = dirname(__FILE__).'/inc/tasks/class.dc.maintenance.indexposts.php';
+$__autoload['dcMaintenanceLogs'] = dirname(__FILE__).'/inc/tasks/class.dc.maintenance.logs.php';
+$__autoload['dcMaintenanceVacuum'] = dirname(__FILE__).'/inc/tasks/class.dc.maintenance.vacuum.php';
+
+$this->core->rest->addFunction('dcMaintenanceStep', array('dcMaintenanceRest', 'step'));
Index: plugins/maintenance/_services.php
===================================================================
--- plugins/maintenance/_services.php	(revision 1925)
+++ plugins/maintenance/_services.php	(revision 1925)
@@ -0,0 +1,57 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+if (!defined('DC_CONTEXT_ADMIN')) { return; }
+
+/**
+@ingroup PLUGIN_MAINTENANCE
+@nosubgrouping
+@brief Maintenance plugin rest service class.
+
+Serve maintenance methods via Dotclear's rest API
+*/
+class dcMaintenanceRest
+{
+	/**
+	 * Serve method to do step by step task for maintenance.
+	 *
+	 * @param	core	<b>dcCore</b>	dcCore instance
+	 * @param	get		<b>array</b>	cleaned $_GET
+	 * @param	post	<b>array</b>	cleaned $_POST
+	 *
+	 * @return	<b>xmlTag</b>	XML representation of response
+	 */
+	public static function step($core, $get, $post)
+	{
+		if (!isset($post['task'])) {
+			throw new Exception('No task ID');
+		}
+		if (!isset($post['code'])) {
+			throw new Exception('No code ID');
+		}
+
+		$maintenance = new dcMaintenance($core);
+		if (($task = $maintenance->getTask($post['task'])) === null) {
+			throw new Exception('Unknow task ID');
+		}
+
+		$task->code((integer) $post['code']);
+		if (($code = $task->execute()) === true) {
+			$code = 0;
+		}
+
+		$rsp = new xmlTag('step');
+		$rsp->code = $code;
+		$rsp->title = html::escapeHTML($task->success());
+
+		return $rsp;
+	}
+}
Index: plugins/maintenance/inc/class.dc.maintenance.php
===================================================================
--- plugins/maintenance/inc/class.dc.maintenance.php	(revision 1925)
+++ plugins/maintenance/inc/class.dc.maintenance.php	(revision 1925)
@@ -0,0 +1,139 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+if (!defined('DC_RC_PATH')) { return; }
+
+/**
+@defgroup PLUGIN_MAINTENANCE Maintenance plugin for Dotclear
+*/
+
+/**
+@ingroup PLUGIN_MAINTENANCE
+@nosubgrouping
+@brief Maintenance plugin core class
+
+Main class to call everything related to maintenance.
+*/
+class dcMaintenance
+{
+	private $core;
+	private $tasks = array();
+	private $groups = array();
+
+	/**
+	 * Constructor.
+	 *
+	 * Here you register tasks and groups for tasks.
+	 *
+	 * @param	core	<b>dcCore</b>	dcCore instance
+	 */
+	public function __construct($core)
+	{
+		$this->core = $core;
+
+		$tasks = new ArrayObject();
+		$groups = new ArrayObject();
+
+		# --BEHAVIOR-- dcMaintenanceRegister
+		$core->callBehavior('dcMaintenanceRegister', $core, $tasks, $groups);
+
+		$this->init($tasks, $groups);
+	}
+
+	/**
+	 * Initialize list of groups and tasks.
+	 *
+	 * @param	tasks	<b>arrayObject</b>	Array of task to register
+	 * @param	groups	<b>arrayObject</b>	Array of groups to add
+	 */
+	public function init($tasks, $groups)
+	{
+		$this->tasks = $this->groups = array();
+
+		foreach($tasks as $task)
+		{
+			if (!class_exists($task)) {
+				continue;
+			}
+
+			$r = new ReflectionClass($task);
+			$p = $r->getParentClass();
+
+			if (!$p || $p->name != 'dcMaintenanceTask') {
+				continue;
+			}
+
+			$this->tasks[$task] = new $task($this->core, 'plugin.php?p=maintenance');
+		}
+
+		foreach($groups as $id => $name)
+		{
+			$this->groups[(string) $id] = (string) $name;
+		}
+	}
+
+	/**
+	 * Get a group name.
+	 *
+	 * @param	id	<b>string</b> Group ID
+	 * @return	<b>mixed</b> Group name or null if not exists
+	 */
+	public function getGroup($id)
+	{
+		return array_key_exists($id, $this->groups) ? $this->groups[$id] : null;
+	}
+
+	/**
+	 * Get groups.
+	 *
+	 * @return	<b>array</b> Array of groups ID and name
+	 */
+	public function getGroups()
+	{
+		return $this->groups;
+	}
+
+	/**
+	 * Get a task object.
+	 *
+	 * @param	id	<b>string</b> task ID
+	 * @return	<b>mixed</b> Task object or null if not exists
+	 */
+	public function getTask($id)
+	{
+		return array_key_exists($id, $this->tasks) ? $this->tasks[$id] : null;
+	}
+
+	/**
+	 * Get tasks.
+	 *
+	 * @return	<b>array</b> Array of tasks objects
+	 */
+	public function getTasks()
+	{
+		return $this->tasks;
+	}
+
+	/**
+	 * Get headers for plugin maintenance admin page.
+	 *
+	 * @return	<b>string</b> Page headers
+	 */
+	public function getHeaders()
+	{
+		$res = '';
+		foreach($this->tasks as $task)
+		{
+			$res .= $task->header();
+		}
+		return $res;
+	}
+}
Index: plugins/maintenance/inc/class.dc.maintenance.task.php
===================================================================
--- plugins/maintenance/inc/class.dc.maintenance.task.php	(revision 1925)
+++ plugins/maintenance/inc/class.dc.maintenance.task.php	(revision 1925)
@@ -0,0 +1,209 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+if (!defined('DC_RC_PATH')) { return; }
+
+/**
+@ingroup PLUGIN_MAINTENANCE
+@nosubgrouping
+@brief Maintenance plugin task class.
+
+Every task of maintenance must extend this class.
+*/
+class dcMaintenanceTask
+{
+	protected $core;
+	protected $p_url;
+	protected $code;
+
+	protected $id;
+	protected $name;
+	protected $group = 'other';
+
+	protected $task;
+	protected $step;
+	protected $error;
+	protected $success;
+	protected $reminder;
+
+	/**
+	 * Constructor.
+	 *
+	 * If your task required something on construct,
+	 * use method init() to do it.
+	 *
+	 * @param	core	<b>dcCore</b>	dcCore instance
+	 * @param	p_url	<b>string</b>	Maintenance plugin url
+	 */
+	public function __construct($core, $p_url)
+	{
+		$this->core =& $core;
+		$this->init();
+
+		$this->p_url = $p_url;
+		$this->id = get_class($this);
+
+		if (!$this->name) {
+			$this->name = get_class($this);
+		}
+		if (!$this->error) {
+			$this->error = __('Failed to execute task.');
+		}
+		if (!$this->success) {
+			$this->success = __('Task successfully executed.');
+		}
+	}
+
+	/**
+	 * Initialize task object.
+	 *
+	 * Better to set translated messages here than
+	 * to rewrite constructor.
+	 */
+	protected function init()
+	{
+		return null;
+	}
+
+	/**
+	 * Set $code for task having multiple steps.
+	 *
+	 * @param	code	<b>integer</b>	Code used for task execution
+	 */
+	public function code($code)
+	{
+		$this->code = (integer) $code;
+	}
+
+	/**
+	 * Get task ID.
+	 *
+	 * @return	<b>string</b>	Task ID (class name)
+	 */
+	public function id()
+	{
+		return $this->id;
+	}
+
+	/**
+	 * Get task name.
+	 *
+	 * @return	<b>string</b>	Task name
+	 */
+	public function name()
+	{
+		return $this->name;
+	}
+
+	/**
+	 * Get task group.
+	 *
+	 * If task required a full tab, 
+	 * this must be returned null.
+	 * 
+	 * @return	<b>mixed</b>	Task group ID or null
+	 */
+	public function group()
+	{
+		return $this->group;
+	}
+
+	/**
+	 * Get task message.
+	 *
+	 * This message is used on form button.
+	 *
+	 * @return	<b>string</b>	Message
+	 */
+	public function task()
+	{
+		return $this->task;
+	}
+
+	/**
+	 * Get step message.
+	 *
+	 * This message is displayed during task step execution.
+	 *
+	 * @return	<b>mixed</b>	Message or null
+	 */
+	public function step()
+	{
+		return $this->step;
+	}
+
+	/**
+	 * Get success message.
+	 *
+	 * This message is displayed when task is accomplished.
+	 *
+	 * @return	<b>mixed</b>	Message or null
+	 */
+	public function success()
+	{
+		return $this->success;
+	}
+
+	/**
+	 * Get error message.
+	 *
+	 * This message is displayed on error.
+	 *
+	 * @return	<b>mixed</b>	Message or null
+	 */
+	public function error()
+	{
+		return $this->error;
+	}
+
+	/** @todo */
+	public function reminder()
+	{
+		return null;
+	}
+
+	/**
+	 * Get header.
+	 *
+	 * Headers required on maintenance page.
+	 *
+	 * @return 	<b>mixed</b>	Message or null
+	 */
+	public function header()
+	{
+		return null;
+	}
+
+	/**
+	 * Get content.
+	 *
+	 * Content for full tab task.
+	 *
+	 * @return	<b>string</b>	Tab's content
+	 */
+	public function content()
+	{
+		return null;
+	}
+
+	/**
+	 * Execute task.
+	 *
+	 * @return	<b>mixed</b>	:
+	 *	- FALSE on error,
+	 *	- TRUE if task is finished
+	 *	- INTEGER if task required a next step
+	 */
+	public function execute()
+	{
+		return null;
+	}
+}
Index: plugins/maintenance/inc/tasks/class.dc.maintenance.cache.php
===================================================================
--- plugins/maintenance/inc/tasks/class.dc.maintenance.cache.php	(revision 1925)
+++ plugins/maintenance/inc/tasks/class.dc.maintenance.cache.php	(revision 1925)
@@ -0,0 +1,31 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+if (!defined('DC_RC_PATH')) { return; }
+
+class dcMaintenanceCache extends dcMaintenanceTask
+{
+	protected $group = 'purge';
+
+	protected function init()
+	{
+		$this->task 		= __('Empty templates cache directory');
+		$this->success 		= __('Templates cache directory emptied.');
+		$this->error 		= __('Failed to empty templates cache directory.');
+	}
+
+	public function execute()
+	{
+		$this->core->emptyTemplatesCache();
+
+		return true;
+	}
+}
Index: plugins/maintenance/inc/tasks/class.dc.maintenance.countcomments.php
===================================================================
--- plugins/maintenance/inc/tasks/class.dc.maintenance.countcomments.php	(revision 1925)
+++ plugins/maintenance/inc/tasks/class.dc.maintenance.countcomments.php	(revision 1925)
@@ -0,0 +1,31 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+if (!defined('DC_RC_PATH')) { return; }
+
+class dcMaintenanceCountcomments extends dcMaintenanceTask
+{
+	protected $group = 'index';
+
+	protected function init()
+	{
+		$this->task 		= __('Reset comments and ping counters');
+		$this->success 		= __('Comments and trackback counted.');
+		$this->error 		= __('Failed to reset comments and ping counters.');
+	}
+
+	public function execute()
+	{
+		$this->core->countAllComments();
+
+		return true;
+	}
+}
Index: plugins/maintenance/inc/tasks/class.dc.maintenance.indexcomments.php
===================================================================
--- plugins/maintenance/inc/tasks/class.dc.maintenance.indexcomments.php	(revision 1925)
+++ plugins/maintenance/inc/tasks/class.dc.maintenance.indexcomments.php	(revision 1925)
@@ -0,0 +1,51 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+if (!defined('DC_RC_PATH')) { return; }
+
+class dcMaintenanceIndexcomments extends dcMaintenanceTask
+{
+	protected $group = 'index';
+	protected $limit = 500;
+	protected $step_task;
+
+	protected function init()
+	{
+		$this->name 		= __('Search engine index');
+		$this->task 		= __('Index all comments');
+		$this->step_task 	= __('next');
+		$this->step 		= __('Indexing comment %d to %d.');
+		$this->success 		= __('Comments index done.');
+		$this->error 		= __('Failed to index comments.');
+	}
+
+	public function execute()
+	{
+		$this->code = $this->core->indexAllComments($this->code, $this->limit);
+		
+		return $this->code ? $this->code : true;
+	}
+
+	public function task()
+	{
+		return $this->code ? $this->step_task : $this->task;
+	}
+
+	public function step()
+	{
+		return $this->code ? sprintf($this->step, $this->code - $this->limit, $this->code) : null;
+	}
+
+	public function success()
+	{
+		return $this->code ? sprintf($this->step, $this->code - $this->limit, $this->code) : $this->success;
+	}
+}
Index: plugins/maintenance/inc/tasks/class.dc.maintenance.indexposts.php
===================================================================
--- plugins/maintenance/inc/tasks/class.dc.maintenance.indexposts.php	(revision 1925)
+++ plugins/maintenance/inc/tasks/class.dc.maintenance.indexposts.php	(revision 1925)
@@ -0,0 +1,51 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+if (!defined('DC_RC_PATH')) { return; }
+
+class dcMaintenanceIndexposts extends dcMaintenanceTask
+{
+	protected $group = 'index';
+	protected $limit = 500;
+	protected $step_task;
+
+	protected function init()
+	{
+		$this->name 		= __('Search engine index');
+		$this->task 		= __('Index all posts');
+		$this->step_task	= __('next');
+		$this->step 		= __('Indexing entry %d to %d.');
+		$this->success 		= __('Entries index done.');
+		$this->error 		= __('Failed to index entries.');
+	}
+
+	public function execute()
+	{
+		$this->code = $this->core->indexAllPosts($this->code, $this->limit);
+
+		return $this->code ? $this->code : true;
+	}
+
+	public function task()
+	{
+		return $this->code ? $this->step_task : $this->task;
+	}
+
+	public function step()
+	{
+		return $this->code ? sprintf($this->step, $this->code - $this->limit, $this->code) : null;
+	}
+
+	public function success()
+	{
+		return $this->code ? sprintf($this->step, $this->code - $this->limit, $this->code) : $this->success;
+	}
+}
Index: plugins/maintenance/inc/tasks/class.dc.maintenance.logs.php
===================================================================
--- plugins/maintenance/inc/tasks/class.dc.maintenance.logs.php	(revision 1925)
+++ plugins/maintenance/inc/tasks/class.dc.maintenance.logs.php	(revision 1925)
@@ -0,0 +1,31 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+if (!defined('DC_RC_PATH')) { return; }
+
+class dcMaintenanceLogs extends dcMaintenanceTask
+{
+	protected $group = 'purge';
+
+	protected function init()
+	{
+		$this->task 		= __('Delete all logs');
+		$this->success 		= __('Logs deleted.');
+		$this->error 		= __('Failed to delete logs.');
+	}
+
+	public function execute()
+	{
+		$this->core->log->delLogs(null, true);
+
+		return true;
+	}
+}
Index: plugins/maintenance/inc/tasks/class.dc.maintenance.vacuum.php
===================================================================
--- plugins/maintenance/inc/tasks/class.dc.maintenance.vacuum.php	(revision 1925)
+++ plugins/maintenance/inc/tasks/class.dc.maintenance.vacuum.php	(revision 1925)
@@ -0,0 +1,38 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+if (!defined('DC_RC_PATH')) { return; }
+
+class dcMaintenanceVacuum extends dcMaintenanceTask
+{
+	protected $group = 'optimize';
+
+	protected function init()
+	{
+		$this->task 		= __('Vacuum tables');
+		$this->success 		= __('Optimization successful.');
+		$this->error 		= __('Failed to vacuum tables.');
+	}
+
+	public function execute()
+	{
+		$schema = dbSchema::init($this->core->con);
+
+		foreach ($schema->getTables() as $table)
+		{
+			if (strpos($table, $this->core->prefix) === 0) {
+				$this->core->con->vacuum($table);
+			}
+		}
+
+		return true;
+	}
+}
Index: plugins/maintenance/index.php
===================================================================
--- plugins/maintenance/index.php	(revision 1760)
+++ plugins/maintenance/index.php	(revision 1925)
@@ -12,187 +12,177 @@
 if (!defined('DC_CONTEXT_ADMIN')) { return; }
 
-$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : null;
-$start = !empty($_GET['start']) ? abs((integer) $_GET['start']) : 0;
+dcPage::checkSuper();
 
-if ($action == 'vacuum')
-{
-	try
-	{
-		$schema = dbSchema::init($core->con);
-		$db_tables = $schema->getTables();
-		
-		foreach ($db_tables as $t) {
-			if (strpos($t,$core->prefix) === 0) {
-				$core->con->vacuum($t);
-			}
+// main class
+
+$maintenance = new dcMaintenance($core);
+
+// Set var
+
+$headers = '';
+$p_url = 'plugin.php?p=maintenance';
+$task = null;
+
+$code = empty($_POST['code']) ? null : (integer) $_POST['code'];
+$tab = empty($_REQUEST['tab']) ? 'maintenance' : $_REQUEST['tab'];
+
+// Get task object
+
+if (!empty($_REQUEST['task'])) {
+	$task = $maintenance->getTask($_REQUEST['task']);
+
+	if ($task === null) {
+		$core->error->add('Unknow task ID');
+	}
+
+	$task->code($code);
+}
+
+// Execute task
+
+if ($task && !empty($_POST['task']) && $task->id() == $_POST['task']) {
+	try {
+		$code = $task->execute();
+		if (false === $code) {
+			throw new Exception($task->error());
 		}
-		http::redirect($p_url.'&vacuum=1');
+		if (true === $code) {
+			http::redirect($p_url.'&task='.$task->id().'&done=1&tab='.$tab);
+		}
 	}
-	catch (Exception $e)
-	{
-		$core->error->add($e->getMessage());
-	}
-}
-elseif ($action == 'commentscount')
-{
-	try {
-		$core->countAllComments();
-		http::redirect($p_url.'&commentscount=1');
-	} catch (Exception $e) {
-		$core->error->add($e->getMessage());
-	}
-}
-elseif ($action == 'empty_cache')
-{
-	try {
-		$core->emptyTemplatesCache();
-		http::redirect($p_url.'&empty_cache=1');
-	} catch (Exception $e) {
-		$core->error->add($e->getMessage());
-	}
-}
-elseif ($action == 'log')
-{
-	try {
-		$core->log->delLogs(null,true);
-		http::redirect($p_url.'&delete_logs=1');
-	} catch (Exception $e) {
+	catch (Exception $e) {
 		$core->error->add($e->getMessage());
 	}
 }
 
-?>
-<html>
-<head>
-  <title><?php echo __('Maintenance'); ?></title>
+// Display page
+
+echo '<html><head>
+<title>'.__('Maintenance').'</title>'.
+dcPage::jsPageTabs($tab);
+
+if ($task) {
+	echo 
+	'<script type="text/javascript">'."\n".
+	"//<![CDATA\n".
+	dcPage::jsVar('dotclear.msg.wait', __('Please wait...')).
+	"//]]>\n".
+	'</script>'.
+	dcPage::jsLoad('index.php?pf=maintenance/js/dc.maintenance.js');
+}
+
+echo 
+$maintenance->getHeaders().'
 </head>
+<body>';
 
-<body>
-<?php
+// Success message
+
+if ($task && !empty($_GET['done'])) {
+	dcPage::success($task->success());
+}
+
+if ($task && ($res = $task->step()) !== null) {
+
+	// Page title
+
+	echo dcPage::breadcrumb(
+		array(
+			__('Plugins') => '',
+			'<a href="'.$p_url.'">'.__('Maintenance').'</a>' => '',
+			'<span class="page-title">'.html::escapeHTML($task->name()).'</span>' => ''
+		)
+	);
+
+	// Intermediate task (task required several steps)
+
+	echo 
+	'<div class="step-box" id="'.$task->id().'">'.
+	'<h3>'.html::escapeHTML($task->name()).'</h3>'.
+	'<form action="'.$p_url.'" method="post">'.
+	'<p class="step-msg">'.
+		$res.
+	'</p>'.
+	'<p class="step-submit">'.
+		'<input type="submit" value="'.$task->task().'" /> '.
+		form::hidden(array('task'), $task->id()).
+		form::hidden(array('code'), (integer) $code).
+		$core->formNonce().
+	'</p>'.
+	'</form>'.
+	'<p class="step-back">'.
+		'<a class="back" href="'.$p_url.'">'.__('Back').'</a>'.
+	'</p>'.
+	'</div>';
+}
+else {
+
+	// Page title
+
 	echo dcPage::breadcrumb(
 		array(
 			__('Plugins') => '',
 			'<span class="page-title">'.__('Maintenance').'</span>' => ''
-		));
-?>
+		)
+	);
 
-<?php
-if (!empty($_GET['vacuum'])) {
-	dcPage::success(__('Optimization successful.'));
-}
-if (!empty($_GET['commentscount'])) {
-	dcPage::success(__('Comments and trackback counted.'));
-}
-if (!empty($_GET['empty_cache'])) {
-	dcPage::success(__('Templates cache directory emptied.'));
-}
-if (!empty($_GET['delete_logs'])) {
-	dcPage::success(__('Logs deleted.'));
+	// Simple task (with only a button to start it)
+
+	echo 
+	'<div id="maintenance" class="multi-part" title="'.__('Maintenance').'">'.
+	'<h3>'.__('Maintenance').'</h3>'.
+	'<form action="'.$p_url.'" method="post">';
+
+	foreach($maintenance->getGroups($core) as $g_id => $g_name)
+	{
+		$res = '';
+		foreach($maintenance->getTasks($core) as $t)
+		{
+			if ($t->group() != $g_id) {
+				continue;
+			}
+
+			$res .=  
+			'<p>'.form::radio(array('task', $t->id()),$t->id()).' '.
+			'<label class="classic" for="'.$t->id().'">'.
+			html::escapeHTML($t->task()).'</label></p>';
+		}
+
+		if (!empty($res)) {
+			echo '<div class="fieldset"><h4 id="'.$g_id.'">'.$g_name.'</h4>'.$res.'</div>';
+		}
+	}
+
+	echo 
+	'<p><input type="submit" value="'.__('Execute task').'" /> '.
+	form::hidden(array('tab'), 'maintenance').
+	$core->formNonce().'</p>'.
+	'<p class="form-note info">'.__('This may take a very long time').'.</p>'.
+	'</form>'.
+	'</div>';
+
+	// Advanced tasks (that required a tab)
+
+	foreach($maintenance->getTasks($core) as $t)
+	{
+		if ($t->group() !== null) {
+			continue;
+		}
+
+		echo 
+		'<div id="'.$t->id().'" class="multi-part" title="'.$t->name().'">'.
+		'<h3>'.$t->name().'</h3>'.
+		'<form action="'.$p_url.'" method="post">'.
+		$t->content().
+		'<p><input type="submit" value="'.__('Execute task').'" /> '.
+		form::hidden(array('task'), $t->id()).
+		form::hidden(array('tab'), $t->id()).
+		$core->formNonce().'</p>'.
+		'</form>'.
+		'</div>';
+	}
 }
 
-if ($action == 'index' && !empty($_GET['indexposts']))
-{
-	$limit = 1000;
-	echo '<p>'.sprintf(__('Indexing entry %d to %d.'),$start,$start+$limit).'</p>';
-	
-	$new_start = $core->indexAllPosts($start,$limit);
-	
-	if ($new_start)
-	{
-		$new_url = $p_url.'&action=index&indexposts=1&start='.$new_start;
-		echo
-		'<script type="text/javascript">'."\n".
-		"//<![CDATA\n".
-		"window.location = '".$new_url."'\n".
-		"//]]>\n".
-		'</script>'.
-		'<noscript><p><a href="'.html::escapeURL($new_url).'">'.__('next').'</a></p></noscript>';
-	}
-	else
-	{
-		dcPage::message(__('Entries index done.'));
-		echo '<p><a class="back" href="'.$p_url.'">'.__('Back').'</a></p>';
-	}
-}
-elseif ($action == 'index' && !empty($_GET['indexcomments']))
-{
-	$limit = 1000;
-	echo '<p>'.sprintf(__('Indexing comment %d to %d.'),$start,$start+$limit).'</p>';
-	
-	$new_start = $core->indexAllComments($start,$limit);
-	
-	if ($new_start)
-	{
-		$new_url = $p_url.'&action=index&indexcomments=1&start='.$new_start;
-		echo
-		'<script type="text/javascript">'."\n".
-		"//<![CDATA\n".
-		"window.location = '".$new_url."'\n".
-		"//]]>\n".
-		'</script>'.
-		'<noscript><p><a href="'.html::escapeURL($new_url).'">'.__('next').'</a></p></noscript>';
-	}
-	else
-	{
-		dcPage::message(__('Comments index done.'));
-		echo '<p><a class="back" href="'.$p_url.'">'.__('Back').'</a></p>';
-	}
-}
-else
-{
-	echo
-	'<div class="two-boxes">'.
-	'<h3>'.__('Optimize database room').'</h3>'.
-	'<form action="plugin.php" method="post">'.
-	'<p><input type="submit" value="'.__('Vacuum tables').'" /> '.
-	$core->formNonce().
-	form::hidden(array('action'),'vacuum').
-	form::hidden(array('p'),'maintenance').'</p>'.
-	'</form></div>';
-	
-	echo
-	'<div class="two-boxes">'.
-	'<h3>'.__('Counters').'</h3>'.
-	'<form action="plugin.php" method="post">'.
-	'<p><input type="submit" value="'.__('Reset comments and ping counters').'" /> '.
-	$core->formNonce().
-	form::hidden(array('action'),'commentscount').
-	form::hidden(array('p'),'maintenance').'</p>'.
-	'</form></div>';
-	
-	echo
-	'<div class="two-boxes">'.
-	'<h3>'.__('Search engine index').'</h3>'.
-	'<form action="plugin.php" method="get">'.
-	'<p><input type="submit" name="indexposts" value="'.__('Index all posts').'" /> '.
-	'<input type="submit" name="indexcomments" value="'.__('Index all comments').'" /> '.
-	form::hidden(array('action'),'index').
-	form::hidden(array('p'),'maintenance').'</p>'.
-	'<p class="form-note info">'.__('This may take a very long time').'.</p>'.
-	'</form></div>';
-	
-	echo
-	'<div class="two-boxes">'.
-	'<h3>'.__('Vacuum logs').'</h3>'.
-	'<form action="plugin.php" method="post">'.
-	'<p><input type="submit" value="'.__('Delete all logs').'" /> '.
-	$core->formNonce().
-	form::hidden(array('action'),'log').
-	form::hidden(array('p'),'maintenance').'</p>'.
-	'</form></div>';
-	
-	echo
-	'<div class="two-boxes">'.
-	'<h3>'.__('Empty templates cache directory').'</h3>'.
-	'<form action="plugin.php" method="post">'.
-	'<p><input type="submit" value="'.__('Empty directory').'" /> '.
-	$core->formNonce().
-	form::hidden(array('action'),'empty_cache').
-	form::hidden(array('p'),'maintenance').'</p>'.
-	'</form></div>';
-}
 dcPage::helpBlock('maintenance');
-?>
 
-</body>
-</html>
+echo '</body></html>';
Index: plugins/maintenance/js/dc.maintenance.js
===================================================================
--- plugins/maintenance/js/dc.maintenance.js	(revision 1925)
+++ plugins/maintenance/js/dc.maintenance.js	(revision 1925)
@@ -0,0 +1,45 @@
+$(function(){
+	$('.step-box').each(function(){
+		var code = $('input[name=code]',this).val();
+
+		$('.step-submit',this).remove();
+		$('.step-back',this).hide();
+		$('.step-msg',this).after(
+			$('<p>').addClass('step-wait').text(
+				dotclear.msg.wait
+			)
+		);
+
+		dcMaintenanceStep(this,code);
+
+		function dcMaintenanceStep(box,code) {
+			var params = {
+				f: 'dcMaintenanceStep',
+				xd_check: dotclear.nonce,
+				task: $(box).attr('id'),
+				code: code
+			}
+			$.post('services.php',params,function(data) {
+				if ($('rsp[status=failed]',data).length > 0) {
+					$('.step-msg',box).text(
+						$('rsp',data).text()
+					);
+					$('.step-wait',box).remove();
+					$('.step-back',box).show();
+				} else {
+					$('.step-msg',box).text(
+						$('rsp>step',data).attr('title')
+					);
+					var code = $('rsp>step',data).attr('code'); 
+					if (code > 0){
+						dcMaintenanceStep(box,code);
+					} else {
+						$('.step-msg',box).addClass('success');
+						$('.step-wait',box).remove();
+						$('.step-back',box).show();
+					}
+				}
+			});
+		}
+	});
+});
