Index: .hgignore
===================================================================
--- .hgignore	(revision 14)
+++ .hgignore	(revision 358)
@@ -1,3 +1,4 @@
 cache
+public
 inc/config.php
 \.htaccess
Index: .hgtags
===================================================================
--- .hgtags	(revision 369)
+++ .hgtags	(revision 279)
@@ -1,2 +1,1 @@
 ec077f3c6b20bc6f7f40a113acaff0220457a2c4 2.3.0
-4e0137ddb12474d62b88c4966d5895a391b6d382 2.3.1
Index: CHANGELOG
===================================================================
--- CHANGELOG	(revision 368)
+++ CHANGELOG	(revision 343)
@@ -1,3 +1,7 @@
-Dotclear 2.3.1 - 2001-06-14
+Dotclear 2.4.0 - ongoing dev
+===========================================================
+* handling of postgres non default schemas (db_prefix = 'schema.prefix')
+
+Dotclear 2.3.1 - ongoing dev
 ===========================================================
 * Updated makefile for cleaner distrib.
@@ -5,6 +9,4 @@
 * Misc JS & CSS cleaning.
 * Import/Export preferences-related bugfix.
-* Administrative mail address is now configurable.
-* Security: one minor fix and changes for two potential problems. Thanks to Jeremie Boutoille
 
 Dotclear 2.3.0 - 2011-05-16
Index: Makefile
===================================================================
--- Makefile	(revision 290)
+++ Makefile	(revision 343)
@@ -25,4 +25,8 @@
 	## Remove .svn folders
 	find ./$(DIST)/ -type d -name '.svn' | xargs rm -rf
+
+	## Remove .hg* files and folders
+	find ./$(DIST)/ -type d -name '.hg*' | xargs rm -rf
+	find ./$(DIST)/ -type f -name '.hg*' | xargs rm -rf
 	
 	## Remove config file if any
Index: admin/auth.php
===================================================================
--- admin/auth.php	(revision 366)
+++ admin/auth.php	(revision 356)
@@ -23,5 +23,5 @@
 $dlang = http::getAcceptLanguage();
 $dlang = ($dlang == '' ? 'en' : $dlang);
-if ($dlang != 'en' && preg_match('/^[a-z]{2}(-[a-z]{2})?$/',$dlang))
+if ($dlang != 'en')
 {
 	l10n::set(dirname(__FILE__).'/../locales/'.$dlang.'/main');
@@ -90,5 +90,5 @@
 		$page_url.'?akey='.$recover_key;
 		
-		$headers[] = 'From: '.(defined('DC_ADMIN_MAILFROM') && DC_ADMIN_MAILFROM ? DC_ADMIN_MAILFROM : 'dotclear@local');
+		$headers[] = 'From: dotclear@'.$_SERVER['HTTP_HOST'];
 		$headers[] = 'Content-Type: text/plain; charset=UTF-8;';
 		
@@ -126,35 +126,22 @@
 }
 # Change password and retry to log
-elseif ($change_pwd)
-{
+elseif ($change_pwd and $data = unserialize(base64_decode($_POST['login_data'])))
+{
+	# Check login informations
+	$check_user = false;
+	if (isset($data['cookie_admin']) && strlen($data['cookie_admin']) == 104)
+	{
+		$user_id = substr($data['cookie_admin'],40);
+		$user_id = @unpack('a32',@pack('H*',$user_id));
+		if (is_array($user_id))
+		{
+			$user_id = $user_id[1];
+			$user_key = substr($data['cookie_admin'],0,40);
+			$check_user = $core->auth->checkUser($user_id,null,$user_key) === true;
+		}
+	}
+	
 	try
 	{
-		$tmp_data = explode('/',$_POST['login_data']);
-		if (count($tmp_data) != 3) {
-			throw new Exception();
-		}
-		$data = array(
-			'user_id'=>base64_decode($tmp_data[0]),
-			'cookie_admin'=>$tmp_data[1],
-			'user_remember'=>$tmp_data[2]=='1'
-		);
-		if ($data['user_id'] === false) {
-			throw new Exception();
-		}
-		
-		# Check login informations
-		$check_user = false;
-		if (isset($data['cookie_admin']) && strlen($data['cookie_admin']) == 104)
-		{
-			$user_id = substr($data['cookie_admin'],40);
-			$user_id = @unpack('a32',@pack('H*',$user_id));
-			if (is_array($user_id))
-			{
-				$user_id = $user_id[1];
-				$user_key = substr($data['cookie_admin'],0,40);
-				$check_user = $core->auth->checkUser($user_id,null,$user_key) === true;
-			}
-		}
-	
 		if (!$core->auth->allowPassChange() || !$check_user) {
 			$change_pwd = false;
@@ -179,5 +166,9 @@
 		$_SESSION['sess_browser_uid'] = http::browserUID(DC_MASTER_KEY);
 		
-		if ($data['user_remember'])
+		if (!empty($data['blog_id'])) {
+			$_SESSION['sess_blog_id'] = $data['blog_id'];
+		}
+		
+		if (!empty($data['user_remember']))
 		{
 			setcookie('dc_admin',$data['cookie_admin'],strtotime('+15 days'),'','',DC_ADMIN_SSL);
@@ -202,9 +193,10 @@
 	if ($check_user && $core->auth->mustChangePassword())
 	{
-		$login_data = join('/',array(
-			base64_encode($user_id),
-			$cookie_admin,
-			empty($_POST['user_remember'])?'0':'1'
-		));
+		$login_data = base64_encode(serialize(array(
+			'user_id'=>$user_id,
+			'cookie_admin'=>$cookie_admin,
+			'blog_id'=>(!empty($_POST['blog']) ? $_POST['blog'] : ''),
+			'user_remember'=>!empty($_POST['user_remember'])
+		)));
 		
 		if (!$core->auth->allowPassChange()) {
Index: admin/install/wizard.php
===================================================================
--- admin/install/wizard.php	(revision 363)
+++ admin/install/wizard.php	(revision 270)
@@ -100,5 +100,4 @@
 		$admin_url = preg_replace('%install/wizard.php$%','',$_SERVER['REQUEST_URI']);
 		writeConfigValue('DC_ADMIN_URL',http::getHost().$admin_url,$full_conf);
-		writeConfigValue('DC_ADMIN_MAILFROM','dotclear@'.$_SERVER['HTTP_HOST'],$full_conf);
 		writeConfigValue('DC_MASTER_KEY',md5(uniqid()),$full_conf);
 		
Index: admin/js/common.js
===================================================================
--- admin/js/common.js	(revision 314)
+++ admin/js/common.js	(revision 343)
@@ -57,5 +57,5 @@
 }
 
-jQuery.fn.toggleWithLegend = function(target,s, callback) {
+jQuery.fn.toggleWithLegend = function(target,s) {
 	var defaults = {
 		img_on_src: dotclear.img_plus_src,
@@ -79,14 +79,14 @@
 	}
 
-	var toggle = function(i,speed, callback) {
+	var toggle = function(i,speed) {
 		speed = speed || 0;
 		if (p.hide) {
 			$(i).get(0).src = p.img_on_src;
 			$(i).get(0).alt = p.img_on_alt;
-			target.hide(speed, callback);
+			target.hide(speed, positionFooter);
 		} else {
 			$(i).get(0).src = p.img_off_src;
 			$(i).get(0).alt = p.img_off_alt;
-			target.show(speed, callback);
+			target.show(speed, positionFooter);
 			if (p.fn) {
 				p.fn.apply(target);
@@ -122,5 +122,5 @@
 		$(ctarget).css('cursor','pointer');
 		$(ctarget).click(function() {
-			toggle(i,p.speed, callback);
+			toggle(i,p.speed, positionFooter);
 			return false;
 		});
@@ -356,18 +356,14 @@
 	}
 	$('#blog-menu h3:first').toggleWithLegend($('#blog-menu ul:first'),
-		$.extend({cookie:'dc_blog_menu'},menu_settings),
-		positionFooter
+		$.extend({cookie:'dc_blog_menu'},menu_settings)
 	);
 	$('#system-menu h3:first').toggleWithLegend($('#system-menu ul:first'),
-		$.extend({cookie:'dc_system_menu'},menu_settings),
-		positionFooter
+		$.extend({cookie:'dc_system_menu'},menu_settings)
 	);
 	$('#plugins-menu h3:first').toggleWithLegend($('#plugins-menu ul:first'),
-		$.extend({cookie:'dc_plugins_menu'},menu_settings),
-		positionFooter
+		$.extend({cookie:'dc_plugins_menu'},menu_settings)
 	);
 	$('#favorites-menu h3:first').toggleWithLegend($('#favorites-menu ul:first'),
-		$.extend({cookie:'dc_favorites_menu',hide:false,reverse_cookie:true},menu_settings),
-		positionFooter
+		$.extend({cookie:'dc_favorites_menu',hide:false,reverse_cookie:true},menu_settings)
 	);
 
Index: admin/js/jquery/jquery.pageTabs.js
===================================================================
--- admin/js/jquery/jquery.pageTabs.js	(revision 0)
+++ admin/js/jquery/jquery.pageTabs.js	(revision 297)
@@ -9,7 +9,7 @@
 		breakerClassName: 'clear'
 	};
-	
+
 	var index = start_tab ? start_tab : 0;
-	
+
 	this.params = jQuery.extend(defaults,settings);
 	this.divs = jQuery('div.'+this.params.className);
@@ -20,10 +20,10 @@
 jQuery._pageTabs.prototype = {
 	items: new Array(),
-	
+
 	createList: function() {
 		if (this.divs.length <= 0) {
 			return;
 		}
-		
+
 		this.block = document.createElement('div');
 		this.block.className = this.params.listClassName;
@@ -32,5 +32,5 @@
 		this.block.appendChild(this.list);
 		var li, a;
-		
+
 		var This = this;
 		var i=0;
@@ -57,30 +57,30 @@
 			}
 		});
-		
+
 		this.breaker = document.createElement('br');
 		this.breaker.className = this.params.breakerClassName;
-		
+
 		jQuery(this.divs.get(0)).before(this.block);
 		jQuery(this.block).after(this.breaker);
 	},
-	
+
 	showDiv: function(index) {
 		var This = this;
 		var i = 0;
 		var to_trigger = null;
-		
+
 		this.divs.each(function() {
 			if ((this.id != '' && this.id == index) || i == index) {
-				jQuery(this).show();
+				jQuery(this).show(0, positionFooter);
 				This.items[i].className = This.params.listClassName+'-active';
 				to_trigger = i;
 			} else {
-				jQuery(this).hide();
+				jQuery(this).hide(0, positionFooter);
 				This.items[i].className = '';
 			}
-			
+
 			i++;
 		});
-		
+
 		if (to_trigger != null) {
 			jQuery(this.divs[to_trigger]).onetabload();
Index: admin/post.php
===================================================================
--- admin/post.php	(revision 270)
+++ admin/post.php	(revision 322)
@@ -527,4 +527,7 @@
 	}
 	
+	# --BEHAVIOR-- adminCommentsActionsCombo
+	$core->callBehavior('adminCommentsActionsCombo',array(&$combo_action));
+	
 	$has_action = !empty($combo_action) && (!$trackbacks->isEmpty() || !$comments->isEmpty());
 	
Index: admin/preferences.php
===================================================================
--- admin/preferences.php	(revision 324)
+++ admin/preferences.php	(revision 371)
@@ -555,8 +555,8 @@
 $array = $_fav;
 function cmp($a,$b) {
-    if ($a[1] == $b[1]) {
+    if (__($a[1]) == __($b[1])) {
         return 0;
     }
-    return ($a[1] < $b[1]) ? -1 : 1;
+    return (__($a[1]) < __($b[1])) ? -1 : 1;
 }
 $array=$array->getArrayCopy();
Index: admin/services.php
===================================================================
--- admin/services.php	(revision 270)
+++ admin/services.php	(revision 323)
@@ -133,6 +133,5 @@
 			$rsp->comment_ip($rs->comment_ip);
 			$rsp->comment_email($rs->comment_email);
-			# --BEHAVIOR-- adminAfterCommentDesc
-			$rsp->comment_spam_disp($core->callBehavior('adminAfterCommentDesc', $rs));
+			$rsp->comment_spam_disp(dcAntispam::statusMessage($rs));
 		}
 		
Index: admin/user.php
===================================================================
--- admin/user.php	(revision 270)
+++ admin/user.php	(revision 313)
@@ -96,4 +96,8 @@
 		$cur->user_post_status = $user_post_status = $_POST['user_post_status'];
 		
+		if ($cur->user_id == $core->auth->userID() && $core->auth->isSuperAdmin()) {
+			// force super_user to true if current user
+			$cur->user_super = $user_super = true;
+		}
 		if ($core->auth->allowPassChange()) {
 			$cur->user_change_pwd = !empty($_POST['user_change_pwd']) ? 1 : 0;
@@ -255,6 +259,8 @@
 }
 
+$super_disabled = $user_super && $user_id == $core->auth->userID();
+
 echo
-'<p><label for="user_super" class="classic">'.form::checkbox('user_super','1',$user_super,'',16).' '.
+'<p><label for="user_super" class="classic">'.form::checkbox('user_super','1',$user_super,'',16,$super_disabled).' '.
 __('Super administrator').'</label></p>'.
 '</div>'.
Index: admin/users.php
===================================================================
--- admin/users.php	(revision 270)
+++ admin/users.php	(revision 313)
@@ -24,6 +24,7 @@
 			# --BEHAVIOR-- adminBeforeUserDelete
 			$core->callBehavior('adminBeforeUserDelete',$u);
-			
-			$core->delUser($u);
+			if ($u != $core->auth->userID()) {
+				$core->delUser($u);
+			}
 		}
 		catch (Exception $e)
@@ -32,5 +33,4 @@
 		}
 	}
-	
 	if (!$core->error->flag()) {
 		http::redirect('users.php?del=1');
Index: inc/admin/lib.dc.page.php
===================================================================
--- inc/admin/lib.dc.page.php	(revision 321)
+++ inc/admin/lib.dc.page.php	(revision 344)
@@ -16,4 +16,6 @@
 class dcPage
 {
+	private static $loaded_js=array();
+
 	# Auth check
 	public static function check($permissions)
@@ -349,5 +351,9 @@
 	public static function jsLoad($src)
 	{
-		return '<script type="text/javascript" src="'.html::escapeHTML($src).'"></script>'."\n";
+		$escaped_src = html::escapeHTML($src);
+		if (!isset(self::$loaded_js[$escaped_src])) {
+			self::$loaded_js[$escaped_src]=true;
+			return '<script type="text/javascript" src="'.$escaped_src.'"></script>'."\n";
+		}
 	}
 	
Index: inc/config.php.in
===================================================================
--- inc/config.php.in	(revision 362)
+++ inc/config.php.in	(revision 270)
@@ -40,7 +40,4 @@
 define('DC_ADMIN_URL','');
 
-// Admin mail from address. For password recovery and such.
-define('DC_ADMIN_MAILFROM','');
-
 // Cookie's name
 define('DC_SESSION_NAME','dcxd');
Index: inc/core/class.dc.auth.php
===================================================================
--- inc/core/class.dc.auth.php	(revision 270)
+++ inc/core/class.dc.auth.php	(revision 333)
@@ -4,5 +4,5 @@
 # This file is part of Dotclear 2.
 #
-# Copyright (c) 2003-2011 Olivier Meunier & Association Dotclear
+# Copyright (c) 2003-2010 Olivier Meunier & Association Dotclear
 # Licensed under the GPL version 2.0 license.
 # See LICENSE file or
@@ -13,37 +13,52 @@
 
 /**
-@ingroup DC_CORE
-@nosubgrouping
-@brief Authentication and user credentials management
-
-dcAuth is a class used to handle everything related to user authentication
-and credentials. Object is provided by dcCore $auth property.
+* @ingroup DC_CORE
+* @nosubgrouping
+* @brief Authentication and user credentials management
+* 
+* dcAuth is a class used to handle everything related to user authentication
+* and credentials. Object is provided by dcCore $auth property.
 */
 class dcAuth
 {
-	protected $core;		///< <b>dcCore</b> dcCore instance
-	protected $con;		///< <b>connection</b> Database connection object
-	
-	protected $user_table;	///< <b>string</b>	User table name
-	protected $perm_table;	///< <b>string</b>	Perm table name
-	
-	protected $user_id;					///< <b>string</b>		Current user ID
-	protected $user_info = array();		///< <b>array</b>		Array with user information
-	protected $user_options = array();		///< <b>array</b>		Array with user options
-	protected $user_change_pwd;			///< <b>boolean</b>		User must change his password after login
-	protected $user_admin;				///< <b>boolean</b>		User is super admin
-	protected $permissions = array();		///< <b>array</b>		Permissions for each blog
-	protected $allow_pass_change = true;	///< <b>boolean</b>		User can change its password
-	protected $blogs = array();			///< <b>array</b>		List of blogs on which the user has permissions
-	public $blog_count = null;				///< <b>integer</b>		Count of user blogs
-	
-	protected $perm_types;	///< <b>array</b> Permission types
-	
-	public $user_prefs;	///< <b>dcPrefs</b>	dcPrefs object
-	
-	/**
-	Class constructor. Takes dcCore object as single argument.
-	
-	@param	core		<b>dcCore</b>		dcCore object
+	/** @var dcCore dcCore instance */
+	protected $core;
+	/** @var connection Database connection object */
+	protected $con;
+	
+	/** @var string User table name */
+	protected $user_table;
+	/** @var string Perm table name */
+	protected $perm_table;
+	
+	/** @var string Current user ID */
+	protected $user_id;
+	/** @var array Array with user information */
+	protected $user_info = array();
+	/** @var array Array with user options */
+	protected $user_options = array();
+	/** @var boolean User must change his password after login */
+	protected $user_change_pwd;
+	/** @var boolean User is super admin */
+	protected $user_admin;
+	/** @var array Permissions for each blog */
+	protected $permissions = array();
+	/** @var boolean User can change its password */
+	protected $allow_pass_change = true;
+	/** @var array List of blogs on which the user has permissions */
+	protected $blogs = array();
+	/** @var integer Count of user blogs */
+	public $blog_count = null;
+	
+	/** @var array Permission types */
+	protected $perm_types;
+	
+	/** @var dcPrefs dcPrefs object */
+	public $user_prefs;
+	
+	/**
+	* Class constructor. Takes dcCore object as single argument.
+	* 
+	* @param dcCore	$core		dcCore object
 	*/
 	public function __construct($core)
@@ -70,14 +85,15 @@
 	//@{
 	/**
-	Checks if user exists and can log in. <var>$pwd</var> argument is optionnal
-	while you may need to check user without password. This method will create
-	credentials and populate all needed object properties.
-	
-	@param	user_id	<b>string</b>		User ID
-	@param	pwd		<b>string</b>		User password
-	@param	user_key	<b>string</b>		User key check
-	@return	<b>boolean</b>
-	*/
-	public function checkUser($user_id, $pwd=null, $user_key=null)
+	* Checks if user exists and can log in. <var>$pwd</var> argument is optionnal
+	* while you may need to check user without password. This method will create
+	* credentials and populate all needed object properties.
+	* 
+	* @param string	$user_id		User ID
+	* @param string	$pwd			User password
+	* @param string	$user_key		User key check
+	* @param boolean	$check_blog	checks if user is associated to a blog or not.
+	* @return boolean
+	*/
+	public function checkUser($user_id, $pwd=null, $user_key=null, $check_blog=true)
 	{
 		# Check user and password
@@ -141,5 +157,5 @@
 		
 		# Get permissions on blogs
-		if ($this->findUserBlog() === false) {
+		if ($check_blog && ($this->findUserBlog() === false)) {
 			return false;
 		}
@@ -148,8 +164,8 @@
 	
 	/**
-	This method only check current user password.
-	
-	@param	pwd		<b>string</b>		User password
-	@return	<b>boolean</b>
+	* This method only check current user password.
+	* 
+	* @param string	$pwd			User password
+	* @return boolean
 	*/
 	public function checkPassword($pwd)
@@ -163,7 +179,7 @@
 	
 	/**
-	This method checks if user session cookie exists
-	
-	@return	<b>boolean</b>
+	* This method checks if user session cookie exists
+	* 
+	* @return boolean
 	*/
 	public function sessionExists()
@@ -173,7 +189,7 @@
 	
 	/**
-	This method checks user session validity.
-	
-	@return	<b>boolean</b>
+	* This method checks user session validity.
+	* 
+	* @return boolean
 	*/
 	public function checkSession($uid=null)
@@ -212,7 +228,7 @@
 	
 	/**
-	Checks if user is super admin
-	
-	@return	<b>boolean</b>
+	* Checks if user is super admin
+	* 
+	* @return boolean
 	*/
 	public function isSuperAdmin()
@@ -222,11 +238,11 @@
 	
 	/**
-	Checks if user has permissions given in <var>$permissions</var> for blog
-	<var>$blog_id</var>. <var>$permissions</var> is a coma separated list of
-	permissions.
-	
-	@param	permissions	<b>string</b>		Permissions list
-	@param	blog_id		<b>string</b>		Blog ID
-	@return	<b>boolean</b>
+	* Checks if user has permissions given in <var>$permissions</var> for blog
+	* <var>$blog_id</var>. <var>$permissions</var> is a coma separated list of
+	* permissions.
+	* 
+	* @param string	$permissions	Permissions list
+	* @param string	$blog_id		Blog ID
+	* @return boolean
 	*/
 	public function check($permissions,$blog_id)
@@ -257,7 +273,7 @@
 	
 	/**
-	Returns true if user is allowed to change its password.
-	
-	@return	<b>boolean</b>
+	* Returns true if user is allowed to change its password.
+	* 
+	* @return	boolean
 	*/
 	public function allowPassChange()
@@ -312,8 +328,9 @@
 	//@{
 	/**
-	Calls <var>$f</var> function with super admin rights.
-	
-	@param	f		<b>callback</b>	Callback function
-	@return	<b>mixed</b> Function result
+	* Calls $f function with super admin rights.
+	* Returns the function result.
+	* 
+	* @param callback	$f			Callback function
+	* @return mixed
 	*/
 	public function sudo($f)
@@ -346,11 +363,12 @@
 	//@{
 	/**
-	Returns user permissions for a blog as an array which looks like:
-	
-	 - [blog_id]
-	   - [permission] => true
-	   - ...
-	
-	@return	<b>array</b>
+	* Returns user permissions for a blog as an array which looks like:
+	* 
+	*  - [blog_id]
+	*    - [permission] => true
+	*    - ...
+	* 
+	* @param string	$blog_id		Blog ID
+	* @return array
 	*/
 	public function getPermissions($blog_id)
@@ -422,7 +440,7 @@
 	
 	/**
-	Returns current user ID
-	
-	@return	<b>string</b>
+	* Returns current user ID
+	* 
+	* @return string
 	*/
 	public function userID()
@@ -432,8 +450,8 @@
 	
 	/**
-	Returns information about a user .
-	
-	@param	n		<b>string</b>		Information name
-	@return	<b>string</b> Information value
+	* Returns information about a user .
+	* 
+	* @param string	$n			Information name
+	* @return string
 	*/
 	public function getInfo($n)
@@ -447,8 +465,8 @@
 	
 	/**
-	Returns a specific user option
-	
-	@param	n		<b>string</b>		Option name
-	@return	<b>string</b> Option value
+	* Returns a specific user option
+	* 
+	* @param string	$n			Option name
+	* @return string
 	*/
 	public function getOption($n)
@@ -461,7 +479,7 @@
 	
 	/**
-	Returns all user options in an associative array.
-	
-	@return	<b>array</b>
+	* Returns all user options in an associative array.
+	* 
+	* @return array
 	*/
 	public function getOptions()
@@ -474,8 +492,8 @@
 	//@{
 	/**
-	Returns an array with permissions parsed from the string <var>$level</var>
-	
-	@param	level	<b>string</b>		Permissions string
-	@return	<b>array</b>
+	* Returns an array with permissions parsed from the string <var>$level</var>
+	* 
+	* @param string	$level		Permissions string
+	* @return array
 	*/
 	public function parsePermissions($level)
@@ -492,7 +510,7 @@
 	
 	/**
-	Returns <var>perm_types</var> property content.
-	
-	@return	<b>array</b>
+	* Returns <var>perm_types</var> property content.
+	* 
+	* @return array
 	*/
 	public function getPermissionsTypes()
@@ -502,8 +520,8 @@
 	
 	/**
-	Adds a new permission type.
-	
-	@param	name		<b>string</b>		Permission name
-	@param	title	<b>string</b>		Permission title
+	* Adds a new permission type.
+	* 
+	* @param string	$name		Permission name
+	* @param string	$title		Permission title
 	*/
 	public function setPermissionType($name,$title)
@@ -516,10 +534,10 @@
 	//@{
 	/**
-	Add a recover key to a specific user identified by its email and
-	password.
-	
-	@param	user_id		<b>string</b>		User ID
-	@param	user_email	<b>string</b>		User Email
-	@return	<b>string</b> Recover key
+	* Add a recover key to a specific user identified by its email and
+	* password.
+	* 
+	* @param string	$user_id		User ID
+	* @param string	$user_email	User Email
+	* @return string
 	*/
 	public function setRecoverKey($user_id,$user_email)
@@ -547,12 +565,12 @@
 	
 	/**
-	Creates a new user password using recovery key. Returns an array:
-	
-	- user_email
-	- user_id
-	- new_pass
-	
-	@param	recover_key	<b>string</b>		Recovery key
-	@return	<b>array</b>
+	* Creates a new user password using recovery key. Returns an array:
+	* 
+	* - user_email
+	* - user_id
+	* - new_pass
+	* 
+	* @param string	$recover_key	Recovery key
+	* @return array
 	*/
 	public function recoverUserPassword($recover_key)
@@ -590,22 +608,22 @@
 	
 	/**
-	Called after core->addUser
-	@see		dcCore::addUser
-	@param	cur		<b>cursor</b>		User cursor
+	* Called after core->addUser
+	* @see dcCore::addUser
+	* @param cursor	$cur			User cursor
 	*/
 	public function afterAddUser($cur) {}
 	
 	/**
-	Called after core->updUser
-	@see		dcCore::updUser
-	@param	id		<b>string</b>		User ID
-	@param	cur		<b>cursor</b>		User cursor
+	* Called after core->updUser
+	* @see dcCore::updUser
+	* @param string	$id			User ID
+	* @param cursor	$cur			User cursor
 	*/
 	public function afterUpdUser($id,$cur) {}
 	
 	/**
-	Called after core->delUser
-	@see		dcCore::delUser
-	@param	id		<b>string</b>		User ID
+	* Called after core->delUser
+	* @see dcCore::delUser
+	* @param string	$id			User ID
 	*/
 	public function afterDelUser($id) {}
Index: inc/core/class.dc.blog.php
===================================================================
--- inc/core/class.dc.blog.php	(revision 270)
+++ inc/core/class.dc.blog.php	(revision 340)
@@ -21,21 +21,36 @@
 class dcBlog
 {
-	protected $core;	///< <b>dcCore</b> dcCore instance
-	public $con;		///< <b>connection</b>	Database connection object
-	public $prefix;	///< <b>string</b>		Database table prefix
-	
-	public $id;		///< <b>string</b>		Blog ID
-	public $uid;		///< <b>string</b>		Blog unique ID
-	public $name;		///< <b>string</b>		Blog name
-	public $desc;		///< <b>string</b>		Blog description
-	public $url;		///< <b>string</b>		Blog URL
-	public $host;		///< <b>string</b>		Blog host
-	public $creadt;	///< <b>string</b>		Blog creation date
-	public $upddt;		///< <b>string</b>		Blog last update date
-	public $status;	///< <b>string</b>		Blog status
-	
-	public $settings;		///< <b>dcSettings</b>	dcSettings object
-	public $themes_path;	///< <b>string</b>		Blog theme path
-	public $public_path;	///< <b>string</b>		Blog public path
+	/** @var dcCore dcCore instance */
+	protected $core;
+	/** @var connection Database connection object */
+	public $con;
+	/** @var string Database table prefix */
+	public $prefix;
+	
+	/** @var string Blog ID */
+	public $id;
+	/** @var string Blog unique ID */
+	public $uid;
+	/** @var string Blog name */
+	public $name;
+	/** @var string Blog description */
+	public $desc;
+	/** @var string Blog URL */
+	public $url;
+	/** @var string Blog host */
+	public $host;
+	/** @var string Blog creation date */
+	public $creadt;
+	/** @var string Blog last update date */
+	public $upddt;
+	/** @var string Blog status */
+	public $status;
+	
+	/** @var dcSettings dcSettings object */
+	public $settings;
+	/** @var string Blog theme path */
+	public $themes_path;
+	/** @var string Blog public path */
+	public $public_path;
 	
 	private $post_status = array();
@@ -44,5 +59,6 @@
 	private $categories;
 	
-	public $without_password = true;	///< <b>boolean</b> Disallow entries password protection
+	/** @var boolean Disallow entries password protection */
+	public $without_password = true;
 	
 	/**
@@ -391,5 +407,5 @@
 		
 		if (!empty($params['post_type'])) {
-			$strReq .= "AND post_type = '".$this->con->escape($params['post_type'])."' ";
+			$strReq .= 'AND P.post_type '.$this->con->in($params['post_type']);
 		}
 		
@@ -711,9 +727,5 @@
 		if (isset($params['post_type']))
 		{
-			if (is_array($params['post_type']) && !empty($params['post_type'])) {
-				$strReq .= 'AND post_type '.$this->con->in($params['post_type']);
-			} elseif ($params['post_type'] != '') {
-				$strReq .= "AND post_type = '".$this->con->escape($params['post_type'])."' ";
-			}
+			$strReq .= 'AND post_type '.$this->con->in($params['post_type']);
 		}
 		else
@@ -858,11 +870,11 @@
 		
 		if($dir > 0) {
-               $sign = '>';
-               $order = 'ASC';
-          }
-          else {
-               $sign = '<';
-               $order = 'DESC';
-          }
+			$sign = '>';
+			$order = 'ASC';
+		}
+		else {
+			$sign = '<';
+			$order = 'DESC';
+		}
 		
 		$params['post_type'] = $post->post_type;
@@ -1025,5 +1037,5 @@
 			$strReq .= "AND post_type = 'post' ";
 		}
-				
+		
 		if (!empty($params['year'])) {
 			$strReq .= 'AND '.$this->con->dateFormat('post_dt','%Y')." = '".sprintf('%04d',$params['year'])."' ";
@@ -1165,5 +1177,5 @@
 		$cur->post_upddt = date('Y-m-d H:i:s');
 		
-		#�If user is only "usage", we need to check the post's owner
+		#If user is only "usage", we need to check the post's owner
 		if (!$this->core->auth->check('contentadmin',$this->id))
 		{
@@ -1768,9 +1780,5 @@
 		if (!empty($params['post_type']))
 		{
-			if (is_array($params['post_type']) && !empty($params['post_type'])) {
-				$strReq .= 'AND post_type '.$this->con->in($params['post_type']);
-			} else {
-				$strReq .= "AND post_type = '".$this->con->escape($params['post_type'])."' ";
-			}
+			$strReq .= 'AND post_type '.$this->con->in($params['post_type']);
 		}
 		
Index: inc/core/class.dc.core.php
===================================================================
--- inc/core/class.dc.core.php	(revision 270)
+++ inc/core/class.dc.core.php	(revision 331)
@@ -63,4 +63,16 @@
 		if ($this->con instanceof mysqlConnection) {
 			mysqlConnection::$weak_locks = true;
+		}
+		
+		# define searchpath for postgresql
+		if ($this->con instanceof pgsqlConnection)
+		{
+			$searchpath = explode ('.',$prefix,2);
+			if (count($searchpath) > 1)
+			{
+				$prefix = $searchpath[1];
+				$sql = 'SET search_path TO '.$searchpath[0].',public;';
+				$this->con->execute($sql);
+			}
 		}
 		
Index: inc/core/class.dc.error.php
===================================================================
--- inc/core/class.dc.error.php	(revision 270)
+++ inc/core/class.dc.error.php	(revision 334)
@@ -13,20 +13,24 @@
 
 /**
-@ingroup DC_CORE
-@brief Error class
-
-dcError is a very simple error class, with a stack. Call dcError::add to
-add an error in stack. In administration area, errors are automatically
-displayed.
+* @ingroup DC_CORE
+* @brief Error class
+* 
+* dcError is a very simple error class, with a stack. Call dcError::add to
+* add an error in stack. In administration area, errors are automatically
+* displayed.
 */
 class dcError
 {
-	protected $errors = array();				///< <b>array</b>	Errors stack
-	protected $flag = false;					///< <b>boolean</b>	True if stack is not empty
-	protected $html_list = "<ul>\n%s</ul>\n";	///< <b>string</b>	HTML errors list pattern
-	protected $html_item = "<li>%s</li>\n";	///< <b>string</b>	HTML error item pattern
+	/** @var array Errors stack */
+	protected $errors = array();
+	/** @var boolean True if stack is not empty */
+	protected $flag = false;
+	/** @var string HTML errors list pattern */
+	protected $html_list = "<ul>\n%s</ul>\n";
+	/** @var string HTML error item pattern */
+	protected $html_item = "<li>%s</li>\n";
 	
 	/**
-	Object constructor.
+	* Object constructor.
 	*/
 	public function __construct()
@@ -37,7 +41,7 @@
 	
 	/**
-	Object string representation. Returns errors stack.
-	
-	@return	<b>string</b>
+	* Object string representation. Returns errors stack.
+	* 
+	* @return string
 	*/
 	public function __toString()
@@ -54,7 +58,7 @@
 	
 	/**
-	Adds an error to stack.
-	
-	@param	msg		<b>string</b>		Error message
+	* Adds an error to stack.
+	* 
+	* @param string	$msg			Error message
 	*/
 	public function add($msg)
@@ -65,7 +69,7 @@
 	
 	/**
-	Returns the value of <var>flag</var> property.
-	
-	@return	<b>boolean</b> True if errors stack is not empty
+	* Returns the value of <var>flag</var> property. True if errors stack is not empty
+	* 
+	* @return boolean
 	*/
 	public function flag()
@@ -75,5 +79,5 @@
 	
 	/**
-	Resets errors stack.
+	* Resets errors stack.
 	*/
 	public function reset()
@@ -84,7 +88,7 @@
 	
 	/**
-	Returns <var>errors</var> property.
-	
-	@return	<b>array</b>
+	* Returns <var>errors</var> property.
+	* 
+	* @return array
 	*/
 	public function getErrors()
@@ -94,8 +98,8 @@
 	
 	/**
-	Sets <var>list</var> and <var>item</var> properties.
-	
-	@param	list		<b>string</b>		HTML errors list pattern
-	@param	item		<b>string</b>		HTML error item pattern
+	* Sets <var>list</var> and <var>item</var> properties.
+	* 
+	* @param string	$list		HTML errors list pattern
+	* @param string	$item		HTML error item pattern
 	*/
 	public function setHTMLFormat($list,$item)
@@ -106,7 +110,7 @@
 	
 	/**
-	Returns errors stack as HTML.
-	
-	@return	<b>string</b>
+	* Returns errors stack as HTML.
+	* 
+	* @return string
 	*/
 	public function toHTML()
Index: inc/core/class.dc.settings.php
===================================================================
--- inc/core/class.dc.settings.php	(revision 270)
+++ inc/core/class.dc.settings.php	(revision 301)
@@ -308,4 +308,77 @@
 	}
 
+	/**
+	Returns a list of settings matching given criteria, for any blog.
+	<b>$params</b> is an array taking the following
+	optionnal parameters:
+	
+	- ns : retrieve setting from given namespace
+	- id : retrieve only settings corresponding to the given id
+	
+	@param	params		<b>array</b>		Parameters
+	@return	<b>record</b>	A record 
+	*/
+	public function getGlobalSettings($params=array())
+	{
+		$strReq = "SELECT * from ".$this->table." ";
+		$where = array();
+		if (!empty($params['ns'])) {
+			$where[] = "setting_ns = '".$this->con->escape($params['ns'])."'";
+		}
+		if (!empty($params['id'])) {
+			$where[] = "setting_id = '".$this->con->escape($params['id'])."'";
+		}
+		if (isset($params['blog_id'])) {
+			if (!empty($params['blog_id'])) {
+				$where[] = "blog_id = '".$this->con->escape($params['blog_id'])."'";
+			} else {
+				$where[] = "blog_id IS NULL";
+			}
+		}
+		if (count($where) != 0) {
+			$strReq .= " WHERE ".join(" AND ", $where);
+		}
+		$strReq .= " ORDER by blog_id";
+		return $this->con->select($strReq);
+	}
+
+	/**
+	Updates a setting from a given record
+	
+	@param	rs		<b>record</b>		the setting to update
+	*/
+	public function updateSetting($rs) 
+	{
+		$cur = $this->con->openCursor($this->table);
+		$cur->setting_id = $rs->setting_id;
+		$cur->setting_value = $rs->setting_value;
+		$cur->setting_type = $rs->setting_type;
+		$cur->setting_label = $rs->setting_label;
+		$cur->blog_id = $rs->blog_id;
+		$cur->setting_ns = $rs->setting_ns;
+		if ($cur->blog_id == null) {
+				$where = 'WHERE blog_id IS NULL ';
+		} else {
+			$where = "WHERE blog_id = '".$this->con->escape($cur->blog_id)."' ";
+		}
+		$cur->update($where."AND setting_id = '".$this->con->escape($cur->setting_id)."' AND setting_ns = '".$this->con->escape($cur->setting_ns)."' ");
+	}
+	
+	/**
+	Drops a setting from a given record
+	
+	@param	rs		<b>record</b>		the setting to drop
+	@return	int		number of deleted records (0 if setting does not exist)
+	*/
+	public function dropSetting($rs) {
+		$strReq = "DELETE FROM ".$this->table.' ';
+		if ($rs->blog_id == null) {
+			$strReq .= 'WHERE blog_id IS NULL ';
+		} else {
+			$strReq .= "WHERE blog_id = '".$this->con->escape($rs->blog_id)."' ";
+		}
+		$strReq .= "AND setting_id = '".$this->con->escape($rs->setting_id)."' AND setting_ns = '".$this->con->escape($rs->setting_ns)."' ";
+		return $this->con->execute($strReq);
+	}
 }
 ?>
Index: inc/prepend.php
===================================================================
--- inc/prepend.php	(revision 370)
+++ inc/prepend.php	(revision 343)
@@ -118,5 +118,5 @@
 # Constants
 define('DC_ROOT',path::real(dirname(__FILE__).'/..'));
-define('DC_VERSION','2.3.2-dev');
+define('DC_VERSION','2.4.0-dev');
 define('DC_DIGESTS',dirname(__FILE__).'/digests');
 define('DC_L10N_ROOT',dirname(__FILE__).'/../locales');
Index: inc/public/class.dc.template.php
===================================================================
--- inc/public/class.dc.template.php	(revision 341)
+++ inc/public/class.dc.template.php	(revision 351)
@@ -323,4 +323,10 @@
 		}
 		
+		if (($node instanceof tplNodeBlock) && !$node->isClosed()) {
+			$errors[] = sprintf(
+				__('Did not find closing tag for block <tpl:%s>. Content has been ignored.'),
+				html::escapeHTML($node->getTag()));
+		}
+		
 		$err = "";
 		if (count($errors) > 0) {
@@ -3236,4 +3242,7 @@
 		$this->closed = true;
 	}
+	public function isClosed() {
+		return $this->closed;
+	}
 	public function compile($tpl) {
 		if ($this->closed) {
Index: inc/public/lib.urlhandlers.php
===================================================================
--- inc/public/lib.urlhandlers.php	(revision 270)
+++ inc/public/lib.urlhandlers.php	(revision 306)
@@ -204,6 +204,9 @@
 		
 		$n = self::getPageNumber($args);
-		
-		$params['lang'] = $args;
+		$params = new ArrayObject(array(
+			'lang' => $args));
+		
+		$core->callBehavior('publicLangBeforeGetLangs',$params,$args);
+		
 		$_ctx->langs = $core->blog->getLangs($params);
 		
@@ -235,6 +238,9 @@
 		else
 		{
-			$params['cat_url'] = $args;
-			$params['post_type'] = 'post';
+			$params = new ArrayObject(array(
+				'cat_url' => $args,
+				'post_type' => 'post'));
+			
+			$core->callBehavior('publicCategoryBeforeGetCategories',$params,$args);
 			
 			$_ctx->categories = $core->blog->getCategories($params);
@@ -267,7 +273,11 @@
 		elseif (preg_match('|^/([0-9]{4})/([0-9]{2})$|',$args,$m))
 		{
-			$params['year'] = $m[1];
-			$params['month'] = $m[2];
-			$params['type'] = 'month';
+			$params = new ArrayObject(array(
+				'year' => $m[1],
+				'month' => $m[2],
+				'type' => 'month'));
+			
+			$core->callBehavior('publicArchiveBeforeGetDates',$params,$args);
+			
 			$_ctx->archives = $core->blog->getDates($params);
 			
@@ -300,7 +310,9 @@
 			$core->blog->withoutPassword(false);
 			
-			$params = new ArrayObject();
-			$params['post_url'] = $args;
-			
+			$params = new ArrayObject(array(
+				'post_url' => $args));
+			
+			$core->callBehavior('publicPostBeforeGetPosts',$params,$args);
+
 			$_ctx->posts = $core->blog->getPosts($params);
 			
@@ -410,5 +422,5 @@
 						
 						$redir = $_ctx->posts->getURL();
-						$redir .= strpos($redir,'?') !== false ? '&' : '?';
+						$redir .= $core->blog->settings->system->url_scan == 'query_string' ? '&' : '?';
 						
 						try
@@ -481,5 +493,4 @@
 		$cat_url = false;
 		$post_id = null;
-		$params = array();
 		$subtitle = '';
 		
@@ -490,6 +501,9 @@
 		
 		if (preg_match('!^([a-z]{2}(-[a-z]{2})?)/(.*)$!',$args,$m)) {
-			$params['lang'] = $m[1];
+			$params = new ArrayObject(array('lang' => $m[1]));
+			
 			$args = $m[3];
+			
+			$core->callBehavior('publicFeedBeforeGetLangs',$params,$args);
 			
 			$_ctx->langs = $core->blog->getLangs($params);
@@ -535,6 +549,10 @@
 		if ($cat_url)
 		{
-			$params['cat_url'] = $cat_url;
-			$params['post_type'] = 'post';
+			$params = new ArrayObject(array(
+				'cat_url' => $cat_url,
+				'post_type' => 'post'));
+			
+			$core->callBehavior('publicFeedBeforeGetCategories',$params,$args);
+			
 			$_ctx->categories = $core->blog->getCategories($params);
 			
@@ -549,6 +567,10 @@
 		elseif ($post_id)
 		{
-			$params['post_id'] = $post_id;
-			$params['post_type'] = '';
+			$params = new ArrayObject(array(
+				'post_id' => $post_id,
+				'post_type' => ''));
+				
+			$core->callBehavior('publicFeedBeforeGetPosts',$params,$args);
+			
 			$_ctx->posts = $core->blog->getPosts($params);
 			
Index: locales/fr/help/core_blog_pref.html
===================================================================
--- locales/fr/help/core_blog_pref.html	(revision 175)
+++ locales/fr/help/core_blog_pref.html	(revision 352)
@@ -43,6 +43,7 @@
 <dl>
   <dt>Nom de l'éditeur du blog</dt>
-  <dd>Nom de la personne en charge du blog. Peut être le propriétaire ou le
-  directeur de publication s'il existe.</dd>
+  <dd>Nom de la personne responsable du contenu du blog. Peut être le propriétaire
+  ou le directeur de publication s'il existe. Information affichée dans les
+  informations du flux de syndication.</dd>
   
   <dt>Note de copyright</dt>
Index: plugins/pages/_public.php
===================================================================
--- plugins/pages/_public.php	(revision 270)
+++ plugins/pages/_public.php	(revision 306)
@@ -33,7 +33,9 @@
 			$core->blog->withoutPassword(false);
 			
-			$params = new ArrayObject();
-			$params['post_type'] = 'page';
-			$params['post_url'] = $args;
+			$params = new ArrayObject(array(
+				'post_type' => 'page',
+				'post_url' => $args));
+			
+			$core->callBehavior('publicPagesBeforeGetPosts',$params,$args);
 			
 			$_ctx->posts = $core->blog->getPosts($params);
