Index: admin/comments.php
===================================================================
--- admin/comments.php	(revision 1912)
+++ admin/comments.php	(revision 1999)
@@ -131,21 +131,12 @@
 $combo_action = array();
 $default = '';
-if ($core->auth->check('publish,contentadmin',$core->blog->id))
+if ($core->auth->check('delete,contentadmin',$core->blog->id) && $status == -2)
 {
-	$combo_action[__('Publish')] = 'publish';
-	$combo_action[__('Unpublish')] = 'unpublish';
-	$combo_action[__('Mark as pending')] = 'pending';
-	$combo_action[__('Mark as junk')] = 'junk';
-}
-if ($core->auth->check('delete,contentadmin',$core->blog->id))
-{
-	$combo_action[__('Delete')] = 'delete';
-	if ($status == -2) {
-		$default = 'delete';
-	}
-}
-
-# --BEHAVIOR-- adminCommentsActionsCombo
-$core->callBehavior('adminCommentsActionsCombo',array(&$combo_action));
+	$default = 'delete';
+}
+
+$comments_actions_page = new dcCommentsActionsPage($core,'comments.php');
+
+$comments_actions_page->process();
 
 /* Get comments
@@ -165,6 +156,4 @@
 	$starting_script .= dcPage::jsLoad('js/filter-controls.js');
 }
-# --BEHAVIOR-- adminCommentsHeaders
-$starting_script .= $core->callBehavior('adminCommentsHeaders');
 
 dcPage::open(__('Comments and trackbacks'),$starting_script,
@@ -175,4 +164,9 @@
 		))
 );
+if (!empty($_GET['upd'])) {
+	dcPage::success(__('Selected comments have been successfully updated.'));
+} elseif (!empty($_GET['del'])) {
+	dcPage::success(__('Selected comments have been successfully deleted.'));
+}
 
 if (!$core->error->flag())
@@ -254,5 +248,5 @@
 	# Show comments
 	$comment_list->display($page,$nb_per_page,
-	'<form action="comments_actions.php" method="post" id="form-comments">'.
+	'<form action="comments.php" method="post" id="form-comments">'.
 	
 	'%s'.
@@ -262,5 +256,5 @@
 	
 	'<p class="col right"><label for="action" class="classic">'.__('Selected comments action:').'</label> '.
-	form::combo('action',$combo_action,$default,'','','','title="'.__('Actions').'"').
+	form::combo('action',$comments_actions_page->getCombo(),$default,'','','','title="'.__('Actions').'"').
 	$core->formNonce().
 	'<input type="submit" value="'.__('ok').'" /></p>'.
Index: admin/comments_actions.php
===================================================================
--- admin/comments_actions.php	(revision 1474)
+++ admin/comments_actions.php	(revision 1905)
@@ -11,132 +11,14 @@
 # -- END LICENSE BLOCK -----------------------------------------
 
+/* ### THIS FILE IS DEPRECATED 					### */
+/* ### IT IS ONLY USED FOR PLUGINS COMPATIBILITY ### */
+
 require dirname(__FILE__).'/../inc/admin/prepend.php';
 
 dcPage::check('usage,contentadmin');
 
-$params = array();
-$redir = 'comments.php';
+$comments_actions_page = new dcCommentsActionsPage($core,'posts.php');
 
-if (!empty($_POST['action']) && !empty($_POST['comments']))
-{
-	$comments = $_POST['comments'];
-	$action = $_POST['action'];
-	
-	if (isset($_POST['redir']) && strpos($_POST['redir'],'://') === false)
-	{
-		$redir = $_POST['redir'];
-	}
-	else
-	{
-		$redir =
-		'comments.php?type='.$_POST['type'].
-		'&author='.$_POST['author'].
-		'&status='.$_POST['status'].
-		'&sortby='.$_POST['sortby'].
-		'&ip='.$_POST['ip'].
-		'&order='.$_POST['order'].
-		'&page='.$_POST['page'].
-		'&nb='.(integer) $_POST['nb'];
-	}
-	
-	foreach ($comments as $k => $v) {
-		$comments[$k] = (integer) $v;
-	}
-	
-	$params['sql'] = 'AND C.comment_id IN('.implode(',',$comments).') ';
-	
-	if (!isset($_POST['full_content']) || empty($_POST['full_content'])) {
-		$params['no_content'] = true;
-	}
-	
-	$co = $core->blog->getComments($params);
-	
-	$co_ids = array();
-	while ($co->fetch())	{
-		$co_ids[] = $co->comment_id;
-	}
-	
-	# --BEHAVIOR-- adminCommentsActions
-	$core->callBehavior('adminCommentsActions',$core,$co,$action,$redir);
-	
-	if (preg_match('/^(publish|unpublish|pending|junk)$/',$action))
-	{
-		switch ($action) {
-			case 'unpublish' : $status = 0; break;
-			case 'pending' : $status = -1; break;
-			case 'junk' : $status = -2; break;
-			default : $status = 1; break;
-		}
-		
-		try {
-			$core->blog->updCommentsStatus($co_ids,$status);
-		} catch (Exception $e) {
-			$core->error->add($e->getMessage());
-		}
-		
-		if (!$core->error->flag()) {
-			http::redirect($redir);
-		}
-	}
-	elseif ($action == 'delete')
-	{
-		try {
-			// Backward compatibility
-			foreach($co_ids as $comment_id)
-			{
-				# --BEHAVIOR-- adminBeforeCommentDelete
-				$core->callBehavior('adminBeforeCommentDelete',$comment_id);				
-			}
-			
-			# --BEHAVIOR-- adminBeforeCommentsDelete
-			$core->callBehavior('adminBeforeCommentsDelete',$co_ids);
-			
-			$core->blog->delComments($co_ids);
-		} catch (Exception $e) {
-			$core->error->add($e->getMessage());
-		}
-		
-		if (!$core->error->flag()) {
-			http::redirect($redir);
-		}
-	}
-}
+$comments_actions_page->process();
 
-/* DISPLAY
--------------------------------------------------------- */
-dcPage::open(__('Comments'));
-
-if (!isset($action)) {
-	dcPage::close();
-	exit;
-}
-
-$hidden_fields = '';
-while ($co->fetch()) {
-	$hidden_fields .= form::hidden(array('comments[]'),$co->comment_id);
-} 
-
-if (isset($_POST['redir']) && strpos($_POST['redir'],'://') === false)
-{
-	$hidden_fields .= form::hidden(array('redir'),html::escapeURL($_POST['redir']));
-}
-else
-{
-	$hidden_fields .=
-	form::hidden(array('type'),$_POST['type']).
-	form::hidden(array('author'),$_POST['author']).
-	form::hidden(array('status'),$_POST['status']).
-	form::hidden(array('sortby'),$_POST['sortby']).
-	form::hidden(array('ip'),$_POST['ip']).
-	form::hidden(array('order'),$_POST['order']).
-	form::hidden(array('page'),$_POST['page']).
-	form::hidden(array('nb'),$_POST['nb']);
-}
-
-# --BEHAVIOR-- adminCommentsActionsContent
-$core->callBehavior('adminCommentsActionsContent',$core,$action,$hidden_fields);
-
-echo '<p><a class="back" href="'.str_replace('&','&amp;',$redir).'">'.__('Back to comments list').'</a></p>';
-
-dcPage::close();
 ?>
Index: admin/js/_posts_actions.js
===================================================================
--- admin/js/_posts_actions.js	(revision 1638)
+++ admin/js/_posts_actions.js	(revision 1806)
@@ -1,9 +1,12 @@
 $(function() {
-	$('#new_auth_id').autocomplete(usersList,
-	{
-		delay: 1000,
-		matchSubset: true,
-		matchContains: true
-	});
+	new_auth_id = $('#new_auth_id');
+	if (new_auth_id.length) {
+		new_auth_id.autocomplete(usersList,
+		{
+			delay: 1000,
+			matchSubset: true,
+			matchContains: true
+		});
+	}
 	$('#new_cat').toggleWithLegend($('#new_cat').parent().children().not('#new_cat'), {
 		// no cookie on new category as we don't use this every day
Index: admin/posts.php
===================================================================
--- admin/posts.php	(revision 1912)
+++ admin/posts.php	(revision 1999)
@@ -103,33 +103,8 @@
 
 # Actions combo box
-$combo_action = array();
-if ($core->auth->check('publish,contentadmin',$core->blog->id))
-{
-	$combo_action[__('Status')] = array(
-		__('Publish') => 'publish',
-		__('Unpublish') => 'unpublish',
-		__('Schedule') => 'schedule',
-		__('Mark as pending') => 'pending'
-	);
-}
-$combo_action[__('Mark')] = array(
-	__('Mark as selected') => 'selected',
-	__('Mark as unselected') => 'unselected'
-);
-$combo_action[__('Change')] = array(
-	__('Change category') => 'category',
-	__('Change language') => 'lang');
-if ($core->auth->check('admin',$core->blog->id))
-{
-	$combo_action[__('Change')] = array_merge($combo_action[__('Change')],
-		array(__('Change author') => 'author'));
-}
-if ($core->auth->check('delete,contentadmin',$core->blog->id))
-{
-	$combo_action[__('Delete')] = array(__('Delete') => 'delete');
-}
-
-# --BEHAVIOR-- adminPostsActionsCombo
-$core->callBehavior('adminPostsActionsCombo',array(&$combo_action));
+
+$posts_actions_page = new dcPostsActionsPage($core,'posts.php');
+
+$posts_actions_page->process();
 
 /* Get posts
@@ -304,5 +279,5 @@
 	# Show posts
 	$post_list->display($page,$nb_per_page,
-	'<form action="posts_actions.php" method="post" id="form-entries">'.
+	'<form action="posts.php" method="post" id="form-entries">'.
 	
 	'%s'.
@@ -312,5 +287,5 @@
 	
 	'<p class="col right"><label for="action" class="classic">'.__('Selected entries action:').'</label> '.
-	form::combo('action',$combo_action).
+	form::combo('action',$posts_actions_page->getCombo()).
 	'<input type="submit" value="'.__('ok').'" /></p>'.
 	form::hidden(array('user_id'),$user_id).
Index: admin/posts_actions.php
===================================================================
--- admin/posts_actions.php	(revision 1964)
+++ admin/posts_actions.php	(revision 1999)
@@ -11,490 +11,14 @@
 # -- END LICENSE BLOCK -----------------------------------------
 
+/* ### THIS FILE IS DEPRECATED 					### */
+/* ### IT IS ONLY USED FOR PLUGINS COMPATIBILITY ### */
+
 require dirname(__FILE__).'/../inc/admin/prepend.php';
 
 dcPage::check('usage,contentadmin');
 
-$params = array();
+$posts_actions_page = new dcPostsActionsPage($core,'posts.php');
 
-/**
-* FieldsList - Compatibility class for hidden fields & entries[] fields
-*
-*/
-class FieldsList {
-	/** @var array list of hidden fields */
-	protected $hidden;
-	/** @var array list of selected entries */
-	protected $entries;
+$posts_actions_page->process();
 
-
-   /**
-     * Class constructor
-	*/
-	public function __construct() {
-		$this->hidden=array();
-		$this->entries =array();
-	}
-
-    /**
-     * addHidden - adds a hidden field
-     * 
-     * @param string $name the field name.
-     * @param mixed $value the field value.
-     *
-     * @access public
-	 * @return the FieldsList instance, enabling to chain requests
-     */	
-	 public function addHidden($name,$value) {
-		$this->hidden[] = form::hidden($name,$value);
-		return $this;
-	}
-
-    /**
-     * addEntry - adds a antry field
-     * 
-     * @param string $id the entry id.
-     * @param mixed $title the entry title.
-     *
-     * @access public
-	 * @return the FieldsList instance, enabling to chain requests
-     */	
-	 public function addEntry($id,$title) {
-		$this->entries[$id]=$title;
-		return $this;
-	}
-
-    /**
-     * getHidden - returns the list of hidden fields, html encoded
-     *
-     * @access public
-	 * @return the list of hidden fields, html encoded
-     */
-	 public function getHidden() {
-		return join('',$this->hidden);
-	}
-	
-    /**
-     * getEntries - returns the list of entry fields, html encoded
-     *
-	 * @param boolean $hidden if set to true, returns entries as a list of hidden field
-	 *                if set to false, returns html code displaying the list of entries
-	 *                with a list of checkboxes to enable to select/deselect entries
-     * @access public
-	 * @return the list of entry fields, html encoded
-     */
-	public function getEntries ($hidden=false) {
-		$ret = '';
-		if ($hidden) {
-			foreach ($this->entries as $id=> $e) {
-				$ret .= form::hidden('entries[]',$id);
-			}
-		} else {
-			$ret = 
-				'<table class="posts-list"><tr>'.
-				'<th colspan="2">'.__('Title').'</th>'.
-				'</tr>';
-			foreach ($this->entries as $id=>$title) {
-				$ret .= 
-					'<tr><td>'.
-					form::checkbox(array('entries[]'),$id,true,'','').'</td>'.
-					'<td>'.	$title.'</td></tr>';
-			}
-			$ret .= '</table>';
-		}
-		return $ret;
-	}
-	
-    /**
-     * getEntriesQS - returns the list of entry fields as query string
-     *
-     * @access public
-	 * @return the list of entry fields, html encoded
-     */
-	public function getEntriesQS() {
-		$ret=array();
-		foreach ($this->entries as $id=>$title) {
-			$ret[] = 'entries[]='.$id;
-		}
-		return join('&',$ret);
-	}
-	
-    /**
-     * __toString - magic method. -- DEPRECATED here
-	 *              This method is only used to preserve compatibility with plugins 
-	 *				relying on previous versions of adminPostsActionsContent behavior, 
-	 *
-     * @access public
-	 * @return the list of hidden fields and entries (as hidden fields too), html encoded
-     */
-	public function __toString() {
-		return join('',$this->hidden).$this->getEntries(true);
-	}
-}
-
-$fields = new FieldsList();
-$posts_ids = array();
-
-if (isset($_POST['redir']) && strpos($_POST['redir'],'://') === false)
-{
-	$redir = $_POST['redir'];
-}
-else
-{
-	$redir =
-	'posts.php?user_id='.$_POST['user_id'].
-	'&cat_id='.$_POST['cat_id'].
-	'&status='.$_POST['status'].
-	'&selected='.$_POST['selected'].
-	'&month='.$_POST['month'].
-	'&lang='.$_POST['lang'].
-	'&sortby='.$_POST['sortby'].
-	'&order='.$_POST['order'].
-	'&page='.$_POST['page'].
-	'&nb='.$_POST['nb'];
-}
-$redir_sel = $redir;
-
-if (!empty($_POST['entries']))
-{
-	$entries = $_POST['entries'];
-	
-	foreach ($entries as $k => $v) {
-		$entries[$k] = (integer) $v;
-	}
-	
-	$params['sql'] = 'AND P.post_id IN('.implode(',',$entries).') ';
-	
-	if (!isset($_POST['full_content']) || empty($_POST['full_content'])) {
-		$params['no_content'] = true;
-	}
-	
-	if (isset($_POST['post_type'])) {
-		$params['post_type'] = $_POST['post_type'];
-	}
-	
-	$posts = $core->blog->getPosts($params);
-	while ($posts->fetch())	{
-		$posts_ids[] = $posts->post_id;
-		$fields->addEntry($posts->post_id,$posts->post_title);
-	}
-	// Redirection including selected entries
-	$redir_sel = $redir.'&'.$fields->getEntriesQS();
-
-} else {
-	$posts = $core->con->select("SELECT blog_id FROM ".$core->prefix."blog WHERE false");;
-}
-
-/* Actions
--------------------------------------------------------- */
-if (!empty($_POST['action']))
-{
-	$action = $_POST['action'];
-} 
-else
-{
-	$core->error->add(__('No action specified.'));
-	dcPage::open(
-		__('Entries'),'',dcPage::breadcrumb(
-		array(
-			html::escapeHTML($core->blog->name) => '',
-			__('Entries') => 'posts.php',
-			'<span class="page-title">'.__('Entries actions').'</span>' => ''
-		))
-	);
-	
-	echo '<p><a class="back" href="'.html::escapeURL($redir_sel).'">'.__('Back to entries list').'</a></p>';
-
-	dcPage::close();
-	exit;
-}
-
-# --BEHAVIOR-- adminPostsActions
-$core->callBehavior('adminPostsActions',$core,$posts,$action,$redir);
-
-if (preg_match('/^(publish|unpublish|schedule|pending)$/',$action))
-{
-	switch ($action) {
-		case 'unpublish' : $status = 0; break;
-		case 'schedule' : $status = -1; break;
-		case 'pending' : $status = -2; break;
-		default : $status = 1; break;
-	}
-	
-	try
-	{
-		$core->blog->updPostsStatus($posts_ids,$status);
-		
-		http::redirect($redir_sel.'&upd=1');
-	}
-	catch (Exception $e)
-	{
-		$core->error->add($e->getMessage());
-	}
-}
-elseif ($action == 'selected' || $action == 'unselected')
-{
-	try
-	{
-		$core->blog->updPostsSelected($posts_ids,$action == 'selected');
-		
-		http::redirect($redir_sel."&upd=1");
-	}
-	catch (Exception $e)
-	{
-		$core->error->add($e->getMessage());
-	}
-}
-elseif ($action == 'delete')
-{
-	try
-	{
-		// Backward compatibility
-		foreach($posts_ids as $post_id)
-		{
-			# --BEHAVIOR-- adminBeforePostDelete
-			$core->callBehavior('adminBeforePostDelete',(integer) $post_id);
-		}
-		
-		# --BEHAVIOR-- adminBeforePostsDelete
-		$core->callBehavior('adminBeforePostsDelete',$posts_ids);
-		
-		$core->blog->delPosts($posts_ids);
-		
-		http::redirect($redir."&del=1");
-	}
-	catch (Exception $e)
-	{
-		$core->error->add($e->getMessage());
-	}
-	
-}
-elseif ($action == 'category' && isset($_POST['new_cat_id']))
-{
-	$new_cat_id = $_POST['new_cat_id'];
-	
-	try
-	{
-		if (!empty($_POST['new_cat_title']) && $core->auth->check('categories', $core->blog->id))
-		{
-			$cur_cat = $core->con->openCursor($core->prefix.'category');
-			$cur_cat->cat_title = $_POST['new_cat_title'];
-			$cur_cat->cat_url = '';
-			
-			$parent_cat = !empty($_POST['new_cat_parent']) ? $_POST['new_cat_parent'] : '';
-			
-			# --BEHAVIOR-- adminBeforeCategoryCreate
-			$core->callBehavior('adminBeforeCategoryCreate', $cur_cat);
-			
-			$new_cat_id = $core->blog->addCategory($cur_cat, (integer) $parent_cat);
-			
-			# --BEHAVIOR-- adminAfterCategoryCreate
-			$core->callBehavior('adminAfterCategoryCreate', $cur_cat, $new_cat_id);
-		}
-		
-		$core->blog->updPostsCategory($posts_ids, $new_cat_id);
-		
-		http::redirect($redir_sel."&upd=1");
-	}
-	catch (Exception $e)
-	{
-		$core->error->add($e->getMessage());
-	}
-}
-elseif ($action == 'author' && isset($_POST['new_auth_id'])
-&& $core->auth->check('admin',$core->blog->id))
-{
-	$new_user_id = $_POST['new_auth_id'];
-	
-	try
-	{
-		if ($core->getUser($new_user_id)->isEmpty()) {
-			throw new Exception(__('This user does not exist'));
-		}
-		
-		$cur = $core->con->openCursor($core->prefix.'post');
-		$cur->user_id = $new_user_id;
-		$cur->update('WHERE post_id '.$core->con->in($posts_ids));
-		
-		http::redirect($redir_sel."&upd=1");
-	}
-	catch (Exception $e)
-	{
-		$core->error->add($e->getMessage());
-	}
-}
-elseif ($action == 'lang' && isset($_POST['new_lang']))
-{
-	$new_lang = $_POST['new_lang'];
-	try
-	{
-		$cur = $core->con->openCursor($core->prefix.'post');
-		$cur->post_lang = $new_lang;
-		$cur->update('WHERE post_id '.$core->con->in($posts_ids));
-		
-		http::redirect($redir_sel."&upd=1");
-	}
-	catch (Exception $e)
-	{
-		$core->error->add($e->getMessages());
-	}
-}
-
-/* DISPLAY
--------------------------------------------------------- */
-// Get current users list
-$usersList = '';
-if ($action == 'author' && $core->auth->check('admin',$core->blog->id)) {
-	$params = array(
-		'limit' => 100,
-		'order' => 'nb_post DESC'
-		);
-	$rs = $core->getUsers($params);
-	while ($rs->fetch())
-	{
-		$usersList .= ($usersList != '' ? ',' : '').'"'.$rs->user_id.'"';
-	}
-}
-dcPage::open(
-	__('Entries'),
-	'<script type="text/javascript">'."\n".
-	"//<![CDATA[\n".
-	'usersList = ['.$usersList.']'."\n".
-	"\n//]]>\n".
-	"</script>\n".
-	dcPage::jsLoad('js/jquery/jquery.autocomplete.js').
-	dcPage::jsLoad('js/_posts_actions.js').
-	dcPage::jsMetaEditor().
-	# --BEHAVIOR-- adminBeforePostDelete
-	$core->callBehavior('adminPostsActionsHeaders')
-);
-
-if (!isset($action)) {
-	dcPage::close();
-	exit;
-}
-
-if (isset($_POST['redir']) && strpos($_POST['redir'],'://') === false)
-{
-	$fields->addHidden(array('redir'),html::escapeURL($_POST['redir']));
-}
-else
-{
-	$fields
-		->addHidden(array('user_id'),$_POST['user_id'])
-		->addHidden(array('cat_id'),$_POST['cat_id'])
-		->addHidden(array('status'),$_POST['status'])
-		->addHidden(array('selected'),$_POST['selected'])
-		->addHidden(array('month'),$_POST['month'])
-		->addHidden(array('lang'),$_POST['lang'])
-		->addHidden(array('sortby'),$_POST['sortby'])
-		->addHidden(array('order'),$_POST['order'])
-		->addHidden(array('page'),$_POST['page'])
-		->addHidden(array('nb'),$_POST['nb'])
-	;
-}
-
-if (isset($_POST['post_type'])) {
-	$fields->addHidden(array('post_type'),$_POST['post_type']);
-}
-
-# --BEHAVIOR-- adminPostsActionsContent
-$core->callBehavior('adminPostsActionsContent',$core,$action,$fields);
-
-if ($action == 'category')
-{
-	echo dcPage::breadcrumb(
-		array(
-			html::escapeHTML($core->blog->name) => '',
-			__('Entries') => 'posts.php',
-			'<span class="page-title">'.__('Change category for this selection').'</span>' => ''
-	));
-	
-	echo '<p><a class="back" href="'.html::escapeURL($redir_sel).'">'.__('Back to entries list').'</a></p>';
-
-	# categories list
-	# Getting categories
-	$categories_combo = dcAdminCombos::getCategoriesCombo(
-		$core->blog->getCategories(array('post_type'=>'post'))
-	);
-	
-	echo
-	'<form action="posts_actions.php" method="post">'.
-	$fields->getEntries().
-	'<p><label for="new_cat_id" class="classic">'.__('Category:').'</label> '.
-	form::combo('new_cat_id',$categories_combo,'');
-	
-	if ($core->auth->check('categories', $core->blog->id)) {
-		echo 
-		'<div>'.
-		'<p id="new_cat">'.__('Create a new category for the post(s)').'</p>'.
-		'<p><label for="new_cat_title">'.__('Title:').'</label> '.
-		form::field('new_cat_title',30,255,'','').'</p>'.
-		'<p><label for="new_cat_parent">'.__('Parent:').'</label> '.
-		form::combo('new_cat_parent',$categories_combo,'','').
-		'</p>'.
-		'</div>';
-	}
-	
-	echo
-	$fields->getHidden().
-	$core->formNonce().
-	form::hidden(array('action'),'category').
-	'<input type="submit" value="'.__('Save').'" /></p>'.
-	'</form>';
-}
-elseif ($action == 'lang')
-{
-	echo dcPage::breadcrumb(
-		array(
-			html::escapeHTML($core->blog->name) => '',
-			__('Entries') => 'posts.php',
-			'<span class="page-title">'.__('Change language for this selection').'</span>' => ''
-	));
-	echo '<p><a class="back" href="'.html::escapeURL($redir_sel).'">'.__('Back to entries list').'</a></p>';
-
-	# lang list
-	# Languages combo
-	$rs = $core->blog->getLangs(array('order'=>'asc'));
-	$lang_combo = dcAdminCombos::getLangsCombo($rs,true);
-	
-	echo
-	'<form action="posts_actions.php" method="post">'.
-	$fields->getEntries().
-	
-	'<p><label for="new_lang" class="classic">'.__('Entry language:').'</label> '.
-	form::combo('new_lang',$lang_combo,'');
-	
-	echo
-	$fields->getHidden().
-	$core->formNonce().
-	form::hidden(array('action'),'lang').
-	'<input type="submit" value="'.__('Save').'" /></p>'.
-	'</form>';
-
-}
-elseif ($action == 'author' && $core->auth->check('admin',$core->blog->id))
-{
-	echo dcPage::breadcrumb(
-		array(
-			html::escapeHTML($core->blog->name) => '',
-			__('Entries') => 'posts.php',
-			'<span class="page-title">'.__('Change author for this selection').'</span>' => ''
-	));
-	echo '<p><a class="back" href="'.html::escapeURL($redir_sel).'">'.__('Back to entries list').'</a></p>';
-
-	echo
-	'<form action="posts_actions.php" method="post">'.
-	$fields->getEntries().
-	'<p><label for="new_auth_id" class="classic">'.__('New author (author ID):').'</label> '.
-	form::field('new_auth_id',20,255);
-	
-	echo
-	$fields->getHidden().
-	$core->formNonce().
-	form::hidden(array('action'),'author').
-	'<input type="submit" value="'.__('Save').'" /></p>'.
-	'</form>';
-}
-
-dcPage::close();
 ?>
Index: inc/admin/actions/class.dcaction.php
===================================================================
--- inc/admin/actions/class.dcaction.php	(revision 1905)
+++ inc/admin/actions/class.dcaction.php	(revision 1905)
@@ -0,0 +1,318 @@
+<?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; }
+
+/**
+* dcActionsPage -- handler for action page on selected entries
+*
+*/
+abstract class dcActionsPage
+{
+	/** @var string form submit uri */
+	protected $uri;
+	/** @var dcCore dotclear core instance */
+	protected $core;
+	/** @var array action combo box */
+	protected $combo;
+	/** @var array list of defined actions (callbacks) */
+	protected $actions;
+	/** @var array selected entries (each key is the entry id, value contains the entry description) */
+	protected $entries;
+	/** @var record record that challenges ids against permissions */
+	protected $rs;
+	/** @var array redirection $_GET arguments, if any (does not contain ids by default, ids may be merged to it) */
+	protected $redir_args;
+	/** @var array list of $_POST fields used to build the redirection  */
+	protected $redirect_fields;
+	/** @var string current action, if any */
+	protected $action;
+	/** @var array list of url parameters (usually $_POST) */
+	protected $from;
+	/** @var string form field name for "entries" (usually "entries") */
+	protected $field_entries;
+	
+	/** @var string title for checkboxes list, if displayed */
+	protected $cb_title;
+	
+    /**
+     * Class constructor
+     * 
+     * @param mixed  $core   dotclear core
+     * @param mixed  $uri   form uri
+     *
+     * @access public
+     *
+     * @return mixed Value.
+     */
+	public function __construct($core,$uri,$redirect_args=array()) {
+		$this->core = $core;
+		$this->actions = new ArrayObject();
+		$this->combo = array();
+		$this->uri = $uri;
+		$this->redir_args = $redirect_args;
+		$this->redirect_fields = array();
+		$this->action = '';
+		$this->cb_title = __('Title');
+		$this->entries = array();
+		$this->from = new ArrayObject($_POST);
+		$this->field_entries = 'entries';
+	}
+	
+    /**
+     * addAction - adds an action
+     *
+     * @param string $actions the actions names as if it was a standalone combo array.
+	 * 						  It will be merged with other actions.
+	 * 						  Can be bound to multiple values, if the same callback is to be called
+     * @param callback $callback the callback for the action.
+     *
+     * @access public
+	 *
+     * @return dcActionsPage the actions page itself, enabling to chain addAction().
+     */
+	public function addAction ($actions,$callback) {
+		foreach ($actions as $k => $a) {
+			// Check each case of combo definition
+			// Store form values in $values
+			if (is_array($a)) {
+				$values = array_values($a);
+				if (!isset($this->combo[$k])) {
+					$this->combo[$k]=array();
+				}
+				$this->combo[$k] = array_merge ($this->combo[$k],$a);
+			} elseif ($a instanceof formSelectOption) {
+				$values = array($a->value);
+				$this->combo[$k] = $a->value;
+			} else {
+				$values = array($a);
+				$this->combo[$k] = $a;
+			}
+			// Associate each potential value to the callback
+			foreach ($values as $v) {
+				$this->actions[$v]=$callback;
+			}
+		}
+		return $this;
+	}
+	
+    /**
+     * getCombo - returns the actions combo, useable through form::combo
+     *
+     * @access public
+	 *
+     * @return array the actions combo
+     */
+	public function getCombo() {
+		return $this->combo;
+	}
+	
+	
+    /**
+     * getIDS() - returns the list of selected entries
+     *
+     * @access public
+	 *
+     * @return array the list
+     */
+	public function getIDs() {
+		return array_keys($this->entries);
+	}
+	
+    /**
+     * getIDS() - returns the list of selected entries as HTML hidden fields string
+     *
+     * @access public
+	 *
+     * @return string the HTML code for hidden fields
+     */
+	 public function getIDsHidden() {
+		$ret = '';
+		foreach  ($this->entries as $id->$v) {
+			$ret .= form::hidden($this->field_entries.'[]',$id);
+		}
+		return $ret;
+	}
+	
+    /**
+     * getHiddenFields() - returns all redirection parameters as HTML hidden fields
+     *
+     * @param boolean $with_ids if true, also include ids in HTML code
+	 *
+     * @access public
+	 *
+     * @return string the HTML code for hidden fields
+     */	
+	public function getHiddenFields($with_ids = false) {
+		$ret = '';
+		foreach ($this->redir_args as $k => $v) {
+			$ret .= form::hidden(array($k),$v);
+		}
+		if ($with_ids) {
+			$ret .= $this->getIDsHidden();
+		}
+		return $ret;
+	}
+	
+	
+	/**
+     * getRS() - get record from DB Query containing requested IDs
+     *
+     * @param boolean $with_ids if true, also include ids in HTML code
+	 *
+     * @access public
+	 *
+     * @return string the HTML code for hidden fields
+     */
+	public function getRS() {
+		return $this->rs;
+	}
+	
+	/**
+     * setupRedir - setup redirection arguments
+	 *  by default, $_POST fields as defined in redirect_fields attributes
+	 *  are set into redirect_args.
+     *
+     * @param array $from input to parse fields from (usually $_POST)
+	 *
+     * @access protected
+     */
+	protected function setupRedir($from) {
+		foreach ($this->redirect_fields as $p) {
+			if (isset($from[$p])) {
+				$redir_args[$p] = $from[$p];
+			}
+		}
+	}
+
+	/**
+     * getRedirection - returns redirection URL
+     *
+     * @param array $params extra parameters to append to redirection
+	 *						must be an array : each key is the name, 
+	 *						each value is the wanted value
+     * @param boolean $with_selected_entries if true, add selected entries in url
+	 *
+     * @access public
+	 *
+     * @return string the redirection url
+     */
+	public function getRedirection($params=array(),$with_selected_entries=false) {
+		$redir_args = array_merge($params,$this->redir_args);
+		if ($with_selected_entries) {
+			$redir_args[$this->field_entries] = array_keys($this->entries);
+		}
+		return $this->uri.'?'.http_build_query($redir_args);
+	}
+
+	/**
+     * redirect - redirects to redirection page
+     *
+	 * @see getRedirection for arguments details
+	 *
+     * @access public
+     */
+	public function redirect($params=array(),$with_selected_entries=false) {
+		http::redirect($this->getRedirection($params,$with_selected_entries));
+	}	
+	
+	/**
+     * getAction - returns current action, if any
+     *
+	 * @see getRedirection for arguments details
+	 *
+     * @access public
+	 *
+     * @return string the action
+     */
+	public function getAction() {
+		return $this->action;
+	}
+
+	/**
+     * process - proceeds action handling, if any
+	 * 			this method may issue an exit() if
+	 *			an action is being processed. If it
+	 *			returns, no action has been performed
+     *
+     * @access public
+     */
+	public function process() {
+
+		$this->setupRedir($this->from);
+		$this->fetchEntries($this->from);	
+		if (isset($this->from['action'])) {
+			$this->action = $this->from['action'];
+			try {
+				$performed=false;
+				foreach ($this->actions as $k=>$v) {
+					if ($this->from['action']==$k) {
+						$performed = true;
+						call_user_func($v,$this->core,$this,$this->from);
+					}
+				}
+				if ($performed) {
+					exit;
+				}
+			} catch (Exception $e) {
+				$this->error($e);
+			}
+		}
+	}
+
+	/**
+     * getcheckboxes -returns html code for selected entries
+	 * 			as a table containing entries checkboxes
+     *
+     * @access public
+	 *
+     * @return string the html code for checkboxes
+     */
+	public function getCheckboxes() {
+		$ret = 
+			'<table class="posts-list"><tr>'.
+			'<th colspan="2">'.$this->cb_title.'</th>'.
+			'</tr>';
+		foreach ($this->entries as $id=>$title) {
+			$ret .= 
+				'<tr><td>'.
+				form::checkbox(array($this->field_entries.'[]'),$id,true,'','').'</td>'.
+				'<td>'.	$title.'</td></tr>';
+		}
+		$ret .= '</table>';
+		return $ret;
+	}
+	
+	/**
+     * beginPage, endPage - displays the beginning/ending of a page, if action does not redirects dirtectly
+     *
+	 * These methods are called from the actions themselves.
+	 *
+     * @param string $breadcrumb breadcrumb to display
+     * @param string $head	page header to include
+	 *
+     * @access public
+     */
+	abstract public function beginPage($breadcrumb='',$head='');
+	abstract public function endPage();
+
+	/**
+     * fetchEntries - fills-in information by requesting into db
+	 * 	this method may setup the following attributes
+	 *   * entries : list of entries (checked against permissions)
+	 *      entries ids are array keys, values contain entry description (if relevant)
+     *   * rs : record given by db request
+     * @access protected
+     */
+	 abstract protected function fetchEntries($from);
+
+}
+
Index: inc/admin/actions/class.dcactioncomments.php
===================================================================
--- inc/admin/actions/class.dcactioncomments.php	(revision 1905)
+++ inc/admin/actions/class.dcactioncomments.php	(revision 1905)
@@ -0,0 +1,140 @@
+<?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 dcCommentsActionsPage extends dcActionsPage
+{
+	public function __construct($core,$uri,$redirect_args=array()) {
+		parent::__construct($core,$uri,$redirect_args);
+		$this->redirect_fields = array('type','author','status',
+			'sortby','ip','order','page','nb');
+		$this->field_entries = 'comments';
+		$this->title_cb = __('Comments');
+		$this->loadDefaults();
+		$core->callBehavior('adminCommentsActionsPage',$core,$this);
+	}
+
+	protected function loadDefaults() {
+		// We could have added a behavior here, but we want default action
+		// to be setup first
+		dcDefaultCommentActions::adminCommentsActionsPage($this->core,$this);
+	}
+	
+	public function beginPage($breadcrumb='',$head='') {
+		dcPage::open(
+			__('Comments'),
+			
+			dcPage::jsLoad('js/_comments_actions.js').
+			$head,
+			$breadcrumb
+		);	
+		echo '<p><a class="back" href="'.$this->getRedirection(array(),true).'">'.__('Back to entries list').'</a></p>';
+	}
+	
+	public function endPage() {
+		dcPage::close();
+	}
+
+	public function error(Exception $e) {
+		$this->core->error->add($e->getMessage());
+		$this->beginPage(dcPage::breadcrumb(
+			array(
+				html::escapeHTML($this->core->blog->name) => '',
+				__('Comments') => 'comments.php',
+				'<span class="page-title">'.__('Comments actions').'</span>' => ''
+			))
+		);
+		$this->endPage();
+	}
+	
+	protected function fetchEntries($from) {
+		if (!empty($from['comments'])) {
+			$comments = $from['comments'];
+			
+			foreach ($comments as $k => $v) {
+				$comments[$k] = (integer) $v;
+			}
+			
+			$params['sql'] = 'AND C.comment_id IN('.implode(',',$comments).') ';
+			
+			if (!isset($from['full_content']) || empty($from['full_content'])) {
+				$params['no_content'] = true;
+			}
+			
+			$co = $this->core->blog->getComments($params);
+			while ($co->fetch())	{
+				$this->entries[$co->comment_id] = array(
+					'title' => $co->post_title,
+					'author' => $co->comment_author
+				);
+			}
+			$this->rs = $co;
+		}
+	}
+}
+
+class dcDefaultCommentActions 
+{
+	public static function adminCommentsActionsPage($core, dcCommentsActionsPage $ap) {
+		if ($core->auth->check('publish,contentadmin',$core->blog->id))
+		{
+			$action = array('dcDefaultCommentActions','doChangeCommentStatus');
+			$ap->addAction (array(__('Publish') => 'publish'), $action);
+			$ap->addAction (array(__('Publish') => 'publish'), $action);
+			$ap->addAction (array(__('Unpublish') => 'unpublish'), $action);
+			$ap->addAction (array(__('Mark as pending') => 'pending'), $action);
+			$ap->addAction(array(__('Mark as junk') => 'junk'), $action);
+		}
+	
+		if ($core->auth->check('delete,contentadmin',$core->blog->id))
+		{
+			$ap->addAction(array(__('Delete') => 'delete'),
+				array('dcDefaultCommentActions','doDeleteComment'));
+		}
+	}
+
+	public static function doChangeCommentStatus($core, dcCommentsActionsPage $ap, $post) {
+		$action = $ap->getAction();
+		$co_ids = $ap->getIDs();
+		if (empty($co_ids)) {
+			throw new Exception(__('No comment selected'));
+		}
+		switch ($action) {
+			case 'unpublish' : $status = 0; break;
+			case 'pending' : $status = -1; break;
+			case 'junk' : $status = -2; break;
+			default : $status = 1; break;
+		}
+		
+		$core->blog->updCommentsStatus($co_ids,$status);
+		$ap->redirect(array('upd'=>1),true);
+	}
+
+	public static function doDeleteComment($core, dcCommentsActionsPage $ap, $post) {
+		$co_ids = $ap->getIDs();
+		if (empty($co_ids)) {
+			throw new Exception(__('No comment selected'));
+		}
+		// Backward compatibility
+		foreach($co_ids as $comment_id)
+		{
+			# --BEHAVIOR-- adminBeforeCommentDelete
+			$core->callBehavior('adminBeforeCommentDelete',$comment_id);				
+		}
+		
+		# --BEHAVIOR-- adminBeforeCommentsDelete
+		$core->callBehavior('adminBeforeCommentsDelete',$co_ids);
+		
+		$core->blog->delComments($co_ids);
+		$ap->redirect(array('del'=>1), false);
+	}
+}
Index: inc/admin/actions/class.dcactionposts.php
===================================================================
--- inc/admin/actions/class.dcactionposts.php	(revision 1903)
+++ inc/admin/actions/class.dcactionposts.php	(revision 1903)
@@ -0,0 +1,361 @@
+<?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 dcPostsActionsPage extends dcActionsPage
+{
+	public function __construct($core,$uri,$redirect_args=array()) {
+		parent::__construct($core,$uri,$redirect_args);
+		$this->redirect_fields = array('user_id','cat_id','status',
+		'selected','month','lang','sortby','order','page','nb');
+		$this->loadDefaults();
+		$core->callBehavior('adminPostsActionsPage',$core,$this);
+	}
+
+	protected function loadDefaults() {
+		// We could have added a behavior here, but we want default action
+		// to be setup first
+		dcDefaultPostActions::adminPostsActionsPage($this->core,$this);
+	}
+	
+	public function beginPage($breadcrumb='',$head='') {
+		dcPage::open(
+			__('Entries'),
+			
+			dcPage::jsLoad('js/_posts_actions.js').
+			$head,
+			$breadcrumb
+		);	
+		echo '<p><a class="back" href="'.$this->getRedirection(array(),true).'">'.__('Back to entries list').'</a></p>';
+	}
+	
+	public function endPage() {
+		dcPage::close();
+	}
+	
+	public function error(Exception $e) {
+		$this->core->error->add($e->getMessage());
+		$this->beginPage(dcPage::breadcrumb(
+			array(
+				html::escapeHTML($this->core->blog->name) => '',
+				__('Entries') => 'posts.php',
+				'<span class="page-title">'.__('Entries actions').'</span>' => ''
+			))
+		);
+		$this->endPage();
+	}
+	
+	protected function fetchEntries($from) {
+		if (!empty($from['entries']))
+		{
+			$entries = $from['entries'];
+			
+			foreach ($entries as $k => $v) {
+				$entries[$k] = (integer) $v;
+			}
+			
+			$params['sql'] = 'AND P.post_id IN('.implode(',',$entries).') ';
+			
+			if (!isset($from['full_content']) || empty($from['full_content'])) {
+				$params['no_content'] = true;
+			}
+			
+			if (isset($from['post_type'])) {
+				$params['post_type'] = $from['post_type'];
+			}
+			
+			$posts = $this->core->blog->getPosts($params);
+			while ($posts->fetch())	{
+				$this->entries[$posts->post_id] = $posts->post_title;
+			}
+			$this->rs = $posts;			
+		} else {
+			$this->rs = $this->core->con->select("SELECT blog_id FROM ".$this->core->prefix."blog WHERE false");;
+		}
+	}
+}
+
+class dcDefaultPostActions 
+{
+	public static function adminPostsActionsPage($core, dcPostsActionsPage $ap) {
+		if ($core->auth->check('publish,contentadmin',$core->blog->id)) {
+			$ap->addAction(
+				array(__('Status') => array(
+					__('Publish') => 'publish',
+					__('Unpublish') => 'unpublish',
+					__('Schedule') => 'schedule',
+					__('Mark as pending') => 'pending'
+				)),
+				array('dcDefaultPostActions','doChangePostStatus')
+			);
+		}
+		$ap->addAction(
+			array(__('Mark')=> array(
+				__('Mark as selected') => 'selected',
+				__('Mark as unselected') => 'unselected'
+			)),
+			array('dcDefaultPostActions','doUpdateSelectedPost')
+		);
+		$ap->addAction(
+			array(__('Change') => array(
+				__('Change category') => 'category',
+			)),
+			array('dcDefaultPostActions','doChangePostCategory')
+		);
+		$ap->addAction(
+			array(__('Change') => array(
+				__('Change language') => 'lang',
+			)),
+			array('dcDefaultPostActions','doChangePostLang')
+		);
+		if ($core->auth->check('admin',$core->blog->id))
+		{
+			$ap->addAction(
+				array(__('Change') => array(
+					__('Change author') => 'author')),
+				array('dcDefaultPostActions','doChangePostAuthor')
+			);
+		}
+		if ($core->auth->check('delete,contentadmin',$core->blog->id)) {
+			$ap->addAction(
+				array(__('Delete') => array(
+					__('Delete') => 'delete')),
+				array('dcDefaultPostActions','doDeletePost')
+			);
+		}
+	}
+
+	public static function doChangePostStatus($core, dcPostsActionsPage $ap, $post) {
+		switch ($ap->getAction()) {
+			case 'unpublish' : $status = 0; break;
+			case 'schedule' : $status = -1; break;
+			case 'pending' : $status = -2; break;
+			default : $status = 1; break;
+		}
+		$posts_ids = $ap->getIDs();
+		if (empty($posts_ids)) {
+			throw new Exception(__('No entry selected'));
+		}
+		$core->blog->updPostsStatus($posts_ids,$status);
+		
+		$ap->redirect(array('upd' => 1),true);
+	}
+	
+	public static function doUpdateSelectedPost($core, dcPostsActionsPage $ap, $post) {
+		$posts_ids = $ap->getIDs();
+		if (empty($posts_ids)) {
+			throw new Exception(__('No entry selected'));
+		}
+		$action = $ap->getAction();
+		$core->blog->updPostsSelected($posts_ids,$action == 'selected');
+		
+		$ap->redirect(array('upd' => 1),true);
+	}
+	
+	public static function doDeletePost($core, dcPostsActionsPage $ap, $post) {
+
+		$posts_ids = $ap->getIDs();
+		if (empty($posts_ids)) {
+			throw new Exception(__('No entry selected'));
+		}
+		// Backward compatibility
+		foreach($posts_ids as $post_id)
+		{
+			# --BEHAVIOR-- adminBeforePostDelete
+			$core->callBehavior('adminBeforePostDelete',(integer) $post_id);
+		}
+		
+		# --BEHAVIOR-- adminBeforePostsDelete
+		$core->callBehavior('adminBeforePostsDelete',$posts_ids);
+		
+		$core->blog->delPosts($posts_ids);
+		
+		$ap->redirect(array('del',1),false);
+	}
+
+	public static function doChangePostCategory($core, dcPostsActionsPage $ap, $post) {
+		if (isset($post['new_cat_id'])) {
+			$posts_ids = $ap->getIDs();
+			if (empty($posts_ids)) {
+				throw new Exception(__('No entry selected'));
+			}
+			$new_cat_id = $post['new_cat_id'];
+			if (!empty($post['new_cat_title']) && $core->auth->check('categories', $core->blog->id))
+			{
+				$cur_cat = $core->con->openCursor($core->prefix.'category');
+				$cur_cat->cat_title = $post['new_cat_title'];
+				$cur_cat->cat_url = '';
+				
+				$parent_cat = !empty($post['new_cat_parent']) ? $post['new_cat_parent'] : '';
+				
+				# --BEHAVIOR-- adminBeforeCategoryCreate
+				$core->callBehavior('adminBeforeCategoryCreate', $cur_cat);
+				
+				$new_cat_id = $core->blog->addCategory($cur_cat, (integer) $parent_cat);
+				
+				# --BEHAVIOR-- adminAfterCategoryCreate
+				$core->callBehavior('adminAfterCategoryCreate', $cur_cat, $new_cat_id);
+			}
+			
+			$core->blog->updPostsCategory($posts_ids, $new_cat_id);
+			
+			$ap->redirect(array('upd'=>1),true);
+		} else {
+			$ap->beginPage(
+				dcPage::breadcrumb(
+					array(
+						html::escapeHTML($core->blog->name) => '',
+						__('Entries') => 'posts.php',
+						'<span class="page-title">'.__('Change category for entries').'</span>' => ''
+			)));
+			
+			# categories list
+			# Getting categories
+			$categories_combo = dcAdminCombos::getCategoriesCombo(
+				$core->blog->getCategories(array('post_type'=>'post'))
+			);			
+			echo
+			'<form action="posts.php" method="post">'.
+			$ap->getCheckboxes().
+			'<p><label for="new_cat_id" class="classic">'.__('Category:').'</label> '.
+			form::combo('new_cat_id',$categories_combo,'');
+			
+			if ($core->auth->check('categories', $core->blog->id)) {
+				echo 
+				'<div>'.
+				'<p id="new_cat">'.__('Create a new category for the post(s)').'</p>'.
+				'<p><label for="new_cat_title">'.__('Title:').'</label> '.
+				form::field('new_cat_title',30,255,'','').'</p>'.
+				'<p><label for="new_cat_parent">'.__('Parent:').'</label> '.
+				form::combo('new_cat_parent',$categories_combo,'','').
+				'</p>'.
+				'</div>';
+			}
+			
+			echo
+			$core->formNonce().
+			form::hidden(array('action'),'category').
+			'<input type="submit" value="'.__('Save').'" /></p>'.
+			'</form>';
+			$ap->endPage();
+
+		}
+	
+	}
+	public static function doChangePostAuthor($core, dcPostsActionsPage $ap, $post) {
+		if (isset($post['new_auth_id']) && $core->auth->check('admin',$core->blog->id)) {
+			$new_user_id = $post['new_auth_id'];
+			$posts_ids = $ap->getIDs();
+			if (empty($posts_ids)) {
+				throw new Exception(__('No entry selected'));
+			}
+			if ($core->getUser($new_user_id)->isEmpty()) {
+				throw new Exception(__('This user does not exist'));
+			}
+			
+			$cur = $core->con->openCursor($core->prefix.'post');
+			$cur->user_id = $new_user_id;
+			$cur->update('WHERE post_id '.$core->con->in($posts_ids));
+			
+			$ap->redirect(array('upd' => 1),true);
+		} else {
+			$usersList = '';
+			if ($core->auth->check('admin',$core->blog->id)) {
+				$params = array(
+					'limit' => 100,
+					'order' => 'nb_post DESC'
+					);
+				$rs = $core->getUsers($params);
+				while ($rs->fetch())
+				{
+					$usersList .= ($usersList != '' ? ',' : '').'"'.$rs->user_id.'"';
+				}
+			}
+			$ap->beginPage(
+				dcPage::breadcrumb(
+					array(
+						html::escapeHTML($core->blog->name) => '',
+						__('Entries') => 'posts.php',
+						'<span class="page-title">'.__('Change author for entries').'</span>' => '')),
+					dcPage::jsLoad('js/jquery/jquery.autocomplete.js').
+					'<script type="text/javascript">'."\n".
+					"//<![CDATA[\n".
+					'usersList = ['.$usersList.']'."\n".
+					"\n//]]>\n".
+					"</script>\n"
+			);
+
+			echo
+			'<form action="posts_actions.php" method="post">'.
+			$ap->getCheckboxes().
+			'<p><label for="new_auth_id" class="classic">'.__('New author (author ID):').'</label> '.
+			form::field('new_auth_id',20,255);
+			
+			echo
+				$core->formNonce().
+				form::hidden(array('action'),'author').
+				'<input type="submit" value="'.__('Save').'" /></p>'.
+				'</form>';
+			$ap->endPage();
+		}
+	}
+	public static function doChangePostLang($core, dcPostsActionsPage $ap, $post) {
+		$posts_ids = $ap->getIDs();
+		if (empty($posts_ids)) {
+			throw new Exception(__('No entry selected'));
+		}
+		if (isset($post['new_lang'])) {
+			$new_lang = $post['new_lang'];
+			$cur = $core->con->openCursor($core->prefix.'post');
+			$cur->post_lang = $new_lang;
+			$cur->update('WHERE post_id '.$core->con->in($posts_ids));
+			
+			$ap->redirect(array('upd' => 1),true);
+		} else {
+			$ap->beginPage(
+				dcPage::breadcrumb(
+					array(
+						html::escapeHTML($core->blog->name) => '',
+						__('Entries') => 'posts.php',
+						'<span class="page-title">'.__('Change language for entries').'</span>' => ''
+			)));
+			# lang list
+			# Languages combo
+			$rs = $core->blog->getLangs(array('order'=>'asc'));
+			$all_langs = l10n::getISOcodes(0,1);
+			$lang_combo = array('' => '', __('Most used') => array(), __('Available') => l10n::getISOcodes(1,1));
+			while ($rs->fetch()) {
+				if (isset($all_langs[$rs->post_lang])) {
+					$lang_combo[__('Most used')][$all_langs[$rs->post_lang]] = $rs->post_lang;
+					unset($lang_combo[__('Available')][$all_langs[$rs->post_lang]]);
+				} else {
+					$lang_combo[__('Most used')][$rs->post_lang] = $rs->post_lang;
+				}
+			}
+			unset($all_langs);
+			unset($rs);
+			
+			echo
+			'<form action="posts_actions.php" method="post">'.
+			$ap->getCheckboxes().
+			
+			'<p><label for="new_lang" class="classic">'.__('Entry lang:').'</label> '.
+			form::combo('new_lang',$lang_combo,'');
+			
+			echo
+				$core->formNonce().
+				form::hidden(array('action'),'lang').
+				'<input type="submit" value="'.__('Save').'" /></p>'.
+				'</form>';
+		}
+	}
+}
Index: inc/admin/lib.pager.php
===================================================================
--- inc/admin/lib.pager.php	(revision 1998)
+++ inc/admin/lib.pager.php	(revision 1999)
@@ -370,4 +370,10 @@
 			$pager = new dcPager($page,$this->rs_count,$nb_per_page,10);
 			
+			$comments = array();
+			if (isset($_REQUEST['comments'])) {
+				foreach ($_REQUEST['comments'] as $v) {
+					$comments[(integer)$v]=true;
+				}
+			}			
 			$html_block =
 			'<table><caption class="hidden">'.__('Comments and trackbacks list').'</caption><tr>'.
@@ -400,5 +406,5 @@
 	}
 	
-	private function commentLine()
+	private function commentLine($checked)
 	{
 		global $author, $status, $sortby, $order, $nb_per_page;
Index: inc/prepend.php
===================================================================
--- inc/prepend.php	(revision 1949)
+++ inc/prepend.php	(revision 1999)
@@ -65,4 +65,7 @@
 $__autoload['context']				= dirname(__FILE__).'/public/lib.tpl.context.php';
 $__autoload['dcUrlHandlers']			= dirname(__FILE__).'/public/lib.urlhandlers.php';
+$__autoload['dcPostsActionsPage']			= dirname(__FILE__).'/admin/actions/class.dcactionposts.php';
+$__autoload['dcCommentsActionsPage']			= dirname(__FILE__).'/admin/actions/class.dcactioncomments.php';
+$__autoload['dcActionsPage']			= dirname(__FILE__).'/admin/actions/class.dcaction.php';
 
 # Clearbricks extensions
Index: plugins/dclegacy/_admin.php
===================================================================
--- plugins/dclegacy/_admin.php	(revision 1905)
+++ plugins/dclegacy/_admin.php	(revision 1905)
@@ -0,0 +1,70 @@
+<?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; }
+
+$GLOBALS['core']->addBehavior('adminPostsActionsPage',array('dcLegacyPosts','adminPostsActionsPage'));
+$GLOBALS['core']->addBehavior('adminCommentsActionsPage',array('dcLegacyComments','adminCommentsActionsPage'));
+
+/* Handle deprecated behaviors : 
+    * adminPostsActionsCombo
+	* adminPostsActionsHeaders
+	* adminPostsActionsContent
+*/
+class dcLegacyPosts
+{
+	public static function adminPostsActionsPage($core, dcPostsActionsPage $as) {
+		$stub_actions = new ArrayObject();
+		$core->callBehavior('adminPostsActionsCombo',array($stub_actions));
+		if (!empty($stub_actions)) {
+			$as->addAction($stub_actions,array('dcLegacyPosts','onActionLegacy'));
+		}
+	}
+	
+	public static function onActionLegacy($core, dcPostsActionsPage $as, $post) {
+		$core->callBehavior('adminPostsActions',$core,$as->getRS(),$as->getAction(),$as->getRedirection());
+		$as->beginPage('',
+			dcPage::jsLoad('js/jquery/jquery.autocomplete.js').
+			dcPage::jsMetaEditor().
+			$core->callBehavior('adminPostsActionsHeaders'),'');
+		$core->callBehavior('adminPostsActionsContent',$core,$as->getAction(),$as->getHiddenFields(true));
+		$as->endPage();
+	
+	}
+}
+
+
+/* Handle deprecated behaviors : 
+    * adminCommentsActionsCombo
+	* adminCommentsActionsHeaders
+	* adminCommentsActionsContent
+*/
+class dcLegacyComments
+{
+	public static function adminCommentsActionsPage($core, dcCommentsActionsPage $as) {
+		$stub_actions = new ArrayObject();
+		$core->callBehavior('adminCommentsActionsCombo',array($stub_actions));
+		if (!empty($stub_actions)) {
+			$as->addAction($stub_actions,array('dcLegacyComments','onActionLegacy'));
+		}
+	}
+	
+	public static function onActionLegacy($core, dcCommentsActionsPage $as, $Comment) {
+		$core->callBehavior('adminCommentsActions',$core,$as->getRS(),$as->getAction(),$as->getRedirection());
+		$as->beginPage('',
+			dcPage::jsLoad('js/jquery/jquery.autocomplete.js').
+			dcPage::jsMetaEditor().
+			$core->callBehavior('adminCommentsActionsHeaders'),'');
+		$core->callBehavior('adminCommentsActionsContent',$core,$as->getAction(),$as->getHiddenFields(true));
+		$as->endPage();
+	
+	}
+}
Index: plugins/dclegacy/_define.php
===================================================================
--- plugins/dclegacy/_define.php	(revision 1806)
+++ plugins/dclegacy/_define.php	(revision 1806)
@@ -0,0 +1,23 @@
+<?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; }
+
+$this->registerModule(
+	/* Name */			"dcLegacy",
+	/* Description*/		"Legacy modules for dotclear",
+	/* Author */			"dc Team",
+	/* Version */			'1.0',
+	array(
+		'priority' =>		500
+	)
+);
+?>
Index: plugins/pages/index.php
===================================================================
--- plugins/pages/index.php	(revision 1179)
+++ plugins/pages/index.php	(revision 1903)
@@ -12,5 +12,5 @@
 if (!defined('DC_CONTEXT_ADMIN')) { return; }
 
-$act = (!empty($_REQUEST['act']) && $_REQUEST['act'] == 'page') ? 'page' : 'list';
+$act = !empty($_REQUEST['act']) ? $_REQUEST['act'] : 'list';
 
 
@@ -20,4 +20,2 @@
 	include dirname(__FILE__).'/list.php';
 }
-
-?>
Index: plugins/pages/list.js
===================================================================
--- plugins/pages/list.js	(revision 1884)
+++ plugins/pages/list.js	(revision 1884)
@@ -0,0 +1,160 @@
+dotclear.postExpander = function(line) {
+	var td = line.firstChild;
+	
+	var img = document.createElement('img');
+	img.src = dotclear.img_plus_src;
+	img.alt = dotclear.img_plus_alt;
+	img.className = 'expand';
+	$(img).css('cursor','pointer');
+	img.line = line;
+	img.onclick = function() { dotclear.viewPostContent(this,this.line); };
+	
+	td.insertBefore(img,td.firstChild);
+};
+
+dotclear.postsExpander = function(line,lines) {
+	var td = line.firstChild;
+
+	var img = document.createElement('img');
+	img.src = dotclear.img_plus_src;
+	img.alt = dotclear.img_plus_alt;
+	img.className = 'expand';
+	$(img).css('cursor','pointer');
+	img.lines = lines;
+	img.onclick = function() { dotclear.viewPostsContent(this,this.lines); };
+
+	td.insertBefore(img,td.firstChild);
+};
+
+dotclear.viewPostsContent = function(img,lines) {
+	
+	action = 'toggle';
+
+	if (img.alt == dotclear.img_plus_alt) {
+		img.src = dotclear.img_minus_src;
+		img.alt = dotclear.img_minus_alt;
+		action = 'open';
+	} else {
+		img.src = dotclear.img_plus_src;
+		img.alt = dotclear.img_plus_alt;
+		action = 'close';
+	}
+	
+	lines.each(function() {
+		var td = this.firstChild;
+		dotclear.viewPostContent(td.firstChild,td.firstChild.line,action);
+	});
+};
+
+dotclear.viewPostContent = function(img,line,action) {
+	
+	var action = action || 'toggle';
+	var postId = line.id.substr(1);
+	var tr = document.getElementById('pe'+postId);
+	
+	if ( !tr && ( action == 'toggle' || action == 'open' ) ) {
+		tr = document.createElement('tr');
+		tr.id = 'pe'+postId;
+		var td = document.createElement('td');
+		td.colSpan = 8;
+		td.className = 'expand';
+		tr.appendChild(td);
+		
+		img.src = dotclear.img_minus_src;
+		img.alt = dotclear.img_minus_alt;
+		
+		// Get post content
+		$.get('services.php',{f:'getPostById', id: postId, post_type: ''},function(data) {
+			var rsp = $(data).children('rsp')[0];
+			
+			if (rsp.attributes[0].value == 'ok') {
+				var post = $(rsp).find('post_display_content').text();
+				var post_excerpt = $(rsp).find('post_display_excerpt').text();
+				var res = '';
+				
+				if (post) {
+					if (post_excerpt) {
+						res += post_excerpt + '<hr />';
+					}
+					res += post;
+					$(td).append(res);
+				}
+			} else {
+				alert($(rsp).find('message').text());
+			}
+		});
+		
+		$(line).addClass('expand');
+		line.parentNode.insertBefore(tr,line.nextSibling);
+	}
+	else if (tr && tr.style.display == 'none' && ( action == 'toggle' || action == 'open' ) )
+	{
+		$(tr).css('display', 'table-row');
+		$(line).addClass('expand');
+		img.src = dotclear.img_minus_src;
+		img.alt = dotclear.img_minus_alt;
+	}
+	else if (tr && tr.style.display != 'none' && ( action == 'toggle' || action == 'close' ) )
+	{
+		$(tr).css('display', 'none');
+		$(line).removeClass('expand');
+		img.src = dotclear.img_plus_src;
+		img.alt = dotclear.img_plus_alt;
+	}
+	
+	parentTable = $(line).parents('table');
+	if( parentTable.find('tr.expand').length == parentTable.find('tr.line').length ) {
+		img = parentTable.find('tr:not(.line) th:first img');
+		img.attr('src',dotclear.img_minus_src);
+		img.attr('alt',dotclear.img_minus_alt);
+	}
+	
+	if( parentTable.find('tr.expand').length == 0 ) {
+		img = parentTable.find('tr:not(.line) th:first img');
+		img.attr('src',dotclear.img_plus_src);
+		img.attr('alt',dotclear.img_plus_alt);
+	}
+	
+};
+
+$(function() {
+
+	$('#pageslist tr.line').prepend('<td class="expander"></td>');
+	$('#form-entries tr:not(.line) th:first').attr('colspan',4);
+	$('#form-entries tr:not(.line)').each(function() {
+		dotclear.postsExpander(this,$('#form-entries tr.line'));
+	});
+	$('#pageslist tr.line').each(function() {
+		dotclear.postExpander(this);
+	});
+	$('.checkboxes-helpers').each(function() {
+		p = $('<p></p>');
+		$(this).prepend(p);
+		dotclear.checkboxesHelpers(p);
+	});
+	$('#pageslist td input[type=checkbox]').enableShiftClick();
+	
+	$("#pageslist tr.line td:not(.expander)").mousedown(function(){
+		$('#pageslist tr.line').each(function() {
+			var td = this.firstChild;
+			dotclear.viewPostContent(td.firstChild,td.firstChild.line,'close');
+		});
+		$('#pageslist tr:not(.line)').remove();
+	});
+	
+	$("#pageslist").sortable({
+		cursor:'move',
+		stop: function( event, ui ) {
+			$("#pageslist tr td input.position").each(function(i) {
+				$(this).val(i+1);
+			});
+		}
+	});
+	$("#pageslist tr").hover(function () {
+		$(this).css({'cursor':'move'});
+	}, function () {
+		$(this).css({'cursor':'auto'});
+	});
+	$("#pageslist tr td input.position").hide();
+	$("#pageslist tr td.handle").addClass('handler');
+});
Index: plugins/pages/list.php
===================================================================
--- plugins/pages/list.php	(revision 1926)
+++ plugins/pages/list.php	(revision 1999)
@@ -28,6 +28,6 @@
 			
 			$html_block =
-			'<table class="clear"><tr>'.
-			'<th colspan="2">'.__('Title').'</th>'.
+			'<table class="maximal dragable"><thead><tr>'.
+			'<th colspan="3">'.__('Title').'</th>'.
 			'<th>'.__('Date').'</th>'.
 			'<th>'.__('Author').'</th>'.
@@ -35,5 +35,5 @@
 			'<th>'.__('Trackbacks').'</th>'.
 			'<th>'.__('Status').'</th>'.
-			'</tr>%s</table>';
+			'</tr></thead><tbody id="pageslist">%s</tbody></table>';
 			
 			if ($enclose_block) {
@@ -47,7 +47,9 @@
 			echo $blocks[0];
 			
+			$count = 0;
 			while ($this->rs->fetch())
 			{
-				echo $this->postLine();
+				echo $this->postLine($count);
+				$count ++;
 			}
 			
@@ -58,5 +60,5 @@
 	}
 	
-	private function postLine()
+	private function postLine($count)
 	{
 		$img = '<img alt="%1$s" title="%1$s" src="images/%2$s" />';
@@ -97,4 +99,5 @@
 		
 		$res .=
+		'<td class="nowrap handle minimal">'.form::field(array('order['.$this->rs->post_id.']'),2,3,$count+1,'position','',false,'title="'.sprintf(__('position of %s'),html::escapeHTML($this->rs->post_title)).'"').'</td>'.
 		'<td class="nowrap">'.
 		form::checkbox(array('entries[]'),$this->rs->post_id,'','','',!$this->rs->isEditable(),'title="'.__('Select this page').'"').'</td>'.
@@ -138,20 +141,73 @@
 }
 
+class dcPagesActionsPage extends dcPostsActionsPage {
+
+	public function __construct($core,$uri,$redirect_args=array()) {
+		parent::__construct($core,$uri,$redirect_args);
+		$this->redirect_fields = array();
+
+	}
+	
+	public function beginPage($breadcrumb='',$header='') {
+		echo '<html><head><title>'.__('Pages').'</title>'.
+			dcPage::jsLoad('index.php?pf=pages/list.js').
+			# --BEHAVIOR-- adminBeforePostDelete
+			$core->callBehavior('adminPagesActionsHeaders').
+			'<script type="text/javascript">'.
+			'//<![CDATA['.
+			dcPage::jsVar('dotclear.msg.confirm_delete_posts',__("Are you sure you want to delete selected pages?")).
+			'//]]>'.
+			'</script></head><body>';
+	}
+	
+	public function endPage() {
+		echo '</body></html>';
+	}
+	public function loadDefaults() {
+		parent::loadDefaults();
+		unset ($this->combos[__('Mark')]);
+		unset ($this->actions['selected']);
+		unset ($this->actions['unselected']);
+		$this->actions['reorder']=array('dcPagesActionsPage','doReorderPages');
+	}
+	public function process() {
+		// fake action for pages reordering
+		if (!empty($this->from['reorder'])) {
+			$this->from['action']='reorder';
+		}
+		parent::process();
+	}
+	
+	public static function doReorderPages($core, dcPostsActionsPage $ap, $post) {
+		foreach($post['order'] as $post_id => $value) {
+			if (!$core->auth->check('publish,contentadmin',$core->blog->id))
+				throw new Exception(__('You are not allowed to change this entry status'));
+			
+			$strReq = "WHERE blog_id = '".$core->con->escape($core->blog->id)."' ".
+					"AND post_id ".$core->con->in($post_id);
+			
+			#If user can only publish, we need to check the post's owner
+			if (!$core->auth->check('contentadmin',$core->blog->id))
+				$strReq .= "AND user_id = '".$core->con->escape($core->auth->userID())."' ";
+			
+			$cur = $core->con->openCursor($core->prefix.'post');
+			
+			$cur->post_position = (integer) $value-1;
+			$cur->post_upddt = date('Y-m-d H:i:s');
+			
+			$cur->update($strReq);
+			$core->blog->triggerBlog();
+			
+		}
+		$ap->redirect(array('reo'=>1),false);
+	}	
+}
+
 # Actions combo box
-$combo_action = array();
-if ($core->auth->check('publish,contentadmin',$core->blog->id))
-{
-	$combo_action[__('Publish')] = 'publish';
-	$combo_action[__('Unpublish')] = 'unpublish';
-	$combo_action[__('Schedule')] = 'schedule';
-	$combo_action[__('Mark as pending')] = 'pending';
-}
-if ($core->auth->check('admin',$core->blog->id)) {
-	$combo_action[__('Change author')] = 'author';
-}
-if ($core->auth->check('delete,contentadmin',$core->blog->id))
-{
-	$combo_action[__('Delete')] = 'delete';
-}
+
+$pages_actions_page = new dcPagesActionsPage($core,'plugin.php',array('p'=>'pages'));
+
+$pages_actions_page->process();
+
 
 # --BEHAVIOR-- adminPagesActionsCombo
@@ -164,5 +220,8 @@
 <head>
   <title><?php echo __('Pages'); ?></title>
-  <script type="text/javascript" src="js/_posts_list.js"></script>
+  <?php
+  	echo dcPage::jsLoad('js/jquery/jquery-ui.custom.js').
+  	     dcPage::jsLoad('index.php?pf=pages/list.js');
+  ?>
   <script type="text/javascript">
   //<![CDATA[
@@ -180,4 +239,11 @@
 	));
 
+if (!empty($_GET['upd'])) {
+	dcPage::success(__('Selected pages have been successfully updated.'));
+} elseif (!empty($_GET['del'])) {
+	dcPage::success(__('Selected pages have been successfully deleted.'));
+} elseif (!empty($_GET['reo'])) {
+	dcPage::success(__('Selected pages have been successfully reordered.'));
+}
 echo
 '<p class="top-add"><a class="button add" href="'.$p_url.'&amp;act=page">'.__('New page').'</a></p>';
@@ -187,5 +253,5 @@
 	# Show pages
 	$post_list->display($page,$nb_per_page,
-	'<form action="posts_actions.php" method="post" id="form-entries">'.
+	'<form action="plugin.php" method="post" id="form-entries">'.
 	
 	'%s'.
@@ -195,10 +261,12 @@
 	
 	'<p class="col right"><label for="action" class="classic">'.__('Selected pages action:').'</label> '.
-	form::combo('action',$combo_action).
+	form::combo('action',$pages_actions_page->getCombo()).
 	'<input type="submit" value="'.__('ok').'" /></p>'.
 	form::hidden(array('post_type'),'page').
-	form::hidden(array('redir'),html::escapeHTML($_SERVER['REQUEST_URI'])).
+	form::hidden(array('p'),'pages').
+	'</div>'.
 	$core->formNonce().
-	'</div>'.
+	'<br class="clear"/>'.
+	'<input type="submit" value="'.__('Save categories order').'" name="reorder" class="clear"/>'.
 	'</form>');
 }
Index: plugins/tags/_admin.php
===================================================================
--- plugins/tags/_admin.php	(revision 1844)
+++ plugins/tags/_admin.php	(revision 1874)
@@ -236,9 +236,9 @@
 			)).
 			'<form action="'.$form_uri.'" method="post">'.
-			$hidden_fields->getEntries().
+			//$hidden_fields->getEntries().
 			'<div><label for="new_tags" class="area">'.__('Tags to add:').'</label> '.
 			form::textarea('new_tags',60,3).
 			'</div>'.
-			$hidden_fields->getHidden().
+			$hidden_fields.
 			$core->formNonce().
 			form::hidden(array('action'),'tags').
