Index: inc/core/class.dc.blog.php
===================================================================
--- inc/core/class.dc.blog.php	(revision 2628)
+++ inc/core/class.dc.blog.php	(revision 2655)
@@ -865,5 +865,9 @@
 
 		if (!empty($params['user_id'])) {
-			$strReq .= "AND U.user_id = '".$this->con->escape($params['user_id'])."' ";
+			$not="";
+			if (!empty($params['user_id_not'])) {
+				$not=" not";
+			}
+			$strReq .= "AND U.user_id ".$not.$this->con->in($params['user_id'])." ";
 		}
 
@@ -966,6 +970,6 @@
 
 		if (!$count_only && !empty($params['limit'])) {
-			$strReq .= $this->con->limit($params['limit']);
-		}
+				$strReq .= $this->con->limit($params['limit']);
+			}
 
 		if (!empty($params['sql_only'])) {
@@ -2206,5 +2210,5 @@
 			'UPDATE '.$this->prefix.'comment '.
 			'SET comment_status = '.$status.' ';
-		$strReq .=
+			$strReq .=
 			'WHERE comment_id'.$this->con->in($co_ids).
 			'AND post_id in (SELECT tp.post_id '.
@@ -2263,5 +2267,5 @@
 		}
 
-		$strReq =
+			$strReq =
 			'DELETE FROM '.$this->prefix.'comment '.
 			'WHERE comment_id'.$this->con->in($co_ids).' '.
@@ -2287,5 +2291,5 @@
 		}
 
-		$strReq =
+			$strReq =
 			'DELETE FROM '.$this->prefix.'comment '.
 			'WHERE comment_status = -2 '.
Index: inc/core/class.dc.context.php
===================================================================
--- inc/core/class.dc.context.php	(revision 1507)
+++ inc/core/class.dc.context.php	(revision 1507)
@@ -0,0 +1,340 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2011 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+if (!defined('DC_RC_PATH')) { return; }
+
+/**
+ * @ingroup DC_CORE
+ * @brief Template extension for public context
+ *
+ * This extends template environment with tools required in context.
+ * Admin context and public context should extend this class.
+ */
+class dcContext extends Twig_Extension
+{
+	protected $core;
+	protected $globals = array();
+	protected $protected_globals = array();
+	protected $memory = array();
+	protected $timestamp_messages = true;
+	
+	public function __construct($core)
+	{
+		$this->core = $core;
+		
+		# Globals editable via context
+		$this->globals = array();
+		
+		# Globals not editable via context
+		$this->protected_globals = array(
+			'messages' => array(
+				'static' => array(),
+				'lists' => array(),
+				'alert' => '',
+				'errors' => array()
+			),
+			
+			'version' 		=> DC_VERSION,
+			'vendor_name' 	=> DC_VENDOR_NAME,
+			
+			'safe_mode' 	=> isset($_SESSION['sess_safe_mode']) && $_SESSION['sess_safe_mode'],
+			'debug_mode'	=> DC_DEBUG
+		);
+	}
+	
+	/**
+	 * Prevent call crash from template on method that return this class
+	 */
+	public function __toString()
+	{
+		return '';
+	}
+	
+	/**
+	 * Test a global variable
+	 *
+	 * @param string $name Name of the variable to test
+	 * @return boolean
+	 */
+	public function __isset($name)
+	{
+		return isset($this->globals[$name]);
+	}
+	
+	/**
+	 * Add a global variable
+	 *
+	 * @param string $name Name of the variable
+	 * @param mixed $value Value of the variable
+	 */
+	public function __set($name,$value)
+	{
+/*
+		# Overload protect
+		if ($value === null && isset($this->globals[$name])) {
+			unset($this->globals[$name]);
+		}
+		elseif (!isset($this->globals[$name])) {
+			throw new Exception('Modification of overloaded globals has no effect');
+		}
+//*/
+		$this->globals[$name] = $value;
+	}
+	
+	/**
+	 * Get a global variable
+	 *
+	 * @param string $name Name of the variable
+	 * @return mixed Value of the variable or null
+	 */
+	public function __get($name)
+	{
+		return isset($this->globals[$name]) ? $this->globals[$name] : null;
+	}
+	
+	/**
+	 * Returns a list of filters to add to the existing list.
+	 *
+	 * @return array An array of filters
+	 */
+	public function getFilters()
+	{
+		return array(
+			'trans' => new Twig_Filter_Function("__", array('is_safe' => array('html')))
+		);
+	}
+	
+	/**
+	 * Returns a list of functions to add to the existing list.
+	 * 
+	 * @return array An array of functions
+	 */
+	public function getFunctions()
+	{
+		return array(
+			'__' 		=> new Twig_Function_Function("__", array('is_safe' => array('html'))),
+			'debug_info' => new Twig_Function_Method($this, 'getDebugInfo', array('is_safe' => array('html'))),
+			'memorize' => new Twig_Function_Method($this, 'setMemory', array('is_safe' => array('html'))),
+			'memorized' => new Twig_Function_Method($this, 'getMemory', array('is_safe' => array('html')))
+		);
+	}
+	
+	/**
+	 * Returns a list of global variables to add to the existing list.
+	 *
+	 * This merges overloaded variables with defined variables.
+	 *
+	 * @return array An array of global variables
+	 */
+	public function getGlobals()
+	{
+		# Keep protected globals safe
+		return array_merge($this->globals,$this->protected_globals);
+	}
+	
+	/**
+	 * Returns the name of the extension.
+	 *
+	 * @return string The extension name
+	 */
+	public function getName()
+	{
+		return 'dcContext';
+	}
+	
+	
+	/**
+	 * Add an informational message
+	 *
+	 * @param string $message A message
+	 * @return object self
+	 */
+	public function setSafeMode($safe_mode)
+	{
+		$this->protected_globals['safe_mode'] = (boolean) $safe_mode;
+		return $this;
+	}
+	
+	/**
+	 * Add an informational message
+	 *
+	 * @param string $message A message
+	 * @return object self
+	 */
+	public function addMessageStatic($message)
+	{
+		$this->protected_globals['messages']['static'][] = $this->timeStamp($message);
+		return $this;
+	}
+	
+	/**
+	 * Add a list of informational messages
+	 *
+	 * @param string $message A title
+	 * @param array $message A list of messages
+	 * @return object self
+	 */
+	public function addMessagesList($title,$messages)
+	{
+		$this->protected_globals['messages']['lists'][$title] = $this->timeStamp($messages);
+		return $this;
+	}
+	
+	/**
+	 * Set an important message
+	 *
+	 * @param string $message A message
+	 * @return object self
+	 */
+	public function setAlert($message)
+	{
+		$this->protected_globals['messages']['alert'] = $this->timeStamp($message);
+		return $this;
+	}
+	
+	/**
+	 * Add an error message
+	 *
+	 * @param string Error message
+	 * @return object self
+	 */
+	public function addError($error)
+	{
+		$this->protected_globals['messages']['errors'][] = $this->timeStamp($error);
+		return $this;
+	}
+	
+	/**
+	 * Check if there is an error message
+	 * 
+	 * @return boolean
+	 */
+	public function hasError()
+	{
+		return !empty($this->protected_globals['messages']['errors']);
+	}
+	
+	/**
+	 * Set use of timestamp on messages
+	 *
+	 * @param boolean $add Add timestamp to messages
+	 * @return object self
+	 */
+	public function setTimestamp($add=true)
+	{
+		$this->timestamp_messages;
+		return $this;
+	}
+	
+	/**
+	 * Prepend message with a timestamp
+	 *
+	 * @param string|array $message Message(s)
+	 * @return string|array Timestamp message(s)
+	 */
+	public function timeStamp($message)
+	{
+		if (!$this->timestamp_messages) {
+			return $message;
+		}
+		
+		$tz = @$this->core->auth->getInfo('user_tz');
+		if (!$tz) {
+			$tz = $this->core->blog->settings->system->blog_timezone;
+		}
+		$dt = dt::str(__('%H:%M:%S:'),null,$tz);
+		
+		if (!is_array($message)) {
+			return $dt.' '.$message;
+		} else {
+			foreach($message as $k => $v)
+			{
+				$message[$k] = $dt.' '.$v;
+			}
+			return $message;
+		}
+	}
+	
+	/**
+	 * Get an array of debug/dev infos
+	 */
+	public function getDebugInfo()
+	{
+		if (!DC_DEBUG) {
+			return array();
+		}
+		
+		$di = array(
+			'global_vars' => implode(', ',array_keys($GLOBALS)),
+			'memory' => array(
+				'usage' => memory_get_usage(),
+				'size' => files::size(memory_get_usage())
+			),
+			'xdebug' => array()
+		);
+		
+		if (function_exists('xdebug_get_profiler_filename')) {
+		
+			$url = http::getSelfURI();
+			$url .= strpos($url,'?') === false ? '?' : '&';
+			$url .= 'XDEBUG_PROFILE';
+			
+			$di['xdebug'] = array(
+				'elapse_time' => xdebug_time_index(),
+				'profiler_file' => xdebug_get_profiler_filename(),
+				'profiler_url' =>  $url
+			);
+			
+			/* xdebug configuration:
+			zend_extension = /.../xdebug.so
+			xdebug.auto_trace = On
+			xdebug.trace_format = 0
+			xdebug.trace_options = 1
+			xdebug.show_mem_delta = On
+			xdebug.profiler_enable = 0
+			xdebug.profiler_enable_trigger = 1
+			xdebug.profiler_output_dir = /tmp
+			xdebug.profiler_append = 0
+			xdebug.profiler_output_name = timestamp
+			*/
+		}
+		
+		return $di;
+	}
+	
+	/**
+	 * Add a value in a namespace memory
+	 *
+	 * This help keep variable when recalling Twig macros
+	 *
+	 * @param string $ns A namespace
+	 * @param string $str A value to memorize in this namespace
+	 */
+	public function setMemory($ns,$str)
+	{
+		if (!array_key_exists($ns,$this->memory) || !in_array($str,$this->memory[$ns])) {
+			$this->memory[$ns][] = $str;
+		}
+	}
+	
+	/**
+	 * Check if a value is previously memorized in a namespace
+	 *
+	 * @param string $ns A namespace
+	 * @param string $str A value to search in this namespace
+	 * @return array True if exists
+	 */
+	public function getMemory($ns,$str)
+	{
+		return array_key_exists($ns,$this->memory) && in_array($str,$this->memory[$ns]);
+	}
+}
+?>
Index: inc/core/class.dc.core.php
===================================================================
--- inc/core/class.dc.core.php	(revision 2682)
+++ inc/core/class.dc.core.php	(revision 2683)
@@ -39,15 +39,16 @@
 	public $rest;		///< <b>dcRestServer</b>	dcRestServer object
 	public $log;		///< <b>dcLog</b>			dcLog object
+	public $tpl;		///< <b>Twig_Environment</b>	Twig_Environment object
 	public $stime;		///< <b>float</b>			starting time
-
+	
 	private $versions = null;
 	private $formaters = array();
 	private $behaviors = array();
 	private $post_types = array();
-
+	
 	/**
 	dcCore constructor inits everything related to Dotclear. It takes arguments
 	to init database connection.
-
+	
 	@param	driver	<b>string</b>	Database driver name
 	@param	host		<b>string</b>	Database hostname
@@ -67,5 +68,5 @@
 
 		$this->con = dbLayer::init($driver,$host,$db,$user,$password,$persist);
-
+		
 		# define weak_locks for mysql
 		if ($this->con instanceof mysqlConnection) {
@@ -74,5 +75,5 @@
 			mysqliConnection::$weak_locks = true;
 		}
-
+		
 		# define searchpath for postgresql
 		if ($this->con instanceof pgsqlConnection)
@@ -86,21 +87,21 @@
 			}
 		}
-
+		
 		$this->prefix = $prefix;
-
+		
 		$this->error = new dcError();
 		$this->auth = $this->authInstance();
 		$this->session = new sessionDB($this->con,$this->prefix.'session',DC_SESSION_NAME,'',null,DC_ADMIN_SSL);
 		$this->url = new dcUrlHandlers();
-
+		
 		$this->plugins = new dcPlugins($this);
-
+		
 		$this->rest = new dcRestServer($this);
-
+		
 		$this->meta = new dcMeta($this);
-
+		
 		$this->log = new dcLog($this);
 	}
-
+	
 	private function authInstance()
 	{
@@ -112,22 +113,57 @@
 			$c = DC_AUTH_CLASS;
 		}
-
+		
 		if (!class_exists($c)) {
 			throw new Exception('Authentication class '.$c.' does not exist.');
 		}
-
+		
 		if ($c != 'dcAuth' && !is_subclass_of($c,'dcAuth')) {
 			throw new Exception('Authentication class '.$c.' does not inherit dcAuth.');
 		}
-
+		
 		return new $c($this);
 	}
-
-
+	
+	/**
+	Create template environment (Twig_Environment instance)
+	
+	default-templates path must be added from admin|public/prepend.php with:
+	$core->tpl->getLoader()->addPath('PATH_TO/default-templates');
+	Selected theme path must be added with:
+	$core->tpl->getLoader()->prependPath('PATH_TO/MY_THEME');
+	*/
+	public function loadTemplateEnvironment()
+	{
+		$cache_dir = path::real(DC_TPL_CACHE.'/twtpl',false);
+		if (!is_dir($cache_dir)) {
+			try {
+				files::makeDir($cache_dir);
+			} catch (Exception $e) {
+				$cache_dir = false;
+			}
+		}
+		
+		$this->tpl = new Twig_Environment(
+			new Twig_Loader_Filesystem(dirname(__FILE__).'/../swf'),
+			array(
+				'auto_reload' => true,
+				'autoescape' => false,
+				'base_template_class' => 'Twig_Template',
+				'cache' => $cache_dir, 
+				'charset' => 'UTF-8',
+				'debug' => DC_DEBUG,
+				'optimizations' => -1,
+				'strict_variables' => 0 //DC_DEBUG // Please fix undefined variables!
+			)
+		);
+		$this->tpl->addExtension(new dcFormExtension($this));
+		$this->tpl->addExtension(new dcTabExtension($this));
+	}
+	
 	/// @name Blog init methods
 	//@{
 	/**
 	Sets a blog to use in <var>blog</var> property.
-
+	
 	@param	id		<b>string</b>		Blog ID
 	*/
@@ -136,5 +172,5 @@
 		$this->blog = new dcBlog($this, $id);
 	}
-
+	
 	/**
 	Unsets <var>blog</var> property.
@@ -145,11 +181,11 @@
 	}
 	//@}
-
-
+	
+	
 	/// @name Blog status methods
 	//@{
 	/**
 	Returns an array of available blog status codes and names.
-
+	
 	@return	<b>array</b> Simple array with codes in keys and names in value
 	*/
@@ -162,10 +198,10 @@
 		);
 	}
-
+	
 	/**
 	Returns a blog status name given to a code. This is intended to be
 	human-readable and will be translated, so never use it for tests.
 	If status code does not exist, returns <i>offline</i>.
-
+	
 	@param	s	<b>integer</b> Status code
 	@return	<b>string</b> Blog status name
@@ -180,13 +216,13 @@
 	}
 	//@}
-
+	
 	/// @name Admin nonce secret methods
 	//@{
-
+	
 	public function getNonce()
 	{
 		return crypt::hmac(DC_MASTER_KEY,session_id());
 	}
-
+	
 	public function checkNonce($secret)
 	{
@@ -194,8 +230,8 @@
 			return false;
 		}
-
+		
 		return $secret == crypt::hmac(DC_MASTER_KEY,session_id());
 	}
-
+	
 	public function formNonce()
 	{
@@ -203,9 +239,9 @@
 			return;
 		}
-
+		
 		return form::hidden(array('xd_check'),$this->getNonce());
 	}
 	//@}
-
+	
 	/// @name Text Formatters methods
 	//@{
@@ -225,5 +261,5 @@
 		}
 	}
-
+	
 	/// @name Text Formatters methods
 	//@{
@@ -232,5 +268,5 @@
 	transform text. The function must be a valid callback and takes one
 	argument: the string to transform. It returns the transformed string.
-
+	
 	@param	name		<b>string</b>		Formater name
 	@param	func		<b>callback</b>	Function to use, must be a valid and callable callback
@@ -256,8 +292,8 @@
 		return $editors;
 	}
-
+	
 	/**
 	Returns formaters list by editor
-
+	
 	@param	editor_id	<b>string</b>	Editor id (dcLegacyEditor, dcCKEditor, ...)
 	@return	<b>array</b> An array of formaters names in values.
@@ -288,9 +324,9 @@
 		return $formaters_list;
 	}
-
+	
 	/**
 	If <var>$name</var> is a valid formater, it returns <var>$str</var>
 	transformed using that formater.
-
+	
 	@param	editor_id	<b>string</b>	Editor id (dcLegacyEditor, dcCKEditor, ...)
 	@param	name		<b>string</b>		Formater name
@@ -303,9 +339,9 @@
 			return call_user_func($this->formaters[$editor_id][$name],$str);
 		}
-
+		
 		return $str;
 	}
 	//@}
-
+	
 	/**
 	If <var>$name</var> is a valid formater, it returns <var>$str</var>
@@ -322,5 +358,5 @@
 	//@}
 
-
+	
 	/// @name Behaviors methods
 	//@{
@@ -328,5 +364,5 @@
 	Adds a new behavior to behaviors stack. <var>$func</var> must be a valid
 	and callable callback.
-
+	
 	@param	behavior	<b>string</b>		Behavior name
 	@param	func		<b>callback</b>	Function to call
@@ -338,8 +374,8 @@
 		}
 	}
-
+	
 	/**
 	Tests if a particular behavior exists in behaviors stack.
-
+	
 	@param	behavior	<b>string</b>	Behavior name
 	@return	<b>boolean</b>
@@ -349,8 +385,8 @@
 		return isset($this->behaviors[$behavior]);
 	}
-
+	
 	/**
 	Get behaviors stack (or part of).
-
+	
 	@param	behavior	<b>string</b>		Behavior name
 	@return	<b>array</b>
@@ -359,5 +395,5 @@
 	{
 		if (empty($this->behaviors)) return null;
-
+		
 		if ($behavior == '') {
 			return $this->behaviors;
@@ -365,15 +401,15 @@
 			return $this->behaviors[$behavior];
 		}
-
+		
 		return array();
 	}
-
+	
 	/**
 	Calls every function in behaviors stack for a given behavior and returns
 	concatened result of each function.
-
+	
 	Every parameters added after <var>$behavior</var> will be pass to
 	behavior calls.
-
+	
 	@param	behavior	<b>string</b>	Behavior name
 	@return	<b>string</b> Behavior concatened result
@@ -385,16 +421,16 @@
 			$args = func_get_args();
 			array_shift($args);
-
+			
 			$res = '';
-
+			
 			foreach ($this->behaviors[$behavior] as $f) {
 				$res .= call_user_func_array($f,$args);
 			}
-
+			
 			return $res;
 		}
 	}
 	//@}
-
+	
 	/// @name Post types URLs management
 	//@{
@@ -404,9 +440,9 @@
 			$type = 'post';
 		}
-
+		
 		$url = sprintf($this->post_types[$type]['admin_url'],$post_id);
 		return $escaped ? html::escapeURL($url) : $url;
 	}
-
+	
 	public function getPostPublicURL($type,$post_url,$escaped=true)
 	{
@@ -414,9 +450,9 @@
 			$type = 'post';
 		}
-
+		
 		$url = sprintf($this->post_types[$type]['public_url'],$post_url);
 		return $escaped ? html::escapeURL($url) : $url;
 	}
-
+	
 	public function setPostType($type,$admin_url,$public_url,$label='')
 	{
@@ -427,5 +463,5 @@
 		);
 	}
-
+	
 	public function getPostTypes()
 	{
@@ -433,10 +469,10 @@
 	}
 	//@}
-
+	
 	/// @name Versions management methods
 	//@{
 	/**
 	Returns a given $module version.
-
+	
 	@param	module	<b>string</b>	Module name
 	@return	<b>string</b>	Module version
@@ -449,10 +485,10 @@
 			$strReq = 'SELECT module, version FROM '.$this->prefix.'version';
 			$rs = $this->con->select($strReq);
-
+			
 			while ($rs->fetch()) {
 				$this->versions[$rs->module] = $rs->version;
 			}
 		}
-
+		
 		if (isset($this->versions[$module])) {
 			return $this->versions[$module];
@@ -461,8 +497,8 @@
 		}
 	}
-
+	
 	/**
 	Sets $version to given $module.
-
+	
 	@param	module	<b>string</b>	Module name
 	@param	version	<b>string</b>	Module version
@@ -471,9 +507,9 @@
 	{
 		$cur_version = $this->getVersion($module);
-
+		
 		$cur = $this->con->openCursor($this->prefix.'version');
 		$cur->module = (string) $module;
 		$cur->version = (string) $version;
-
+		
 		if ($cur_version === null) {
 			$cur->insert();
@@ -481,11 +517,11 @@
 			$cur->update("WHERE module='".$this->con->escape($module)."'");
 		}
-
+		
 		$this->versions[$module] = $version;
 	}
-
+	
 	/**
 	Removes given $module version entry.
-
+	
 	@param	module	<b>string</b>	Module name
 	*/
@@ -495,19 +531,19 @@
 		'DELETE FROM '.$this->prefix.'version '.
 		"WHERE module = '".$this->con->escape($module)."' ";
-
+		
 		$this->con->execute($strReq);
-
+		
 		if (is_array($this->versions)) {
 			unset($this->versions[$module]);
 		}
 	}
-
+	
 	//@}
-
+	
 	/// @name Users management methods
 	//@{
 	/**
 	Returns a user by its ID.
-
+	
 	@param	id		<b>string</b>		User ID
 	@return	<b>record</b>
@@ -516,17 +552,17 @@
 	{
 		$params['user_id'] = $id;
-
+		
 		return $this->getUsers($params);
 	}
-
+	
 	/**
 	Returns a users list. <b>$params</b> is an array with the following
 	optionnal parameters:
-
+	
 	 - <var>q</var>: search string (on user_id, user_name, user_firstname)
 	 - <var>user_id</var>: user ID
 	 - <var>order</var>: ORDER BY clause (default: user_id ASC)
 	 - <var>limit</var>: LIMIT clause (should be an array ![limit,offset])
-
+	
 	@param	params		<b>array</b>		Parameters
 	@param	count_only	<b>boolean</b>		Only counts results
@@ -553,5 +589,5 @@
 			'WHERE NULL IS NULL ';
 		}
-
+		
 		if (!empty($params['q'])) {
 			$q = $this->con->escape(str_replace('*','%',strtolower($params['q'])));
@@ -562,14 +598,14 @@
 				') ';
 		}
-
+		
 		if (!empty($params['user_id'])) {
 			$strReq .= "AND U.user_id = '".$this->con->escape($params['user_id'])."' ";
 		}
-
+		
 		if (!$count_only) {
 			$strReq .= 'GROUP BY U.user_id,user_super,user_status,user_pwd,user_change_pwd,'.
 			'user_name,user_firstname,user_displayname,user_email,user_url,'.
 			'user_desc, user_lang,user_tz,user_post_status,user_options ';
-
+			
 			if (!empty($params['order']) && !$count_only) {
 				$strReq .= 'ORDER BY '.$this->con->escape($params['order']).' ';
@@ -578,17 +614,17 @@
 			}
 		}
-
+		
 		if (!$count_only && !empty($params['limit'])) {
 			$strReq .= $this->con->limit($params['limit']);
 		}
-
+		
 		$rs = $this->con->select($strReq);
 		$rs->extend('rsExtUser');
 		return $rs;
 	}
-
+	
 	/**
 	Create a new user. Takes a cursor as input and returns the new user ID.
-
+	
 	@param	cur		<b>cursor</b>		User cursor
 	@return	<b>string</b>
@@ -599,29 +635,29 @@
 			throw new Exception(__('You are not an administrator'));
 		}
-
+		
 		if ($cur->user_id == '') {
 			throw new Exception(__('No user ID given'));
 		}
-
+		
 		if ($cur->user_pwd == '') {
 			throw new Exception(__('No password given'));
 		}
-
+		
 		$this->getUserCursor($cur);
-
+		
 		if ($cur->user_creadt === null) {
 			$cur->user_creadt = date('Y-m-d H:i:s');
 		}
-
+		
 		$cur->insert();
-
+		
 		$this->auth->afterAddUser($cur);
-
+		
 		return $cur->user_id;
 	}
-
+	
 	/**
 	Updates an existing user. Returns the user ID.
-
+	
 	@param	id		<b>string</b>		User ID
 	@param	cur		<b>cursor</b>		User cursor
@@ -631,18 +667,18 @@
 	{
 		$this->getUserCursor($cur);
-
+		
 		if (($cur->user_id !== null || $id != $this->auth->userID()) &&
 		!$this->auth->isSuperAdmin()) {
 			throw new Exception(__('You are not an administrator'));
 		}
-
+		
 		$cur->update("WHERE user_id = '".$this->con->escape($id)."' ");
-
+		
 		$this->auth->afterUpdUser($id,$cur);
-
+		
 		if ($cur->user_id !== null) {
 			$id = $cur->user_id;
 		}
-
+		
 		# Updating all user's blogs
 		$rs = $this->con->select(
@@ -650,5 +686,5 @@
 			"WHERE user_id = '".$this->con->escape($id)."' "
 			);
-
+		
 		while ($rs->fetch()) {
 			$b = new dcBlog($this,$rs->blog_id);
@@ -656,11 +692,11 @@
 			unset($b);
 		}
-
+		
 		return $id;
 	}
-
+	
 	/**
 	Deletes a user.
-
+	
 	@param	id		<b>string</b>		User ID
 	*/
@@ -670,26 +706,26 @@
 			throw new Exception(__('You are not an administrator'));
 		}
-
+		
 		if ($id == $this->auth->userID()) {
 			return;
 		}
-
+		
 		$rs = $this->getUser($id);
-
+		
 		if ($rs->nb_post > 0) {
 			return;
 		}
-
+		
 		$strReq = 'DELETE FROM '.$this->prefix.'user '.
 				"WHERE user_id = '".$this->con->escape($id)."' ";
-
+		
 		$this->con->execute($strReq);
-
+		
 		$this->auth->afterDelUser($id);
 	}
-
+	
 	/**
 	Checks whether a user exists.
-
+	
 	@param	id		<b>string</b>		User ID
 	@return	<b>boolean</b>
@@ -700,20 +736,20 @@
 				'FROM '.$this->prefix.'user '.
 				"WHERE user_id = '".$this->con->escape($id)."' ";
-
+		
 		$rs = $this->con->select($strReq);
-
+		
 		return !$rs->isEmpty();
 	}
-
+	
 	/**
 	Returns all user permissions as an array which looks like:
-
+	
 	 - [blog_id]
 	   - [name] => Blog name
 	   - [url] => Blog URL
 	   - [p]
-		- [permission] => true
+	   	- [permission] => true
 		- ...
-
+	
 	@param	id		<b>string</b>		User ID
 	@return	<b>array</b>
@@ -725,9 +761,9 @@
 				'INNER JOIN '.$this->prefix.'blog B ON P.blog_id = B.blog_id '.
 				"WHERE user_id = '".$this->con->escape($id)."' ";
-
+		
 		$rs = $this->con->select($strReq);
-
+		
 		$res = array();
-
+		
 		while ($rs->fetch())
 		{
@@ -738,14 +774,14 @@
 			);
 		}
-
+		
 		return $res;
 	}
-
+	
 	/**
 	Sets user permissions. The <var>$perms</var> array looks like:
-
+	
 	 - [blog_id] => '|perm1|perm2|'
 	 - ...
-
+	
 	@param	id		<b>string</b>		User ID
 	@param	perms	<b>array</b>		Permissions array
@@ -756,19 +792,19 @@
 			throw new Exception(__('You are not an administrator'));
 		}
-
+		
 		$strReq = 'DELETE FROM '.$this->prefix.'permissions '.
 				"WHERE user_id = '".$this->con->escape($id)."' ";
-
+		
 		$this->con->execute($strReq);
-
+		
 		foreach ($perms as $blog_id => $p) {
 			$this->setUserBlogPermissions($id, $blog_id, $p, false);
 		}
 	}
-
+	
 	/**
 	Sets user permissions for a given blog. <var>$perms</var> is an array with
 	permissions in values
-
+	
 	@param	id			<b>string</b>		User ID
 	@param	blog_id		<b>string</b>		Blog ID
@@ -781,15 +817,15 @@
 			throw new Exception(__('You are not an administrator'));
 		}
-
+		
 		$no_perm = empty($perms);
-
+		
 		$perms = '|'.implode('|',array_keys($perms)).'|';
-
+		
 		$cur = $this->con->openCursor($this->prefix.'permissions');
-
+		
 		$cur->user_id = (string) $id;
 		$cur->blog_id = (string) $blog_id;
 		$cur->permissions = $perms;
-
+		
 		if ($delete_first || $no_perm)
 		{
@@ -797,16 +833,16 @@
 					"WHERE blog_id = '".$this->con->escape($blog_id)."' ".
 					"AND user_id = '".$this->con->escape($id)."' ";
-
+			
 			$this->con->execute($strReq);
 		}
-
+		
 		if (!$no_perm) {
 			$cur->insert();
 		}
 	}
-
+	
 	/**
 	Sets a user default blog. This blog will be selected when user log in.
-
+	
 	@param	id			<b>string</b>		User ID
 	@param	blog_id		<b>string</b>		Blog ID
@@ -815,10 +851,10 @@
 	{
 		$cur = $this->con->openCursor($this->prefix.'user');
-
+		
 		$cur->user_default_blog = (string) $blog_id;
-
+		
 		$cur->update("WHERE user_id = '".$this->con->escape($id)."'");
 	}
-
+	
 	private function getUserCursor($cur)
 	{
@@ -827,5 +863,5 @@
 			throw new Exception(__('User ID must contain at least 2 characters using letters, numbers or symbols.'));
 		}
-
+		
 		if ($cur->user_url !== null && $cur->user_url != '') {
 			if (!preg_match('|^http(s?)://|',$cur->user_url)) {
@@ -833,5 +869,5 @@
 			}
 		}
-
+		
 		if ($cur->isField('user_pwd')) {
 			if (strlen($cur->user_pwd) < 6) {
@@ -840,22 +876,22 @@
 			$cur->user_pwd = crypt::hmac(DC_MASTER_KEY,$cur->user_pwd);
 		}
-
+		
 		if ($cur->user_lang !== null && !preg_match('/^[a-z]{2}(-[a-z]{2})?$/',$cur->user_lang)) {
 			throw new Exception(__('Invalid user language code'));
 		}
-
+		
 		if ($cur->user_upddt === null) {
 			$cur->user_upddt = date('Y-m-d H:i:s');
 		}
-
+		
 		if ($cur->user_options !== null) {
 			$cur->user_options = serialize((array) $cur->user_options);
 		}
 	}
-
+	
 	/**
 	Returns user default settings in an associative array with setting names in
 	keys.
-
+	
 	@return	<b>array</b>
 	*/
@@ -869,10 +905,10 @@
 	}
 	//@}
-
+	
 	/// @name Blog management methods
 	//@{
 	/**
 	Returns all blog permissions (users) as an array which looks like:
-
+	
 	 - [user_id]
 	   - [name] => User name
@@ -881,7 +917,7 @@
 	   - [super] => (true|false) super admin
 	   - [p]
-		- [permission] => true
+	   	- [permission] => true
 		- ...
-
+	
 	@param	id			<b>string</b>		Blog ID
 	@param	with_super	<b>boolean</b>		Includes super admins in result
@@ -896,5 +932,5 @@
 		'JOIN '.$this->prefix.'permissions P ON U.user_id = P.user_id '.
 		"WHERE blog_id = '".$this->con->escape($id)."' ";
-
+		
 		if ($with_super) {
 			$strReq .=
@@ -905,9 +941,9 @@
 			'WHERE user_super = 1 ';
 		}
-
+		
 		$rs = $this->con->select($strReq);
-
+		
 		$res = array();
-
+		
 		while ($rs->fetch())
 		{
@@ -921,11 +957,11 @@
 			);
 		}
-
+		
 		return $res;
 	}
-
+	
 	/**
 	Returns a blog of given ID.
-
+	
 	@param	id		<b>string</b>		Blog ID
 	@return	<b>record</b>
@@ -934,20 +970,20 @@
 	{
 		$blog = $this->getBlogs(array('blog_id'=>$id));
-
+		
 		if ($blog->isEmpty()) {
 			return false;
 		}
-
+		
 		return $blog;
 	}
-
+	
 	/**
 	Returns a record of blogs. <b>$params</b> is an array with the following
 	optionnal parameters:
-
+	
 	 - <var>blog_id</var>: Blog ID
 	 - <var>q</var>: Search string on blog_id, blog_name and blog_url
 	 - <var>limit</var>: limit results
-
+	
 	@param	params		<b>array</b>		Parameters
 	@param	count_only	<b>boolean</b>		Count only results
@@ -958,5 +994,5 @@
 		$join = '';	// %1$s
 		$where = '';	// %2$s
-
+		
 		if ($count_only)
 		{
@@ -976,5 +1012,5 @@
 			'WHERE NULL IS NULL '.
 			'%2$s ';
-
+			
 			if (!empty($params['order'])) {
 				$strReq .= 'ORDER BY '.$this->con->escape($params['order']).' ';
@@ -982,10 +1018,10 @@
 				$strReq .= 'ORDER BY B.blog_id ASC ';
 			}
-
+			
 			if (!empty($params['limit'])) {
 				$strReq .= $this->con->limit($params['limit']);
 			}
 		}
-
+		
 		if ($this->auth->userID() && !$this->auth->isSuperAdmin())
 		{
@@ -998,9 +1034,9 @@
 			$where = 'AND blog_status IN (1,0) ';
 		}
-
+		
 		if (!empty($params['blog_id'])) {
 			$where .= "AND B.blog_id = '".$this->con->escape($params['blog_id'])."' ";
 		}
-
+		
 		if (!empty($params['q'])) {
 			$params['q'] = strtolower(str_replace('*','%',$params['q']));
@@ -1012,12 +1048,12 @@
 			') ';
 		}
-
+		
 		$strReq = sprintf($strReq,$join,$where);
 		return $this->con->select($strReq);
 	}
-
+	
 	/**
 	Creates a new blog.
-
+	
 	@param	cur			<b>cursor</b>		Blog cursor
 	*/
@@ -1027,17 +1063,17 @@
 			throw new Exception(__('You are not an administrator'));
 		}
-
+		
 		$this->getBlogCursor($cur);
-
+		
 		$cur->blog_creadt = date('Y-m-d H:i:s');
 		$cur->blog_upddt = date('Y-m-d H:i:s');
 		$cur->blog_uid = md5(uniqid());
-
+		
 		$cur->insert();
 	}
-
+	
 	/**
 	Updates a given blog.
-
+	
 	@param	id		<b>string</b>		Blog ID
 	@param	cur		<b>cursor</b>		Blog cursor
@@ -1046,10 +1082,10 @@
 	{
 		$this->getBlogCursor($cur);
-
+		
 		$cur->blog_upddt = date('Y-m-d H:i:s');
-
+		
 		$cur->update("WHERE blog_id = '".$this->con->escape($id)."'");
 	}
-
+	
 	private function getBlogCursor($cur)
 	{
@@ -1057,27 +1093,27 @@
 			&& !preg_match('/^[A-Za-z0-9._-]{2,}$/',$cur->blog_id)) ||
 			(!$cur->blog_id)) {
-			throw new Exception(__('Blog ID must contain at least 2 characters using letters, numbers or symbols.'));
-		}
-
+			throw new Exception(__('Blog ID must contain at least 2 characters using letters, numbers or symbols.')); 
+		}
+		
 		if (($cur->blog_name !== null && $cur->blog_name == '') ||
 			(!$cur->blog_name)) {
 			throw new Exception(__('No blog name'));
 		}
-
+		
 		if (($cur->blog_url !== null && $cur->blog_url == '') ||
 			(!$cur->blog_url)) {
 			throw new Exception(__('No blog URL'));
 		}
-
+		
 		if ($cur->blog_desc !== null) {
 			$cur->blog_desc = html::clean($cur->blog_desc);
 		}
 	}
-
+	
 	/**
 	Removes a given blog.
 	@warning This will remove everything related to the blog (posts,
 	categories, comments, links...)
-
+	
 	@param	id		<b>string</b>		Blog ID
 	*/
@@ -1087,14 +1123,14 @@
 			throw new Exception(__('You are not an administrator'));
 		}
-
+		
 		$strReq = 'DELETE FROM '.$this->prefix.'blog '.
 				"WHERE blog_id = '".$this->con->escape($id)."' ";
-
+		
 		$this->con->execute($strReq);
 	}
-
+	
 	/**
 	Checks if a blog exist.
-
+	
 	@param	id		<b>string</b>		Blog ID
 	@return	<b>boolean</b>
@@ -1105,13 +1141,13 @@
 				'FROM '.$this->prefix.'blog '.
 				"WHERE blog_id = '".$this->con->escape($id)."' ";
-
+		
 		$rs = $this->con->select($strReq);
-
+		
 		return !$rs->isEmpty();
 	}
-
+	
 	/**
 	Count posts on a blog
-
+	
 	@param	id		<b>string</b>		Blog ID
 	@param	type		<b>string</b>		Post type
@@ -1123,13 +1159,13 @@
 				'FROM '.$this->prefix.'post '.
 				"WHERE blog_id = '".$this->con->escape($id)."' ";
-
+		
 		if ($type) {
 			$strReq .= "AND post_type = '".$this->con->escape($type)."' ";
 		}
-
+		
 		return $this->con->select($strReq)->f(0);
 	}
 	//@}
-
+	
 	/// @name HTML Filter methods
 	//@{
@@ -1138,5 +1174,5 @@
 	tidy extension is present). If <b>enable_html_filter</b> blog setting is
 	false, returns not filtered string.
-
+	
 	@param	str	<b>string</b>		String to filter
 	@return	<b>string</b> Filtered string.
@@ -1147,5 +1183,5 @@
 			return $str;
 		}
-
+		
 		$filter = new htmlFilter;
 		$str = trim($filter->apply($str));
@@ -1153,5 +1189,5 @@
 	}
 	//@}
-
+	
 	/// @name wiki2xhtml methods
 	//@{
@@ -1160,8 +1196,8 @@
 		$this->wiki2xhtml = new wiki2xhtml;
 	}
-
+	
 	/**
 	Returns a transformed string with wiki2xhtml.
-
+	
 	@param	str		<b>string</b>		String to transform
 	@return	<b>string</b>	Transformed string
@@ -1174,5 +1210,5 @@
 		return $this->wiki2xhtml->transform($str);
 	}
-
+	
 	/**
 	Inits <var>wiki2xhtml</var> property for blog post.
@@ -1181,5 +1217,5 @@
 	{
 		$this->initWiki();
-
+		
 		$this->wiki2xhtml->setOpts(array(
 			'active_title' => 1,
@@ -1213,11 +1249,11 @@
 			'note_str' => '<div class="footnotes"><h4>Notes</h4>%s</div>'
 		));
-
+		
 		$this->wiki2xhtml->registerFunction('url:post',array($this,'wikiPostLink'));
-
+		
 		# --BEHAVIOR-- coreWikiPostInit
 		$this->callBehavior('coreInitWikiPost',$this->wiki2xhtml);
 	}
-
+	
 	/**
 	Inits <var>wiki2xhtml</var> property for simple blog comment (basic syntax).
@@ -1226,5 +1262,5 @@
 	{
 		$this->initWiki();
-
+		
 		$this->wiki2xhtml->setOpts(array(
 			'active_title' => 0,
@@ -1255,9 +1291,9 @@
 			'active_fr_syntax' => 0
 		));
-
+		
 		# --BEHAVIOR-- coreInitWikiSimpleComment
 		$this->callBehavior('coreInitWikiSimpleComment',$this->wiki2xhtml);
 	}
-
+	
 	/**
 	Inits <var>wiki2xhtml</var> property for blog comment.
@@ -1266,5 +1302,5 @@
 	{
 		$this->initWiki();
-
+		
 		$this->wiki2xhtml->setOpts(array(
 			'active_title' => 0,
@@ -1295,44 +1331,44 @@
 			'active_fr_syntax' => 0
 		));
-
+		
 		# --BEHAVIOR-- coreInitWikiComment
 		$this->callBehavior('coreInitWikiComment',$this->wiki2xhtml);
 	}
-
+	
 	public function wikiPostLink($url,$content)
 	{
-		if (!($this->blog instanceof dcBlog)) {
+		if (!($this->blog instanceof dcBlog)) { 
 			return array();
 		}
-
+		
 		$post_id = abs((integer) substr($url,5));
 		if (!$post_id) {
 			return array();
 		}
-
+		
 		$post = $this->blog->getPosts(array('post_id'=>$post_id));
 		if ($post->isEmpty()) {
 			return array();
 		}
-
+		
 		$res = array('url' => $post->getURL());
 		$post_title = $post->post_title;
-
+		
 		if ($content != $url) {
 			$res['title'] = html::escapeHTML($post->post_title);
 		}
-
+		
 		if ($content == '' || $content == $url) {
 			$res['content'] = html::escapeHTML($post->post_title);
 		}
-
+		
 		if ($post->post_lang) {
 			$res['lang'] = $post->post_lang;
 		}
-
+		
 		return $res;
 	}
 	//@}
-
+	
 	/// @name Maintenance methods
 	//@{
@@ -1340,5 +1376,5 @@
 	Creates default settings for active blog. Optionnal parameter
 	<var>defaults</var> replaces default params while needed.
-
+	
 	@param	defaults		<b>array</b>	Default parameters
 	*/
@@ -1425,19 +1461,19 @@
 			);
 		}
-
+		
 		$settings = new dcSettings($this,null);
 		$settings->addNamespace('system');
-
+		
 		foreach ($defaults as $v) {
 			$settings->system->put($v[0],$v[2],$v[1],$v[3],false,true);
 		}
 	}
-
+	
 	/**
 	Recreates entries search engine index.
-
+	
 	@param	start	<b>integer</b>		Start entry index
 	@param	limit	<b>integer</b>		Number of entry to index
-
+	
 	@return	<b>integer</b>		<var>$start</var> and <var>$limit</var> sum
 	*/
@@ -1448,26 +1484,26 @@
 		$rs = $this->con->select($strReq);
 		$count = $rs->f(0);
-
+		
 		$strReq = 'SELECT post_id, post_title, post_excerpt_xhtml, post_content_xhtml '.
 				'FROM '.$this->prefix.'post ';
-
+		
 		if ($start !== null && $limit !== null) {
 			$strReq .= $this->con->limit($start,$limit);
 		}
-
+		
 		$rs = $this->con->select($strReq,true);
-
+		
 		$cur = $this->con->openCursor($this->prefix.'post');
-
+		
 		while ($rs->fetch())
 		{
 			$words = $rs->post_title.' '.	$rs->post_excerpt_xhtml.' '.
 			$rs->post_content_xhtml;
-
+			
 			$cur->post_words = implode(' ',text::splitWords($words));
 			$cur->update('WHERE post_id = '.(integer) $rs->post_id);
 			$cur->clean();
 		}
-
+		
 		if ($start+$limit > $count) {
 			return null;
@@ -1475,11 +1511,11 @@
 		return $start+$limit;
 	}
-
+	
 	/**
 	Recreates comments search engine index.
-
+	
 	@param	start	<b>integer</b>		Start comment index
 	@param	limit	<b>integer</b>		Number of comments to index
-
+	
 	@return	<b>integer</b>		<var>$start</var> and <var>$limit</var> sum
 	*/
@@ -1490,16 +1526,16 @@
 		$rs = $this->con->select($strReq);
 		$count = $rs->f(0);
-
+		
 		$strReq = 'SELECT comment_id, comment_content '.
 				'FROM '.$this->prefix.'comment ';
-
+		
 		if ($start !== null && $limit !== null) {
 			$strReq .= $this->con->limit($start,$limit);
 		}
-
+		
 		$rs = $this->con->select($strReq);
-
+		
 		$cur = $this->con->openCursor($this->prefix.'comment');
-
+		
 		while ($rs->fetch())
 		{
@@ -1508,5 +1544,5 @@
 			$cur->clean();
 		}
-
+		
 		if ($start+$limit > $count) {
 			return null;
@@ -1514,5 +1550,5 @@
 		return $start+$limit;
 	}
-
+	
 	/**
 	Reinits nb_comment and nb_trackback in post table.
@@ -1520,5 +1556,5 @@
 	public function countAllComments()
 	{
-
+	
 		$updCommentReq = 'UPDATE '.$this->prefix.'post P '.
 			'SET nb_comment = ('.
@@ -1536,5 +1572,5 @@
 		$this->con->execute($updTrackbackReq);
 	}
-
+	
 	/**
 	Empty templates cache directory
@@ -1549,7 +1585,7 @@
 	/**
 	 Return elapsed time since script has been started
-	 @param	  $mtime <b>float</b> timestamp (microtime format) to evaluate delta from
-								  current time is taken if null
-	 @return <b>float</b>		 elapsed time
+	 @param   $mtime <b>float</b> timestamp (microtime format) to evaluate delta from
+	                       		  current time is taken if null
+	 @return <b>float</b>        elapsed time
 	 */
 	public function getElapsedTime ($mtime=null) {
Index: inc/core/class.dc.template.php
===================================================================
--- inc/core/class.dc.template.php	(revision 1128)
+++ inc/core/class.dc.template.php	(revision 1128)
@@ -0,0 +1,241 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2011 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+if (!defined('DC_RC_PATH')) { return; }
+
+/**
+ * @ingroup DC_CORE
+ * @brief Template environment.
+ *
+ * This class provides same Twig environment for both admin and public side. 
+ * It adds methods for backwards compatibility with old template engine.
+ *
+ * default-templates path must be added from admin|public/prepend.php with:
+ * $core->tpl->getLoader()->addPath('PATH_TO/default-templates');
+ * Selected theme path must be added with:
+ * $core->tpl->getLoader()->prependPath('PATH_TO/MY_THEME');
+ */
+class dcTemplate extends Twig_Environment
+{
+	protected $dc_blocks = array();
+	protected $dc_values = array();
+	
+	private $core;
+	
+	/**
+	 * Create template environment (Twig_Environment instance).
+	 * 
+	 * We keep same signature as the old dcTemplate class.
+	 */
+	public function __construct($cache_dir,$self_name,$core)
+	{
+		$this->core =& $core;
+		
+		# Check cache directory
+		$cache_dir = path::real($cache_dir.'/twtpl',false);
+		if (!is_dir($cache_dir)) {
+			try {
+				files::makeDir($cache_dir);
+			} catch (Exception $e) {
+				$cache_dir = false;
+			}
+		}
+		
+		# Create Twig environment
+		parent::__construct(
+			new Twig_Loader_Filesystem(dirname(__FILE__).'/../swf'),
+			array(
+				'auto_reload' => true,
+				'autoescape' => false,
+				'base_template_class' => 'Twig_Template',
+				'cache' => $cache_dir, 
+				'charset' => 'UTF-8',
+				'debug' => DC_DEBUG,
+				'optimizations' => -1,
+				'strict_variables' => 0 //DC_DEBUG // Please fix undefined variables!
+			)
+		);
+		
+		# Add extensions
+		$this->addExtension(new dcFormExtension($this));
+		$this->addExtension(new dcTabExtension($this));
+	}
+	
+	/// @name Old template engine compatibility methods
+	//@{
+	/**
+	 * Old style to add a template block.
+	 *
+	 * @ignore
+	 * @deprecated
+	 */
+	public function addBlock($name,$callback)
+	{
+		if (!is_callable($callback)) {
+			throw new Exception('No valid callback for '.$name);
+		}
+		
+		$this->dc_blocks[$name] = $callback;
+	}
+	
+	/**
+	 * Old style to add a template value.
+	 *
+	 * @ignore
+	 * @deprecated
+	 */
+	public function addValue($name,$callback)
+	{
+		if (!is_callable($callback)) {
+			throw new Exception('No valid callback for '.$name);
+		}
+		
+		$this->dc_values[$name] = $callback;
+	}
+	
+	/**
+	 * Old style to check if a template block exists.
+	 *
+	 * @ignore
+	 * @deprecated
+	 */
+	public function blockExists($name)
+	{
+		return isset($this->dc_blocks[$name]);
+	}
+	
+	
+	/**
+	 * Old style to check if a template value exists.
+	 *
+	 * @ignore
+	 * @deprecated
+	 */
+	public function valueExists($name)
+	{
+		return isset($this->dc_values[$name]);
+	}
+	
+	
+	/**
+	 * Old style to check if a template tag exists.
+	 *
+	 * @ignore
+	 * @deprecated
+	 */
+	public function tagExists($name)
+	{
+		return $this->blockExists($name) || $this->valueExists($name);
+	}
+	
+	
+	/**
+	 * Old style to get a template value callback.
+	 *
+	 * @ignore
+	 * @deprecated
+	 */
+	public function getValueCallback($name)
+	{
+		if ($this->valueExists($name))
+		{
+			return $this->dc_values[$name];
+		}
+		
+		return false;
+	}
+	
+	/**
+	 * Old style to get a template block callback.
+	 *
+	 * @ignore
+	 * @deprecated
+	 */
+	public function getBlockCallback($name)
+	{
+		if ($this->blockExists($name))
+		{
+			return $this->dc_blocks[$name];
+		}
+		
+		return false;
+	}
+	
+	/**
+	 * Old style to get list of template blocks.
+	 *
+	 * @ignore
+	 * @deprecated
+	 */
+	public function getBlocksList()
+	{
+		return array_keys($this->dc_blocks);
+	}
+	
+	/**
+	 * Old style to get list of template values.
+	 *
+	 * @ignore
+	 * @deprecated
+	 */
+	public function getValuesList()
+	{
+		return array_keys($this->dc_values);
+	}
+	
+	/**
+	 * Old style to add template path.
+	 *
+	 * Many plugins and themes use this method, so we keep it.
+	 *
+	 * @ignore
+	 * @deprecated
+	 */
+	public function setPath()
+	{
+		$path = array();
+		
+		foreach (func_get_args() as $v)
+		{
+			if (is_array($v)) {
+				$path = array_merge($path,array_values($v));
+			} else {
+				$path[] = $v;
+			}
+		}
+		
+		foreach ($path as $k => $v)
+		{
+			if (($v = path::real($v)) === false) {
+				unset($path[$k]);
+			}
+		}
+		
+		$path = array_unique($path);
+		
+		$this->getLoader()->setPaths($path);
+	}
+	
+	/**
+	 * Old style to get template path.
+	 *
+	 * Many plugins and themes use this method, so we keep it.
+	 *
+	 * @ignore
+	 * @deprecated
+	 */
+	public function getPath()
+	{
+		return $this->getLoader()->getPaths();
+	}
+	//@}
+}
+?>
Index: inc/core/class.dc.twig.page.php
===================================================================
--- inc/core/class.dc.twig.page.php	(revision 1014)
+++ inc/core/class.dc.twig.page.php	(revision 1014)
@@ -0,0 +1,65 @@
+<?php
+
+// common to public and admin sides @see inc/admin/prepend.php
+class dcTwigPage extends Twig_Environment
+{
+	protected $core;
+	protected $context = null;
+	
+	public function __construct($default_tpl_dir,$cache_dir,$context,$core)
+	{
+		$this->core =& $core;
+		
+		# Twig Loader
+		$loader = new Twig_Loader_Filesystem($this->checkDefaultTemplateDir($default_tpl_dir));
+		
+		# Twig Environment options
+		$options = array(
+			'auto_reload' => true,
+			'base_template_class' => 'Twig_Template',
+			'cache' => $this->setCacheDir($cache_dir),
+			'charset' => 'UTF-8',
+			'debug' => DC_DEBUG,
+			'optimizations' => -1
+		);
+		
+		# Twig Environment
+		parent::__construct($loader,$options);
+		
+		# Add context helper (public vs admin)
+		if ($context instanceof Twig_ExtensionInterface) {
+			$this->context = $context;
+			$this->addExtension($context);
+		}
+		
+		# Add form helper
+		$this->addExtension(new dcFormExtension($core));
+		
+		$this->clearCacheFiles();
+	}
+	
+	protected function checkDefaultTemplateDir($dir)
+	{
+		$dir = path::real($dir,false);
+		
+		return is_dir($dir) ? $dir : false;
+	}
+	
+	protected function setCacheDir($dir)
+	{
+		try {
+			$dir = path::real($dir,false);
+			files::makeDir($dir,true);
+			return $dir;
+		}
+		catch(Exception $e) { }
+		
+		return false;
+	}
+	
+	public function getContext()
+	{
+		return $this->context;
+	}
+}
+?>
