Index: admin/index.php
===================================================================
--- admin/index.php	(revision 2225)
+++ admin/index.php	(revision 2229)
@@ -74,57 +74,9 @@
 $__dashboard_icons = new ArrayObject();
 
-# Dashboard favorites
-$post_count = $core->blog->getPosts(array(),true)->f(0);
-$str_entries = ($post_count > 1) ? __('%d entries') : __('%d entry');
-
-$comment_count = $core->blog->getComments(array(),true)->f(0);
-$str_comments = ($comment_count > 1) ? __('%d comments') : __('%d comment');
-
-$ws = $core->auth->user_prefs->addWorkspace('favorites');
-$count = 0;
-foreach ($ws->dumpPrefs() as $k => $v) {
-	// User favorites only
-	if (!$v['global']) {
-		$fav = unserialize($v['value']);
-		if (($fav['permissions'] == '*') || $core->auth->check($fav['permissions'],$core->blog->id)) {
-			if (dc_valid_fav($fav['url'])) {
-				$count++;
-				$title = ($fav['name'] == 'posts' ? sprintf($str_entries,$post_count) : 
-					($fav['name'] == 'comments' ? sprintf($str_comments,$comment_count) : $fav['title']));
-				$__dashboard_icons[$fav['name']] = new ArrayObject(array(__($title),$fav['url'],$fav['large-icon']));
-
-				# Let plugins set their own title for favorite on dashboard
-				$core->callBehavior('adminDashboardFavsIcon',$core,$fav['name'],$__dashboard_icons[$fav['name']]);
-			}
-		}
-	}
-}	
-if (!$count) {
-	// Global favorites if any
-	foreach ($ws->dumpPrefs() as $k => $v) {
-		$fav = unserialize($v['value']);
-		if (($fav['permissions'] == '*') || $core->auth->check($fav['permissions'],$core->blog->id)) {
-			if (dc_valid_fav($fav['url'])) {
-				$count++;
-				$title = ($fav['name'] == 'posts' ? sprintf($str_entries,$post_count) : 
-					($fav['name'] == 'comments' ? sprintf($str_comments,$comment_count) : $fav['title']));
-				$__dashboard_icons[$fav['name']] = new ArrayObject(array(__($title),$fav['url'],$fav['large-icon']));
-
-				# Let plugins set their own title for favorite on dashboard
-				$core->callBehavior('adminDashboardFavsIcon',$core,$fav['name'],$__dashboard_icons[$fav['name']]);
-			}
-		}
-	}
-}
-if (!$count) {
-	// No user or global favorites, add "user pref" and "new entry" fav
-	if ($core->auth->check('usage,contentadmin',$core->blog->id)) {
-		$__dashboard_icons['new_post'] = new ArrayObject(array(__('New entry'),'post.php','images/menu/edit-b.png'));
-	}
-	$__dashboard_icons['prefs'] = new ArrayObject(array(__('My preferences'),'preferences.php','images/menu/user-pref-b.png'));
-}
+$favs = $core->favs->getUserFavorites();
+$core->favs->appendDashboardIcons($__dashboard_icons);
 
 # Latest news for dashboard
-$__dashboard_items = new ArrayObject(array(new ArrayObject,new ArrayObject));
+$__dashboard_items = new ArrayObject(array(new ArrayObject(),new ArrayObject()));
 
 $dashboardItem = 0;
Index: admin/preferences.php
===================================================================
--- admin/preferences.php	(revision 2206)
+++ admin/preferences.php	(revision 2229)
@@ -215,32 +215,13 @@
 			throw new Exception(__('No favorite selected'));
 		}
-
-		$ws = $core->auth->user_prefs->addWorkspace('favorites');
-		$user_favs = $ws->DumpLocalPrefs();
-		$count = count($user_favs);
+		$user_favs = $core->favs->getFavoriteIDs(false);
 		foreach ($_POST['append'] as $k => $v)
 		{
-			try {
-				$found = false;
-				foreach ($user_favs as $f) {
-					$f = unserialize($f['value']);
-					if ($f['name'] == $v) {
-						$found = true;
-						break;
-					}
-				}
-				if (!$found) {
-					$uid = sprintf("u%03s",$count);
-					$fav = array('name' => $_fav[$v][0],'title' => $_fav[$v][1],'url' => $_fav[$v][2],'small-icon' => $_fav[$v][3],
-						'large-icon' => $_fav[$v][4],'permissions' => $_fav[$v][5],'id' => $_fav[$v][6],'class' => $_fav[$v][7]);
-					$core->auth->user_prefs->favorites->put($uid,serialize($fav),'string');
-					$count++;
-				}
-			} catch (Exception $e) {
-				$core->error->add($e->getMessage());
-				break;
+			if ($core->favs->exists($v)) {
+				$user_favs[] = $v;
 			}
 		}
-	
+		$core->favs->setFavoriteIDs($user_favs,false);
+
 		if (!$core->error->flag()) {
 			http::redirect('preferences.php?append=1');
@@ -258,33 +239,15 @@
 			throw new Exception(__('No favorite selected'));
 		}
-		
-		$ws = $core->auth->user_prefs->addWorkspace('favorites');
-		foreach ($_POST['remove'] as $k => $v)
+		$user_fav_ids = array();
+		foreach ($core->favs->getFavoriteIDs(false) as $v) {
+			$user_fav_ids[$v]=true;
+		}
+		foreach ($_POST['remove'] as $v)
 		{
-			try {
-				$core->auth->user_prefs->favorites->drop($v);
-			} catch (Exception $e) {
-				$core->error->add($e->getMessage());
-				break;
+			if (isset($user_fav_ids[$v])) {
+				unset($user_fav_ids[$v]);
 			}
 		}
-		// Update pref_id values
-		try {
-			$user_favs = $ws->DumpLocalPrefs();
-			$core->auth->user_prefs->favorites->dropAll();
-			$count = 0;
-			foreach ($user_favs as $k => $v)
-			{
-				$uid = sprintf("u%03s",$count);
-				$f = unserialize($v['value']);
-				$fav = array('name' => $f['name'],'title' => $f['title'],'url' => $f['url'],'small-icon' => $f['small-icon'],
-					'large-icon' => $f['large-icon'],'permissions' => $f['permissions'],'id' => $f['id'],'class' => $f['class']);
-				$core->auth->user_prefs->favorites->put($uid,serialize($fav),'string');
-				$count++;
-			}
-		} catch (Exception $e) {
-			$core->error->add($e->getMessage());
-		}
-	
+		$core->favs->setFavoriteIDs(array_keys($user_fav_ids),false);
 		if (!$core->error->flag()) {
 			http::redirect('preferences.php?removed=1');
@@ -307,21 +270,10 @@
 if (!empty($_POST['saveorder']) && !empty($order))
 {
-	try {
-		$ws = $core->auth->user_prefs->addWorkspace('favorites');
-		$user_favs = $ws->DumpLocalPrefs();
-		$core->auth->user_prefs->favorites->dropAll();
-		$count = 0;
-		foreach ($order as $i => $k) {
-			$uid = sprintf("u%03s",$count);
-			$f = unserialize($user_favs[$k]['value']);
-			$fav = array('name' => $f['name'],'title' => $f['title'],'url' => $f['url'],'small-icon' => $f['small-icon'],
-				'large-icon' => $f['large-icon'],'permissions' => $f['permissions'],'id' => $f['id'],'class' => $f['class']);
-			$core->auth->user_prefs->favorites->put($uid,serialize($fav),'string');
-			$count++;
-		}
-	} catch (Exception $e) {
-		$core->error->add($e->getMessage());
-	}
-
+	foreach ($order as $k => $v) {
+		if (!$core->favs->exists($v)) {
+			unset($order[$k]);
+		}
+	}	
+	$core->favs->setFavoriteIDs($order,false);
 	if (!$core->error->flag()) {
 		http::redirect('preferences.php?&neworder=1');
@@ -331,21 +283,6 @@
 # Replace default favorites by current set (super admin only)
 if (!empty($_POST['replace']) && $core->auth->isSuperAdmin()) {
-	try {
-		$ws = $core->auth->user_prefs->addWorkspace('favorites');
-		$user_favs = $ws->DumpLocalPrefs();
-		$core->auth->user_prefs->favorites->dropAll(true);
-		$count = 0;
-		foreach ($user_favs as $k => $v)
-		{
-			$uid = sprintf("g%03s",$count);
-			$f = unserialize($v['value']);
-			$fav = array('name' => $f['name'],'title' => $f['title'],'url' => $f['url'],'small-icon' => $f['small-icon'],
-				'large-icon' => $f['large-icon'],'permissions' => $f['permissions'],'id' => $f['id'],'class' => $f['class']);
-			$core->auth->user_prefs->favorites->put($uid,serialize($fav),'string',null,null,true);
-			$count++;
-		}
-	} catch (Exception $e) {
-		$core->error->add($e->getMessage());
-	}
+	$user_favs = $core->favs->getFavoriteIDs(false);
+	$core->favs->setFavoriteIDs($user_favs,true);
 
 	if (!$core->error->flag()) {
@@ -548,20 +485,17 @@
 
 $count = 0;
-$user_fav = array();
-foreach ($ws->dumpPrefs() as $k => $v) {
-	// User favorites only
-	if (!$v['global']) {
-		$fav = unserialize($v['value']);
-		if (($fav['permissions'] == '*') || $core->auth->check($fav['permissions'],$core->blog->id)) {
-			if ($count == 0) echo '<ul class="fav-list">';
-			$count++;
-			echo '<li id="fu-'.$k.'">'.'<label for="fuk-'.$k.'">'.
-				'<img src="'.dc_admin_icon_url($fav['small-icon']).'" alt="" /> '.'<span class="zoom"><img src="'.dc_admin_icon_url($fav['large-icon']).'" alt="" /></span>'.
-				form::field(array('order['.$k.']'),2,3,$count,'position','',false,'title="'.sprintf(__('position of %s'),$fav['title']).'"').
-				form::hidden(array('dynorder[]','dynorder-'.$k.''),$k).
-				form::checkbox(array('remove[]','fuk-'.$k),$k).__($fav['title']).'</label>'.
-				'</li>';
-			$user_fav[] = $fav['name'];
-		}
+$user_fav = $core->favs->getFavoriteIDs(false);
+foreach ($user_fav as $id) {
+	$fav = $core->favs->getFavorite($id);
+	if ($fav != false) {
+		// User favorites only
+		if ($count == 0) echo '<ul class="fav-list">';
+		$count++;
+		echo '<li id="fu-'.$id.'">'.'<label for="fuk-'.$id.'">'.
+			'<img src="'.dc_admin_icon_url($fav['small-icon']).'" alt="" /> '.'<span class="zoom"><img src="'.dc_admin_icon_url($fav['large-icon']).'" alt="" /></span>'.
+			form::field(array('order['.$id.']'),2,3,$count,'position','',false,'title="'.sprintf(__('position of %s'),$fav['title']).'"').
+			form::hidden(array('dynorder[]','dynorder-'.$id.''),$id).
+			form::checkbox(array('remove[]','fuk-'.$id),$id).__($fav['title']).'</label>'.
+			'</li>';
 	}
 }
@@ -593,15 +527,9 @@
 }
 
-$default_fav = array();
-foreach ($ws->dumpPrefs() as $k => $v) {
-	// Global favorites only
-	if ($v['global']) {
-		$fav = unserialize($v['value']);
-		if (($fav['permissions'] == '*') || $core->auth->check($fav['permissions'],$core->blog->id)) {
-			$default_fav[] = $fav['name'];
-		}
-	}
-}
-
+$avail_fav = $core->favs->getFavorites($core->favs->getAvailableFavoritesIDs());
+$default_fav_ids = array();
+foreach($core->favs->getFavoriteIDs(true) as $v) {
+	$default_fav_ids[$v]=true;
+}
 echo '</div>'; # /box my-fav
 
@@ -610,27 +538,27 @@
 echo '<h5 class="pretty-title">'.__('Other available favorites').'</h5>';
 $count = 0;
-$array = $_fav;
 function cmp($a,$b) {
-    if (__($a[1]) == __($b[1])) {
+    if ($a['title'] == $b['title']) {
         return 0;
     }
-    return (__($a[1]) < __($b[1])) ? -1 : 1;
-}
-$array=$array->getArrayCopy();
-uasort($array,'cmp');
-foreach ($array as $k => $fav) {
-	if (($fav[5] == '*') || $core->auth->check($fav[5],$core->blog->id)) {
-		if (!in_array($fav[0], $user_fav)) {
-			if ($count == 0) echo '<ul class="fav-list">';
-			$count++;
-			echo '<li id="fa-'.$fav[0].'">'.'<label for="fak-'.$fav[0].'">'.
-				'<img src="'.dc_admin_icon_url($fav[3]).'" alt="" /> '.
-				'<span class="zoom"><img src="'.dc_admin_icon_url($fav[4]).'" alt="" /></span>'.
-				form::checkbox(array('append[]','fak-'.$fav[0]),$k).
-				__($fav[1]).'</label>'.
-				(in_array($fav[0], $default_fav) ? ' <span class="default-fav"><img src="images/selected.png" alt="'.__('(default favorite)').'" /></span>' : '').
-				'</li>';
-		}
-	}
+    return ($a['title'] < $b['title']) ? -1 : 1;
+}
+uasort($avail_fav,'cmp');
+
+foreach ($avail_fav as $k => $v) {
+	if (in_array($k,$user_fav)) {
+		unset($avail_fav[$k]);
+	}
+}
+foreach ($avail_fav as $k=>$fav) {
+	if ($count == 0) echo '<ul class="fav-list">';
+	$count++;
+	echo '<li id="fa-'.$k.'">'.'<label for="fak-'.$k.'">'.
+		'<img src="'.dc_admin_icon_url($fav['small-icon']).'" alt="" /> '.
+		'<span class="zoom"><img src="'.dc_admin_icon_url($fav['large-icon']).'" alt="" /></span>'.
+		form::checkbox(array('append[]','fak-'.$k),$k).
+		$fav['title'].'</label>'.
+		(isset($default_fav_ids[$k]) ? ' <span class="default-fav"><img src="images/selected.png" alt="'.__('(default favorite)').'" /></span>' : '').
+		'</li>';
 }
 if ($count > 0) echo '</ul>';
Index: inc/admin/class.dc.favorites.php
===================================================================
--- inc/admin/class.dc.favorites.php	(revision 2229)
+++ inc/admin/class.dc.favorites.php	(revision 2229)
@@ -0,0 +1,492 @@
+<?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; }
+
+/**
+* dcFavorites -- Favorites handling facilities
+*
+*/
+class dcFavorites
+{
+	/** @var dcCore dotclear core instance */
+	protected $core;
+	
+	/** @var array list of favorite definitions  */
+	protected $fav_defs;
+	
+	/** @var dcWorkspace current favorite landing workspace */
+	protected $ws;
+	
+	/** @var array list of user-defined favorite ids */
+	protected $local_prefs;
+
+	/** @var array list of globally-defined favorite ids */
+	protected $global_prefs;
+	
+	/** @var array list of user preferences (either one of the 2 above, or not!) */	
+	protected $user_prefs;
+	
+    /**
+     * Class constructor
+     * 
+     * @param mixed  $core   dotclear core
+     *
+     * @access public
+     *
+     * @return mixed Value.
+     */
+	 public function __construct($core) {
+		$this->core = $core;
+		$this->fav_defs = new ArrayObject();
+		$this->ws = $core->auth->user_prefs->addWorkspace('dashboard');
+		$this->user_prefs = array();
+		
+		if ($this->ws->prefExists('favorites')) {
+			$this->local_prefs = @unserialize($this->ws->getLocal('favorites'));
+			$this->global_prefs = @unserialize($this->ws->getGlobal('favorites'));
+			// Since we never know what user puts through user:preferences ...
+			if (!is_array($this->local_prefs)) {
+				$this->local_prefs = array();
+			}
+			if (!is_array($this->global_prefs)) {
+				$this->global_prefs = array();
+			}
+		} else {
+			// No favorite defined ? Huhu, let's go for a migration
+			$this->migrateFavorites();
+		}	
+		defaultFavorites::initDefaultFavorites($this);
+	}
+	
+	
+    /**
+     * setup - sets up favorites, fetch user favorites (against his permissions)
+     *			This method is to be called after loading plugins
+	 *
+     * @access public
+	 *
+     */
+	 public function setup() {
+		$this->legacyFavorites();
+		$this->setUserPrefs();
+	}
+
+   /**
+     * getFavorite - retrieves a favorite (complete description) from its id.
+     * 
+     * @param string  $id   the favorite id, or an array having 1 key 'name' set to id, ther keys are merged to favorite.
+     *
+     * @access public
+     *
+     * @return array the favorite, false if not found (or not permitted)
+     */
+	public function getFavorite($p) {
+		if (is_array($p)) {
+			$fname=$p['name'];
+			if (!isset($this->fav_defs[$fname])) {
+				return false;
+			}
+			$fattr = $p;
+			unset($fattr['name']);
+			$fattr = array_merge($this->fav_defs[$fname],$fattr);
+		} else {
+			if (!isset($this->fav_defs[$p])) {
+				return false;
+			}
+			$fattr = $this->fav_defs[$p];
+		}
+		$fattr = array_merge (array('id' => null,'class'=>null),$fattr);
+		if (!isset($fattr['permissions']) || $this->core->auth->check($fattr['permissions'],$this->core->blog->id)) {
+			return $fattr;
+		} else {
+			return false;
+		}
+	}
+	
+   /**
+     * getFavorites - retrieves a list of favorites.
+     * 
+     * @param string  $ids   an array of ids, as defined in getFavorite.
+     *
+     * @access public
+     *
+     * @return array array of favorites, can be empty if ids are not found (or not permitted)
+     */
+	public function getFavorites($ids) {
+		$prefs = array();
+		foreach ($ids as $id) {
+			$f = $this->getFavorite($id);
+			if ($f !== false) {
+				$prefs[$id]=$f;
+			}
+		}
+		return $prefs;
+	}
+		
+   /**
+     * setUserPrefs - get user favorites from settings. These are complete favorites, not ids only
+     * 				returned favorites are the first non-empty list from :
+	 *				 * user-defined favorites
+	 *				 * globally-defined favorites
+	 *				 * a failback list "new post" (shall never be empty)
+	 *				This method is called by ::setup()
+     * @access protected
+     *
+     */
+	protected function setUserPrefs() {
+		$this->user_prefs = $this->getFavorites($this->local_prefs);
+		if (!count($this->user_prefs)) {
+			$this->user_prefs = $this->getFavorites($this->global_prefs);
+		}
+		if (!count($this->user_prefs)) {
+			$this->user_prefs = $this->getFavorites(array('new_post'));
+		}
+
+		// Loop over prefs to enable active favorites
+		foreach ($this->user_prefs as $k=>&$v) {
+			if (isset($v['active_cb']) && is_callable($v['active_cb'])) {
+				// Use callback if defined to match whether favorite is active or not
+				$v['active'] = call_user_func($v['active_cb'],$_SERVER['REQUEST_URI'],$_REQUEST);
+			} else {
+				// Failback active detection. We test against URI name & parameters
+				$v['active']=true; // true until something proves it is false
+				$u = explode('?',$v['url'],2);
+				if (!preg_match('/'.preg_quote($u[0],"/").'/',$_SERVER['REQUEST_URI'])) {
+					$v['active'] = false; // no URI match
+				}
+				if (count($u) == 2) {
+					parse_str($u[1],$p);
+					// test against each request parameter.
+					foreach ($p as $k2 => $v2) {
+						if (!isset($_REQUEST[$k2]) || $_REQUEST[$k2] !== $v2) {
+							$v['active'] = false;
+						}
+					}
+				}
+			}
+		}
+			
+	}
+	
+   /**
+     * migrateFavorites - migrate dc < 2.6 favorites to new format
+	 *
+     * @access protected
+     *
+     */
+	protected function migrateFavorites() {
+		$fav_ws = $this->core->auth->user_prefs->addWorkspace('favorites');
+		$this->local_prefs=array();
+		$this->global_prefs=array();
+		foreach ($fav_ws->dumpPrefs() as $k => $v) {
+			$fav = @unserialize($v['value']);
+			if (is_array($fav)) {
+				if ($v['global']) {
+					$this->global_prefs[] = $fav['name'];
+				} else {
+					$this->local_prefs[] = $fav['name'];
+				}
+			}
+		}
+		$this->ws->put('favorites',serialize($this->global_prefs),'string','User favorites',true,true);
+		$this->ws->put('favorites',serialize($this->local_prefs));
+		$this->user_prefs = $this->getFavorites($this->local_prefs);
+	}
+
+	
+	
+   /**
+     * legacyFavorites - handle legacy favorites using adminDashboardFavs behavior
+	 *
+     * @access protected
+     *
+     */
+	protected function legacyFavorites() {
+		$f = new ArrayObject();
+		$this->core->callBehavior('adminDashboardFavs', $this->core, $f);
+		foreach ($f as $k => $v) {
+			$fav = array (
+				'title' => __($v[1]),
+				'url' => $v[2],
+				'small-icon' => $v[3],
+				'large-icon' => $v[4],
+				'id' => $v[5],
+				'class' => $v[6]
+			);
+			$this->register ($v[0], $fav);
+		}
+		
+	}
+	
+   /**
+     * getUserFavorites - returns favorites that correspond to current user
+	 *   (may be local, global, or failback favorites)
+	 *
+     * @access public
+     *
+     * @return array array of favorites (enriched)
+     */
+	public function getUserFavorites() {
+		return $this->user_prefs;
+	}
+	
+
+   /**
+     * getFavoriteIDs - returns user-defined or global favorites ids list
+	 *					shall not be called outside preferences.php...
+	 *
+     * @param boolean  $global   if true, retrieve global favs, user favs otherwise
+	 *
+     * @access public
+     *
+     * @return array array of favorites ids (only ids, not enriched)
+     */
+	public function getFavoriteIDs ($global=false) {
+		return $global?$this->global_prefs:$this->local_prefs;
+	}
+
+   /**
+     * setFavoriteIDs - stores user-defined or global favorites ids list
+	 *					shall not be called outside preferences.php...
+	 *
+     * @param array  $ids   list of fav ids
+     * @param boolean  $global   if true, retrieve global favs, user favs otherwise
+	 *
+     * @access public
+     */
+	public function setFavoriteIDs($ids,$global=false) {
+		$this->ws->put('favorites',serialize($ids),null,null,true,$global);
+	}
+	
+   /**
+     * getAvailableFavoritesIDs - returns all available fav ids
+	 *
+     * @access public
+     *
+     * @return array array of favorites ids (only ids, not enriched)
+     */
+	public function getAvailableFavoritesIDs () {
+		return array_keys($this->fav_defs->getArrayCopy());
+	}
+
+   /**
+     * appendMenuTitle - adds favorites section title to sidebar menu
+	 *					shall not be called outside admin/prepend.php...
+	 *
+     * @param dcMenu  $menu   admin menu instance
+	 *
+     * @access public
+     */
+	public function appendMenuTitle($menu) {
+		$menu['Favorites'] = new dcMenu('favorites-menu','My favorites');
+		$menu['Favorites']->title = __('My favorites');
+	}
+
+   /**
+     * appendMenu - adds favorites items title to sidebar menu
+	 *					shall not be called outside admin/prepend.php...
+	 *
+     * @param dcMenu  $menu   admin menu instance
+	 *
+     * @access public
+     */
+	public function appendMenu($menu) {
+		foreach ($this->user_prefs as $k=>$v) {
+			$menu['Favorites']->addItem(
+				$v['title'],
+				$v['url'],
+				$v['small-icon'],
+				$v['active'],
+				true,
+				$v['id'],
+				$v['class']
+			);
+		}
+	}
+	
+   /**
+     * appendDashboardIcons - adds favorites icons to index page
+	 *					shall not be called outside admin/index.php...
+	 *
+     * @param array  $icons   dashboard icon list to enrich
+	 *
+     * @access public
+     */
+	public function appendDashboardIcons($icons) {
+		foreach ($this->user_prefs as $k=>$v) {
+			if (isset($v['title_cb']) && is_callable($v['title_cb'])) {
+				$title = call_user_func($v['title_cb'],$this->core);
+			} else {
+				$title = $v['title'];
+			}
+			$icons[$k]=new ArrayObject(array($title,$v['url'],$v['large-icon']));
+			$this->core->callBehavior('adminDashboardFavsIcon',$this->core,$k,$icons[$k]);
+		}		
+	}
+	
+   /**
+     * register - registers a new favorite definition
+	 *
+     * @param string  $id   favorite id
+	 * @param array  $data favorite information. Array keys are :
+	 *	'title' => favorite title (localized)
+	 *	'url' => favorite URL,
+	 *	'small-icon' => favorite small icon (for menu)
+	 *	'large-icon' => favorite large icon (for dashboard)
+	 *	'permissions' => (optional) comma-separated list of permissions for thie fav, if not set : no restriction
+	 *	'title_cb' => (optional) callback to modify title if dynamic, if not set : title is taken as is
+	 *	'active_cb' => (optional) callback to tell whether current page matches favorite or not, for complex pages
+	 *
+     * @access public
+     */
+	public function register($id,$data) {
+		$this->fav_defs[$id] = $data;
+		return $this;
+	}
+	
+   /**
+     * registerMultiple - registers a list of favorites definition
+	 *
+     * @param array an array defining all favorites key is the id, value is the data. 
+	 *				see register method for data format
+     * @access public
+     */	
+	 public function registerMultiple($data) {
+		foreach ($data as $k=>$v) {
+			$this->register($k,$v);
+		}
+		return $this;
+	}
+	
+   /**
+     * exists - tells whether a fav definition exists or not
+	 *
+     * @param string $id : the fav id to test
+	 *
+     * @access public
+	 *
+	 * @return true if the fav definition exists, false otherwise
+     */	
+	public function exists($id) {
+		return isset($this->fav_defs[$id]);
+	}
+	
+}
+
+
+/**
+* defaultFavorites -- default favorites definition
+*
+*/
+class defaultFavorites
+{
+	public static function initDefaultFavorites($favs) {
+		$favs->registerMultiple(array(
+			'prefs' => array(
+				'title' => __('My preferences'),
+				'url' => 'preferences.php',
+				'small-icon' => 'images/menu/user-pref.png',
+				'large-icon' => 'images/menu/user-pref-b.png'),
+			'new_post' => array(
+				'title' => __('New entry'),
+				'url' => 'post.php',
+				'small-icon' => 'images/menu/edit.png',
+				'large-icon' => 'images/menu/edit-b.png',
+				'permissions' =>'usage,contentadmin',
+				'id' => 'menu-new-post'),
+			'posts' => array(
+				'title' => __('Entries'),
+				'url' => 'posts.php',
+				'small-icon' => 'images/menu/entries.png',
+				'large-icon' => 'images/menu/entries-b.png',
+				'permissions' => 'usage,contentadmin',
+				'title_cb' => array('defaultFavorites','postsTitle')),
+			'comments' => array(
+				'title' => __('Comments'),
+				'url' => 'comments.php',
+				'small-icon' => 'images/menu/comments.png',
+				'large-icon' => 'images/menu/comments-b.png',
+				'permissions' => 'usage,contentadmin',
+				'title_cb' => array('defaultFavorites','commentsTitle')),
+			'search' => array(
+				'title' => __('Search'),
+				'url' => 'search.php',
+				'small-icon' => 'images/menu/search.png',
+				'large-icon' => 'images/menu/search-b.png',
+				'permissions' => 'usage,contentadmin'),
+			'categories' => array(
+				'title' => __('Categories'),
+				'url' => 'categories.php',
+				'small-icon' => 'images/menu/categories.png',
+				'large-icon' => 'images/menu/categories-b.png',
+				'permissions' =>'categories'),
+			'media' => array(
+				'title' => __('Media manager'),
+				'url' => 'media.php',
+				'small-icon' => 'images/menu/media.png',
+				'large-icon' => 'images/menu/media-b.png',
+				'permissions' => 'media, media_admin'),
+			'blog_pref' => array(
+				'title' => __('Blog settings'),
+				'url' => 'blog_pref.php',
+				'small-icon' => 'images/menu/blog-pref.png',
+				'large-icon' => 'images/menu/blog-pref-b.png',
+				'permissions' => 'admin'),
+			'blog_theme' => array(
+				'title' => __('Blog appearance'),
+				'url' => 'blog_theme.php',
+				'small-icon' => 'images/menu/themes.png',
+				'large-icon' => 'images/menu/blog-theme-b.png',
+				'permissions' => 'admin'),
+			'blogs' => array(
+				'title' => __('Blogs'),
+				'url' => 'blogs.php',
+				'small-icon' => 'images/menu/blogs.png',
+				'large-icon' => 'images/menu/blogs-b.png',
+				'permissions' =>'usage,contentadmin'),
+			'users' => array(
+				'title' => __('Users'),
+				'url' => 'users.php',
+				'small-icon' => 'images/menu/users.png',
+				'large-icon' => 'images/menu/users-b.png'),
+			'plugins' => array(
+				'title' => __('Plugins management'),
+				'url' => 'plugins.php',
+				'small-icon' => 'images/menu/plugins.png',
+				'large-icon' => 'images/menu/plugins-b.png'), 
+			'langs' => array(
+				'title' => __('Languages'),
+				'url' => 'langs.php',
+				'small-icon' => 'images/menu/langs.png',
+				'large-icon' => 'images/menu/langs-b.png'),
+			'help' => array(
+				'title' => __('Global help'),
+				'url' => 'help.php',
+				'small-icon' => 'images/menu/help.png',
+				'large-icon' => 'images/menu/help-b.png')
+		));
+	}
+	
+	public static function postsTitle($core) {
+		$post_count = $core->blog->getPosts(array(),true)->f(0);
+		$str_entries = __('%d entry', '%d entries',$post_count);
+		return sprintf($str_entries,$post_count);
+	}
+	
+	public static function commentsTitle($core) {
+		$comment_count = $core->blog->getComments(array(),true)->f(0);
+		$str_comments = __('%d comments', '%d comments',$comment_count);
+		return sprintf($str_comments,$comment_count);
+	}
+
+
+}
Index: inc/admin/prepend.php
===================================================================
--- inc/admin/prepend.php	(revision 2159)
+++ inc/admin/prepend.php	(revision 2229)
@@ -21,34 +21,4 @@
 // HTTP/1.0
 header("Pragma: no-cache");
-
-function dc_valid_fav($url) {
-	global $core;
-
-	if (preg_match('#plugin\.php\?p=([^&]+)#',$url,$matches)) {
-		if (isset($matches[1])) {
-			if (!$core->plugins->moduleExists($matches[1])) {
-				return false;
-			}
-		}
-	}
-	return true;
-}
-
-function dc_check_active($url) {
-	$u = explode('?',$url,2);
-	if (!preg_match('/'.preg_quote($u[0],"/").'/',$_SERVER['REQUEST_URI'])) {
-		return false;
-	}
-	if (count($u) == 2) {
-		parse_str($u[1],$p);
-		foreach ($p as $k => $v) {
-			if (!isset($_REQUEST[$k]) || $_REQUEST[$k] !== $v) {
-				return false;
-			}
-		}
-	}
-	return true;
-}
-
 
 function dc_load_locales() {
@@ -230,69 +200,28 @@
 	$user_ui_nofavmenu = $core->auth->user_prefs->interface->nofavmenu;
 
-	# Standard favorites
-	$_fav = new ArrayObject();
+	$core->favs = new dcFavorites($core);
+
 
 	# [] : Title, URL, small icon, large icon, permissions, id, class
 	# NB : '*' in permissions means any, null means super admin only
 	
-	$_fav['prefs'] = new ArrayObject(array('prefs','My preferences','preferences.php',
-		'images/menu/user-pref.png','images/menu/user-pref-b.png',
-		'*',null,null));
-
-	$_fav['new_post'] = new ArrayObject(array('new_post','New entry','post.php',
-		'images/menu/edit.png','images/menu/edit-b.png',
-		'usage,contentadmin',null,'menu-new-post'));
-	$_fav['posts'] = new ArrayObject(array('posts','Entries','posts.php',
-		'images/menu/entries.png','images/menu/entries-b.png',
-		'usage,contentadmin',null,null));
-	$_fav['comments'] = new ArrayObject(array('comments','Comments','comments.php',
-		'images/menu/comments.png','images/menu/comments-b.png',
-		'usage,contentadmin',null,null));
-	$_fav['search'] = new ArrayObject(array('search','Search','search.php',
-		'images/menu/search.png','images/menu/search-b.png',
-		'usage,contentadmin',null,null));
-	$_fav['categories'] = new ArrayObject(array('categories','Categories','categories.php',
-		'images/menu/categories.png','images/menu/categories-b.png',
-		'categories',null,null));
-	$_fav['media'] = new ArrayObject(array('media','Media manager','media.php',
-		'images/menu/media.png','images/menu/media-b.png',
-		'media,media_admin',null,null));
-	$_fav['blog_pref'] = new ArrayObject(array('blog_pref','Blog settings','blog_pref.php',
-		'images/menu/blog-pref.png','images/menu/blog-pref-b.png',
-		'admin',null,null));
-	$_fav['blog_theme'] = new ArrayObject(array('blog_theme','Blog appearance','blog_theme.php',
-		'images/menu/themes.png','images/menu/blog-theme-b.png',
-		'admin',null,null));
-
-	$_fav['blogs'] = new ArrayObject(array('blogs','Blogs','blogs.php',
-		'images/menu/blogs.png','images/menu/blogs-b.png',
-		'usage,contentadmin',null,null));
-	$_fav['users'] = new ArrayObject(array('users','Users','users.php',
-		'images/menu/users.png','images/menu/users-b.png',
-		null,null,null));
-	$_fav['plugins'] = new ArrayObject(array('plugins','Plugins management','plugins.php',
-		'images/menu/plugins.png','images/menu/plugins-b.png',
-		null,null,null));
-	$_fav['langs'] = new ArrayObject(array('langs','Languages','langs.php',
-		'images/menu/langs.png','images/menu/langs-b.png',
-		null,null,null));
-
-	$_fav['help'] = new ArrayObject(array('help','Global help','help.php',
-		'images/menu/help.png','images/menu/help-b.png',
-		'*',null,null));
 	
 	# Menus creation
+	$_menu = new ArrayObject();
 	$_menu['Dashboard'] = new dcMenu('dashboard-menu',null);
-	if (!$user_ui_nofavmenu)
-		$_menu['Favorites'] = new dcMenu('favorites-menu','My favorites');
+	if (!$user_ui_nofavmenu) {
+		$core->favs->appendMenuTitle($_menu);
+	}
 	$_menu['Blog'] = new dcMenu('blog-menu','Blog');
 	$_menu['System'] = new dcMenu('system-menu','System');
 	$_menu['Plugins'] = new dcMenu('plugins-menu','Plugins');
-	
 	# Loading plugins
 	$core->plugins->loadModules(DC_PLUGINS_ROOT,'admin',$_lang);
-
-	# Loading favorites info from plugins
-	$core->callBehavior('adminDashboardFavs', $core, $_fav);
+	$core->favs->setup();
+	
+	if (!$user_ui_nofavmenu) {
+		$core->favs->appendMenu($_menu);
+	}
+
 	
 	# Set menu titles
@@ -301,6 +230,5 @@
 	$_menu['Blog']->title = __('Blog');
 	$_menu['Plugins']->title = __('Plugins');
-	if (!$user_ui_nofavmenu)
-		$_menu['Favorites']->title = __('My favorites');
+
 
 /*	
@@ -352,40 +280,4 @@
 		$core->auth->isSuperAdmin() ||
 		$core->auth->check('usage,contentadmin',$core->blog->id) && $core->auth->getBlogCount() > 1);
-
-	if (!$user_ui_nofavmenu) {
-		// Set favorites menu
-		$ws = $core->auth->user_prefs->addWorkspace('favorites');
-		$count = 0;
-		foreach ($ws->dumpPrefs() as $k => $v) {
-			// User favorites only
-			if (!$v['global']) {
-				$fav = unserialize($v['value']);
-				if (dc_valid_fav($fav['url'])) {
-					$count++;
-					$_menu['Favorites']->addItem(__($fav['title']),$fav['url'],$fav['small-icon'],
-						dc_check_active($fav['url']),
-						(($fav['permissions'] == '*') || $core->auth->check($fav['permissions'],$core->blog->id)),$fav['id'],$fav['class']);
-				}
-			}
-		}	
-		if (!$count) {
-			// Global favorites if any
-			foreach ($ws->dumpPrefs() as $k => $v) {
-				$fav = unserialize($v['value']);
-				if (dc_valid_fav($fav['url'])) {
-					$count++;
-					$_menu['Favorites']->addItem(__($fav['title']),$fav['url'],$fav['small-icon'],
-						dc_check_active($fav['url']),
-						(($fav['permissions'] == '*') || $core->auth->check($fav['permissions'],$core->blog->id)),$fav['id'],$fav['class']);
-				}
-			}
-		}
-		if (!$count) {
-			// No user or global favorites, add "new entry" fav
-			$_menu['Favorites']->addItem(__('New entry'),'post.php','images/menu/edit.png',
-				preg_match('/post.php$/',$_SERVER['REQUEST_URI']),
-				$core->auth->check('usage,contentadmin',$core->blog->id),'menu-new-post',null);
-		}
-	}
 	
 	if (empty($core->blog->settings->system->jquery_migrate_mute)) {
Index: inc/core/class.dc.workspace.php
===================================================================
--- inc/core/class.dc.workspace.php	(revision 1179)
+++ inc/core/class.dc.workspace.php	(revision 2229)
@@ -127,5 +127,34 @@
 		return null;
 	}
-	
+
+	/**
+	Returns global pref value if exists.
+	
+	@param	n		<b>string</b>		Pref name
+	@return	<b>mixed</b>
+	*/
+	public function getGlobal($n)
+	{
+		if (isset($this->global_prefs[$n]['value'])) {
+			return $this->global_prefs[$n]['value'];
+		}
+		
+		return null;
+	}
+	
+	/**
+	Returns local pref value if exists.
+	
+	@param	n		<b>string</b>		Pref name
+	@return	<b>mixed</b>
+	*/
+	public function getLocal($n)
+	{
+		if (isset($this->local_prefs[$n]['value'])) {
+			return $this->local_prefs[$n]['value'];
+		}
+		
+		return null;
+	}	
 	/**
 	Magic __get method.
Index: inc/prepend.php
===================================================================
--- inc/prepend.php	(revision 1999)
+++ inc/prepend.php	(revision 2229)
@@ -46,4 +46,5 @@
 $__autoload['dcWorkspace']			= dirname(__FILE__).'/core/class.dc.workspace.php';
 $__autoload['dcPrefs']				= dirname(__FILE__).'/core/class.dc.prefs.php';
+$__autoload['dcFavorites']			= dirname(__FILE__).'/admin/class.dC.favorites.php';
 
 $__autoload['rsExtPost']				= dirname(__FILE__).'/core/class.dc.rs.extensions.php';
