Index: .hgsubstate
===================================================================
--- .hgsubstate	(revision 1624)
+++ .hgsubstate	(revision 1693)
@@ -1,1 +1,1 @@
-f5e0517435fc94082b6acaad5f4f111252b6149f inc/libs/clearbricks
+3811959404614e7014b897d3d3b96caad10e214f inc/libs/clearbricks
Index: admin/blog_pref.php
===================================================================
--- admin/blog_pref.php	(revision 1636)
+++ admin/blog_pref.php	(revision 1698)
@@ -580,5 +580,14 @@
 			$user_url_p = '%1$s';
 		}
-		
+
+		# Sort users list on user_id key
+		ksort($blog_users);
+
+		$post_type = $core->getPostTypes();
+		$current_blog_id = $core->blog->id;
+		if ($blog_id != $core->blog->id) {
+			$core->setBlog($blog_id);
+		}
+
 		foreach ($blog_users as $k => $v)
 		{
@@ -586,23 +595,47 @@
 			{
 				echo
+				'<div class="user-perm">'.
 				'<h4>'.sprintf($user_url_p,html::escapeHTML($k)).
 				' ('.html::escapeHTML(dcUtils::getUserCN(
 					$k, $v['name'], $v['firstname'], $v['displayname']
 				)).')</h4>';
-				
-				echo '<ul class="nice">';
+
+				if ($core->auth->isSuperAdmin()) {
+					echo 
+					'<p>'.__('Email:').' '.
+					($v['email'] != '' ? '<a href="mailto:'.$v['email'].'">'.$v['email'].'</a>' : __('(none)')).
+					'</p>';
+				}
+
+				echo
+				'<h5>'.__('Publications on this blog:').'</h5>'.
+				'<ul>';
+				foreach ($post_type as $type => $pt_info) {
+					$params = array(
+						'post_type' => $type,
+						'user_id' => $k
+						);
+					echo '<li>'.sprintf(__('%1$s: %2$s'),__($pt_info['label']),$core->blog->getPosts($params,true)->f(0)).'</li>';
+				}
+				echo
+				'</ul>';
+
+				echo
+				'<h5>'.__('Permissions:').'</h5>'.
+				'<ul>';
 				if ($v['super']) {
-					echo '<li>'.__('Super administrator').'</li>';
+					echo '<li class="user_super">'.__('Super administrator').'</li>';
 				} else {
 					foreach ($v['p'] as $p => $V) {
-						echo '<li>'.__($perm_types[$p]).'</li>';
+						echo '<li '.($p == 'admin' ? 'class="user_admin"' : '').'>'.__($perm_types[$p]).'</li>';
 					}
 				}
-				echo '</ul>';
+				echo 
+				'</ul>';
 				
 				if (!$v['super'] && $core->auth->isSuperAdmin()) {
 					echo 
 					'<form action="users_actions.php" method="post">'.
-					'<p><input type="submit" value="'.__('Change permissions').'" />'.
+					'<p class="change-user-perm"><input type="submit" class="reset" value="'.__('Change permissions').'" />'.
 					form::hidden(array('redir'),'blog_pref.php?id='.$k).
 					form::hidden(array('action'),'perms').
@@ -613,5 +646,9 @@
 					'</form>';
 				}
+				echo '</div>';
 			}
+		}
+		if ($current_blog_id != $core->blog->id) {
+			$core->setBlog($current_blog_id);
 		}
 	}
Index: admin/index.php
===================================================================
--- admin/index.php	(revision 1620)
+++ admin/index.php	(revision 1699)
@@ -63,4 +63,11 @@
 	$core->auth->user_prefs->dashboard->put('quickentry',false,'boolean');
 }
+
+// Handle folded/unfolded sections in admin from user preferences
+$ws = $core->auth->user_prefs->addWorkspace('toggles');
+if (!$core->auth->user_prefs->toggles->prefExists('unfolded_sections')) {
+	$core->auth->user_prefs->toggles->put('unfolded_sections','','string','Folded sections in admin',null,true);
+}
+
 
 # Dashboard icons
@@ -210,11 +217,11 @@
 
 	if ($updater->getNotify() && $new_v) {
-		$message =
-		'<div><p>'.sprintf(__('Dotclear %s is available!'),$new_v).'</p> '.
-		'<ul><li><strong><a href="update.php">'.sprintf(__('Upgrade now'),$new_v).'</a></strong>'.
-		'</li><li><a href="update.php?hide_msg=1">'.__('Remind me later').'</a>'.
-		($version_info ? ' </li><li><a href="'.$version_info.'">'.__('information about this version').'</a>' : '').
-		'</li></ul></div>';
-		dcPage::message($message,false,true);
+		echo
+		'<div class="dc-update"><h3>'.sprintf(__('Dotclear %s is available!'),$new_v).'</h3> '.
+		'<p><a class="button submit" href="update.php">'.sprintf(__('Upgrade now'),$new_v).'</a> '.
+		'<a href="update.php?hide_msg=1">'.__('Remind me later').'</a>'.
+		($version_info ? ' </p>'.
+		'<p><a href="'.$version_info.'" class="info">'.__('information about this version').'</a>' : '').'</p>'.
+		'</div>';
 	}
 }
Index: admin/js/_index.js
===================================================================
--- admin/js/_index.js	(revision 1638)
+++ admin/js/_index.js	(revision 1699)
@@ -75,5 +75,5 @@
 	$('#quick h3').toggleWithLegend($('#quick').children().not('h3'),{
 		legend_click: true,
-		cookie: 'dcx_quick_entry'
+		user_pref: 'dcx_quick_entry'
 	});
 });
Index: admin/js/_post.js
===================================================================
--- admin/js/_post.js	(revision 1712)
+++ admin/js/_post.js	(revision 1714)
@@ -120,5 +120,5 @@
 		// Hide some fields
 		$('#notes-area label').toggleWithLegend($('#notes-area').children().not('label'),{
-			cookie: 'dcx_post_notes',
+			user_pref: 'dcx_post_notes',
 			legend_click:true,
 			hide: $('#post_notes').val() == ''
@@ -129,26 +129,26 @@
 		});
 		$('#post_lang').parent().children('label').toggleWithLegend($('#post_lang'),{
-			cookie: 'dcx_post_lang',
+			user_pref: 'dcx_post_lang',
 			legend_click: true
 		});
 		$('#post_password').parent().children('label').toggleWithLegend($('#post_password'),{
-			cookie: 'dcx_post_password',
+			user_pref: 'dcx_post_password',
 			legend_click: true,
 			hide: $('#post_password').val() == ''
 		});
 		$('#post_status').parent().children('label').toggleWithLegend($('#post_status'),{
-			cookie: 'dcx_post_status',
+			user_pref: 'dcx_post_status',
 			legend_click: true
 		});
 		$('#post_dt').parent().children('label').toggleWithLegend($('#post_dt').parent().children().not('label'),{
-			cookie: 'dcx_post_dt',
+			user_pref: 'dcx_post_dt',
 			legend_click: true
 		});
 		$('#label_format').toggleWithLegend($('#label_format').parent().children().not('#label_format'),{
-			cookie: 'dcx_post_format',
+			user_pref: 'dcx_post_format',
 			legend_click: true
 		});
 		$('#cat_id').parent().children('label').toggleWithLegend($('#cat_id'),{
-			cookie: 'cat_id',
+			user_pref: 'cat_id',
 			legend_click: true
 		});
@@ -158,10 +158,10 @@
 		});
 		$('#post_url').parent().children('label').toggleWithLegend($('#post_url').parent().children().not('label'),{
-			cookie: 'post_url',
+			user_pref: 'post_url',
 			legend_click: true
 		});
 		// We load toolbar on excerpt only when it's ready
 		$('#excerpt-area label').toggleWithLegend($('#excerpt-area').children().not('label'),{
-			cookie: 'dcx_post_excerpt',
+			user_pref: 'dcx_post_excerpt',
 			legend_click: true,
 			hide: $('#post_excerpt').val() == ''
Index: admin/js/common.js
===================================================================
--- admin/js/common.js	(revision 1635)
+++ admin/js/common.js	(revision 1699)
@@ -63,10 +63,13 @@
 		img_off_src: dotclear.img_minus_src,
 		img_off_alt: dotclear.img_minus_alt,
+		unfolded_sections: dotclear.unfolded_sections,
 		hide: true,
 		speed: 0,
 		legend_click: false,
 		fn: false, // A function called on first display,
-		cookie: false,
-		reverse_cookie: false // Reverse cookie behavior
+		user_pref: false,
+		reverse_user_pref: false, // Reverse cookie behavior
+		user_pref:false,
+		reverse_user_pref: false
 	};
 	var p = jQuery.extend(defaults,s);
@@ -78,5 +81,9 @@
 		p.hide = p.reverse_cookie;
 	}
-
+	
+	var set_user_pref = p.hide ^ p.reverse_user_pref;
+	if (p.user_pref && (p.user_pref in p.unfolded_sections)) {
+		p.hide = p.reverse_user_pref;
+	}
 	var toggle = function(i,speed) {
 		speed = speed || 0;
@@ -102,5 +109,4 @@
 			}
 		}
-
 		p.hide = !p.hide;
 	};
@@ -125,4 +131,16 @@
 		}
 		$(ctarget).click(function() {
+			if (p.user_pref && set_user_pref) {
+				if (p.hide ^ p.reverse_user_pref) {
+					jQuery.post('services.php', 
+						{'f':'setSectionFold','section':p.user_pref,'value':1,xd_check: dotclear.nonce},
+						function(data) {});
+				} else {
+					jQuery.post('services.php', 
+						{'f':'setSectionFold','section':p.user_pref,'value':0,xd_check: dotclear.nonce},
+						function(data) {});
+				}
+				jQuery.cookie(p.user_pref,'',{expires: -1});
+			}
 			toggle(i,p.speed);
 			return false;
@@ -348,14 +366,14 @@
 	}
 	$('#blog-menu h3:first').toggleWithLegend($('#blog-menu ul:first'),
-		$.extend({cookie:'dc_blog_menu'},menu_settings)
+		$.extend({user_pref:'dc_blog_menu'},menu_settings)
 	);
 	$('#system-menu h3:first').toggleWithLegend($('#system-menu ul:first'),
-		$.extend({cookie:'dc_system_menu'},menu_settings)
+		$.extend({user_pref:'dc_system_menu'},menu_settings)
 	);
 	$('#plugins-menu h3:first').toggleWithLegend($('#plugins-menu ul:first'),
-		$.extend({cookie:'dc_plugins_menu'},menu_settings)
+		$.extend({user_pref:'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)
+		$.extend({user_pref:'dc_favorites_menu',hide:false,reverse_user_pref:true},menu_settings)
 	);
 
Index: admin/js/tool-man/cookies.js
===================================================================
--- admin/js/tool-man/cookies.js	(revision 0)
+++ admin/js/tool-man/cookies.js	(revision 0)
@@ -0,0 +1,34 @@
+/* Copyright (c) 2005 Tim Taylor Consulting (see LICENSE.txt)
+
+based on http://www.quirksmode.org/js/cookies.html
+*/
+
+ToolMan._cookieOven = {
+	
+	set : function(name, value, expirationInDays) {
+		if (expirationInDays) {
+			var date = new Date()
+			date.setTime(date.getTime() + (expirationInDays * 24 * 60 * 60 * 1000))
+			var expires = "; expires=" + date.toGMTString()
+		} else {
+			var expires = ""
+		}
+		document.cookie = name + "=" + value + expires + "; path=/"
+	},
+	
+	get : function(name) {
+		var namePattern = name + "="
+		var cookies = document.cookie.split(';')
+		for(var i = 0, n = cookies.length; i < n; i++) {
+			var c = cookies[i]
+			while (c.charAt(0) == ' ') c = c.substring(1, c.length)
+			if (c.indexOf(namePattern) == 0)
+				return c.substring(namePattern.length, c.length)
+		}
+		return null
+	},
+	
+	eraseCookie : function(name) {
+		createCookie(name, "", -1)
+	}
+};
Index: admin/js/tool-man/coordinates.js
===================================================================
--- admin/js/tool-man/coordinates.js	(revision 0)
+++ admin/js/tool-man/coordinates.js	(revision 0)
@@ -0,0 +1,154 @@
+/* Copyright (c) 2005 Tim Taylor Consulting (see LICENSE.txt) */
+
+/* FIXME: assumes position styles are specified in 'px' */
+
+ToolMan._coordinatesFactory = {
+	
+	create : function(x, y) {
+		// FIXME: Safari won't parse 'throw' and aborts trying to do anything with this file
+		//if (isNaN(x) || isNaN(y)) throw "invalid x,y: " + x + "," + y
+		return new _ToolManCoordinate(this, x, y)
+	},
+	
+	origin : function() {
+		return this.create(0, 0)
+	},
+	
+	/*
+	 * FIXME: Safari 1.2, returns (0,0) on absolutely positioned elements
+	 */
+	topLeftPosition : function(element) {
+		var left = parseInt(ToolMan.css().readStyle(element, "left"))
+		var left = isNaN(left) ? 0 : left
+		var top = parseInt(ToolMan.css().readStyle(element, "top"))
+		var top = isNaN(top) ? 0 : top
+		
+		return this.create(left, top)
+	},
+	
+	bottomRightPosition : function(element) {
+		return this.topLeftPosition(element).plus(this._size(element))
+	},
+	
+	topLeftOffset : function(element) {
+		var offset = this._offset(element) 
+		
+		var parent = element.offsetParent
+		while (parent) {
+			offset = offset.plus(this._offset(parent))
+			parent = parent.offsetParent
+		}
+		return offset
+	},
+	
+	bottomRightOffset : function(element) {
+		return this.topLeftOffset(element).plus(
+				this.create(element.offsetWidth, element.offsetHeight))
+	},
+	
+	scrollOffset : function() {
+		if (window.pageXOffset) {
+			return this.create(window.pageXOffset, window.pageYOffset)
+		} else if (document.documentElement) {
+			return this.create(
+					document.body.scrollLeft + document.documentElement.scrollLeft, 
+					document.body.scrollTop + document.documentElement.scrollTop)
+		} else if (document.body.scrollLeft >= 0) {
+			return this.create(document.body.scrollLeft, document.body.scrollTop)
+		} else {
+			return this.create(0, 0)
+		}
+	},
+	
+	clientSize : function() {
+		if (window.innerHeight >= 0) {
+			return this.create(window.innerWidth, window.innerHeight)
+		} else if (document.documentElement) {
+			return this.create(document.documentElement.clientWidth,
+					document.documentElement.clientHeight)
+		} else if (document.body.clientHeight >= 0) {
+			return this.create(document.body.clientWidth,
+					document.body.clientHeight)
+		} else {
+			return this.create(0, 0)
+		}
+	},
+	
+	/**
+	 * mouse coordinate relative to the window (technically the
+	 * browser client area) i.e. the part showing your page
+	 *
+	 * NOTE: in Safari the coordinate is relative to the document
+	 */
+	mousePosition : function(event) {
+		event = ToolMan.events().fix(event)
+		return this.create(event.clientX, event.clientY)
+	},
+	
+	/**
+	 * mouse coordinate relative to the document
+	 */
+	mouseOffset : function(event) {
+		event = ToolMan.events().fix(event)
+		if (event.pageX >= 0 || event.pageX < 0) {
+			return this.create(event.pageX, event.pageY)
+		} else if (event.clientX >= 0 || event.clientX < 0) {
+			return this.mousePosition(event).plus(this.scrollOffset())
+		}
+	},
+	
+	_size : function(element) {
+	/* TODO: move to a Dimension class */
+		return this.create(element.offsetWidth, element.offsetHeight)
+	},
+	
+	_offset : function(element) {
+		return this.create(element.offsetLeft, element.offsetTop)
+	}
+}
+
+function _ToolManCoordinate(factory, x, y) {
+	this.factory = factory
+	this.x = isNaN(x) ? 0 : x
+	this.y = isNaN(y) ? 0 : y
+}
+
+_ToolManCoordinate.prototype = {
+	toString : function() {
+		return "(" + this.x + "," + this.y + ")"
+	},
+	
+	plus : function(that) {
+		return this.factory.create(this.x + that.x, this.y + that.y)
+	},
+	
+	minus : function(that) {
+		return this.factory.create(this.x - that.x, this.y - that.y)
+	},
+	
+	min : function(that) {
+		return this.factory.create(
+				Math.min(this.x , that.x), Math.min(this.y , that.y))
+	},
+	
+	max : function(that) {
+		return this.factory.create(
+				Math.max(this.x , that.x), Math.max(this.y , that.y))
+	},
+	
+	constrainTo : function (one, two) {
+		var min = one.min(two)
+		var max = one.max(two)
+		
+		return this.max(min).min(max)
+	},
+	
+	distance : function (that) {
+		return Math.sqrt(Math.pow(this.x - that.x, 2) + Math.pow(this.y - that.y, 2))
+	},
+	
+	reposition : function(element) {
+		element.style["top"] = this.y + "px"
+		element.style["left"] = this.x + "px"
+	}
+}
Index: admin/js/tool-man/core.js
===================================================================
--- admin/js/tool-man/core.js	(revision 0)
+++ admin/js/tool-man/core.js	(revision 0)
@@ -0,0 +1,170 @@
+/*
+Copyright (c) 2005 Tim Taylor Consulting <http://tool-man.org/>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+*/
+
+var ToolMan = {
+	events : function() {
+		if (!ToolMan._eventsFactory) throw "ToolMan Events module isn't loaded";
+		return ToolMan._eventsFactory
+	},
+	
+	css : function() {
+		if (!ToolMan._cssFactory) throw "ToolMan CSS module isn't loaded";
+		return ToolMan._cssFactory
+	},
+	
+	coordinates : function() {
+		if (!ToolMan._coordinatesFactory) throw "ToolMan Coordinates module isn't loaded";
+		return ToolMan._coordinatesFactory
+	},
+	
+	drag : function() {
+		if (!ToolMan._dragFactory) throw "ToolMan Drag module isn't loaded";
+		return ToolMan._dragFactory
+	},
+	
+	dragsort : function() {
+		if (!ToolMan._dragsortFactory) throw "ToolMan DragSort module isn't loaded";
+		return ToolMan._dragsortFactory
+	},
+	
+	helpers : function() {
+		return ToolMan._helpers
+	},
+	
+	cookies : function() {
+		if (!ToolMan._cookieOven) throw "ToolMan Cookie module isn't loaded";
+		return ToolMan._cookieOven
+	},
+	
+	junkdrawer : function() {
+		return ToolMan._junkdrawer
+	}
+	
+}
+
+ToolMan._helpers = {
+	map : function(array, func) {
+		for (var i = 0, n = array.length; i < n; i++) func(array[i])
+	},
+	
+	nextItem : function(item, nodeName) {
+		if (item == null) return false;
+		var next = item.nextSibling
+		while (next != null) {
+			if (next.nodeName == nodeName) return next
+			next = next.nextSibling
+		}
+		return null
+	},
+	
+	previousItem : function(item, nodeName) {
+		var previous = item.previousSibling
+		while (previous != null) {
+			if (previous.nodeName == nodeName) return previous
+			previous = previous.previousSibling
+		}
+		return null
+	},
+	
+	moveBefore : function(item1, item2) {
+		var parent = item1.parentNode
+		parent.removeChild(item1)
+		parent.insertBefore(item1, item2)
+	},
+	
+	moveAfter : function(item1, item2) {
+		var parent = item1.parentNode
+		parent.removeChild(item1)
+		parent.insertBefore(item1, item2 ? item2.nextSibling : null)
+	}
+}
+
+/** 
+ * scripts without a proper home
+ *
+ * stuff here is subject to change unapologetically and without warning
+ */
+ToolMan._junkdrawer = {
+	serializeList : function(list) {
+		var items = list.getElementsByTagName("li")
+		var array = new Array()
+		for (var i = 0, n = items.length; i < n; i++) {
+			var item = items[i]
+			
+			array.push(ToolMan.junkdrawer()._identifier(item))
+		}
+		return array.join('|')
+	},
+	
+	inspectListOrder : function(id) {
+		alert(ToolMan.junkdrawer().serializeList(document.getElementById(id)))
+	},
+	
+	restoreListOrder : function(listID) {
+		var list = document.getElementById(listID)
+		if (list == null) return
+		
+		var cookie = ToolMan.cookies().get("list-" + listID)
+		if (!cookie) return;
+		
+		var IDs = cookie.split('|')
+		var items = ToolMan.junkdrawer()._itemsByID(list)
+		
+		for (var i = 0, n = IDs.length; i < n; i++) {
+			var itemID = IDs[i]
+			if (itemID in items) {
+				var item = items[itemID]
+				list.removeChild(item)
+				list.insertBefore(item, null)
+			}
+		}
+	},
+	
+	_identifier : function(item) {
+		var trim = ToolMan.junkdrawer().trim
+		var identifier
+		
+		identifier = trim(item.getAttribute("id"))
+		if (identifier != null && identifier.length > 0) return identifier;
+		
+		identifier = trim(item.getAttribute("itemID"))
+		if (identifier != null && identifier.length > 0) return identifier;
+		
+		// FIXME: strip out special chars or make this an MD5 hash or something
+		return trim(item.innerHTML)
+	},
+	
+	_itemsByID : function(list) {
+		var array = new Array()
+		var items = list.getElementsByTagName('li')
+		for (var i = 0, n = items.length; i < n; i++) {
+			var item = items[i]
+			array[ToolMan.junkdrawer()._identifier(item)] = item
+		}
+		return array
+	},
+	
+	trim : function(text) {
+		if (text == null) return null
+		return text.replace(/^(\s+)?(.*\S)(\s+)?$/, '$2')
+	}
+}
Index: admin/js/tool-man/css.js
===================================================================
--- admin/js/tool-man/css.js	(revision 0)
+++ admin/js/tool-man/css.js	(revision 0)
@@ -0,0 +1,17 @@
+/* Copyright (c) 2005 Tim Taylor Consulting (see LICENSE.txt) */
+
+// TODO: write unit tests
+ToolMan._cssFactory = {
+	readStyle : function(element, property) {
+		if (element.style[property]) {
+			return element.style[property]
+		} else if (element.currentStyle) {
+			return element.currentStyle[property]
+		} else if (document.defaultView && document.defaultView.getComputedStyle) {
+			var style = document.defaultView.getComputedStyle(element, null)
+			return style.getPropertyValue(property)
+		} else {
+			return null
+		}
+	}
+}
Index: admin/js/tool-man/drag.js
===================================================================
--- admin/js/tool-man/drag.js	(revision 0)
+++ admin/js/tool-man/drag.js	(revision 0)
@@ -0,0 +1,249 @@
+/* Copyright (c) 2005 Tim Taylor Consulting (see LICENSE.txt) */
+
+ToolMan._dragFactory = {
+	createSimpleGroup : function(element, handle) {
+		handle = handle ? handle : element
+		var group = this.createGroup(element)
+		group.setHandle(handle)
+		//group.transparentDrag()
+		group.onTopWhileDragging()
+		return group
+	},
+	
+	createGroup : function(element) {
+		var group = new _ToolManDragGroup(this, element)
+		
+		var position = ToolMan.css().readStyle(element, 'position')
+		if (position == 'static') {
+			element.style["position"] = 'relative'
+		} else if (position == 'absolute') {
+			/* for Safari 1.2 */
+			ToolMan.coordinates().topLeftOffset(element).reposition(element)
+		}
+		
+		// TODO: only if ToolMan.isDebugging()
+		group.register('draginit', this._showDragEventStatus)
+		group.register('dragmove', this._showDragEventStatus)
+		group.register('dragend', this._showDragEventStatus)
+		
+		return group
+	},
+	
+	_showDragEventStatus : function(dragEvent) {
+		window.status = dragEvent.toString()
+	},
+	
+	constraints : function() {
+		return this._constraintFactory
+	},
+	
+	_createEvent : function(type, event, group) {
+		return new _ToolManDragEvent(type, event, group)
+	}
+}
+
+function _ToolManDragGroup(factory, element) {
+	this.factory = factory
+	this.element = element
+	this._handle = null
+	this._thresholdDistance = 0
+	this._transforms = new Array()
+	// TODO: refactor into a helper object, move into events.js
+	this._listeners = new Array()
+	this._listeners['draginit'] = new Array()
+	this._listeners['dragstart'] = new Array()
+	this._listeners['dragmove'] = new Array()
+	this._listeners['dragend'] = new Array()
+}
+
+_ToolManDragGroup.prototype = {
+	/*
+	 * TODO:
+	 *   - unregister(type, func)
+	 *   - move custom event listener stuff into Event library
+	 *   - keyboard nudging of "selected" group
+	 */
+	
+	setHandle : function(handle) {
+		var events = ToolMan.events()
+		
+		handle.toolManDragGroup = this
+		events.register(handle, 'mousedown', this._dragInit)
+		//handle.onmousedown = function() { return false }
+		
+		if (this.element != handle)
+			events.unregister(this.element, 'mousedown', this._dragInit)
+	},
+	
+	register : function(type, func) {
+		this._listeners[type].push(func)
+	},
+	
+	addTransform : function(transformFunc) {
+		this._transforms.push(transformFunc)
+	},
+	
+	verticalOnly : function() {
+		this.addTransform(this.factory.constraints().vertical())
+	},
+	
+	horizontalOnly : function() {
+		this.addTransform(this.factory.constraints().horizontal())
+	},
+	
+	setThreshold : function(thresholdDistance) {
+		this._thresholdDistance = thresholdDistance
+	},
+	
+	transparentDrag : function(opacity) {
+		if (typeof(opacity) == "undefined") {
+			opacity = 0.75;
+		}
+		var originalOpacity = ToolMan.css().readStyle(this.element, "opacity")
+		
+		this.register('dragstart', function(dragEvent) {
+			var element = dragEvent.group.element
+			element.style.opacity = opacity
+			element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')'
+		})
+		this.register('dragend', function(dragEvent) {
+			var element = dragEvent.group.element
+			element.style.opacity = originalOpacity
+			element.style.filter = 'alpha(opacity=100)'
+		})
+	},
+	
+	onTopWhileDragging : function(zIndex) {
+		if (typeof(zIndex) == "undefined") {
+			zIndex = 100000;
+		}
+		
+		var originalZIndex = ToolMan.css().readStyle(this.element, "z-index")
+		
+		this.register('dragstart', function(dragEvent) {
+			dragEvent.group.element.style.zIndex = zIndex
+		})
+		this.register('dragend', function(dragEvent) {
+			if (typeof(originalZIndex) != "undefined") {
+				dragEvent.group.element.style.zIndex = originalZIndex
+			}
+			//dragEvent.group.element.style.backgroundColor = '#f00';
+		})
+	},
+	
+	_dragInit : function(event) {
+		event = ToolMan.events().fix(event)
+		var group = document.toolManDragGroup = this.toolManDragGroup
+		var dragEvent = group.factory._createEvent('draginit', event, group)
+		
+		group._isThresholdExceeded = false
+		group._initialMouseOffset = dragEvent.mouseOffset
+		group._grabOffset = dragEvent.mouseOffset.minus(dragEvent.topLeftOffset)
+		ToolMan.events().register(document, 'mousemove', group._drag)
+		document.onmousemove = function() { return false }
+		ToolMan.events().register(document, 'mouseup', group._dragEnd)
+		
+		ToolMan.events().register(document, 'mousedown',group._drag);
+		document.onmousedown = function() { return false; };
+		
+		group._notifyListeners(dragEvent)
+	},
+	
+	_drag : function(event) {
+		event = ToolMan.events().fix(event)
+		var coordinates = ToolMan.coordinates()
+		var group = this.toolManDragGroup
+		if (!group) return
+		var dragEvent = group.factory._createEvent('dragmove', event, group)
+		
+		var newTopLeftOffset = dragEvent.mouseOffset.minus(group._grabOffset)
+		
+		// TODO: replace with DragThreshold object
+		if (!group._isThresholdExceeded) {
+			var distance = 
+					dragEvent.mouseOffset.distance(group._initialMouseOffset)
+			if (distance < group._thresholdDistance) return
+			group._isThresholdExceeded = true
+			group._notifyListeners(
+					group.factory._createEvent('dragstart', event, group))
+		}
+		
+		for (i in group._transforms) {
+			var transform = group._transforms[i]
+			newTopLeftOffset = transform(newTopLeftOffset, dragEvent)
+		}
+		
+		var dragDelta = newTopLeftOffset.minus(dragEvent.topLeftOffset)
+		var newTopLeftPosition = dragEvent.topLeftPosition.plus(dragDelta)
+		newTopLeftPosition.reposition(group.element)
+		dragEvent.transformedMouseOffset = newTopLeftOffset.plus(group._grabOffset)
+		
+		group._notifyListeners(dragEvent)
+		
+		var errorDelta = newTopLeftOffset.minus(coordinates.topLeftOffset(group.element))
+		if (errorDelta.x != 0 || errorDelta.y != 0) {
+			coordinates.topLeftPosition(group.element).plus(errorDelta).reposition(group.element)
+		}
+	},
+	
+	_dragEnd : function(event) {
+		event = ToolMan.events().fix(event)
+		var group = this.toolManDragGroup
+		var dragEvent = group.factory._createEvent('dragend', event, group)
+		
+		group._notifyListeners(dragEvent)
+		
+		this.toolManDragGroup = null
+		ToolMan.events().unregister(document, 'mousemove', group._drag)
+		document.onmousemove = null
+		ToolMan.events().unregister(document, 'mouseup', group._dragEnd)
+		
+		ToolMan.events().register(document, 'mousedown', group._drag);
+		document.onmousedown = function() { return true };
+	},
+	
+	_notifyListeners : function(dragEvent) {
+		var listeners = this._listeners[dragEvent.type]
+		for (i in listeners) {
+			listeners[i](dragEvent)
+		}
+	}
+}
+
+function _ToolManDragEvent(type, event, group) {
+	this.type = type
+	this.group = group
+	this.mousePosition = ToolMan.coordinates().mousePosition(event)
+	this.mouseOffset = ToolMan.coordinates().mouseOffset(event)
+	this.transformedMouseOffset = this.mouseOffset
+	this.topLeftPosition = ToolMan.coordinates().topLeftPosition(group.element)
+	this.topLeftOffset = ToolMan.coordinates().topLeftOffset(group.element)
+}
+
+_ToolManDragEvent.prototype = {
+	toString : function() {
+		return "mouse: " + this.mousePosition + this.mouseOffset + "    " +
+				"xmouse: " + this.transformedMouseOffset + "    " +
+				"left,top: " + this.topLeftPosition + this.topLeftOffset
+	}
+}
+
+ToolMan._dragFactory._constraintFactory = {
+	vertical : function() {
+		return function(coordinate, dragEvent) {
+			var x = dragEvent.topLeftOffset.x
+			return coordinate.x != x
+					? coordinate.factory.create(x, coordinate.y) 
+					: coordinate
+		}
+	},
+	
+	horizontal : function() {
+		return function(coordinate, dragEvent) {
+			var y = dragEvent.topLeftOffset.y
+			return coordinate.y != y
+					? coordinate.factory.create(coordinate.x, y) 
+					: coordinate
+		}
+	}
+}
Index: admin/js/tool-man/dragsort.js
===================================================================
--- admin/js/tool-man/dragsort.js	(revision 0)
+++ admin/js/tool-man/dragsort.js	(revision 0)
@@ -0,0 +1,87 @@
+/* Copyright (c) 2005 Tim Taylor Consulting (see LICENSE.txt) */
+
+ToolMan._dragsortFactory = {
+	makeSortable : function(item) {
+		var group = ToolMan.drag().createSimpleGroup(item)
+		
+		group.register('dragstart', this._onDragStart)
+		group.register('dragmove', this._onDragMove)
+		group.register('dragend', this._onDragEnd)
+		
+		return group
+	},
+	
+	/** 
+	 * Iterates over a list's items, making them sortable, applying
+	 * optional functions to each item.
+	 *
+	 * example: makeListSortable(myList, myFunc1, myFunc2, ... , myFuncN)
+	 */
+	makeListSortable : function(list) {
+		var helpers = ToolMan.helpers()
+		var coordinates = ToolMan.coordinates()
+		var items = list.getElementsByTagName("li")
+		
+		helpers.map(items, function(item) {
+			var dragGroup = dragsort.makeSortable(item)
+			dragGroup.setThreshold(4)
+			var min, max
+			dragGroup.addTransform(function(coordinate, dragEvent) {
+				return coordinate.constrainTo(min, max)
+			})
+			dragGroup.register('dragstart', function() {
+				var items = list.getElementsByTagName("li")
+				min = max = coordinates.topLeftOffset(items[0])
+				for (var i = 1, n = items.length; i < n; i++) {
+					var offset = coordinates.topLeftOffset(items[i])
+					min = min.min(offset)
+					max = max.max(offset)
+				}
+			})
+		})
+		for (var i = 1, n = arguments.length; i < n; i++)
+			helpers.map(items, arguments[i])
+	},
+	
+	_onDragStart : function(dragEvent) {
+	},
+	
+	_onDragMove : function(dragEvent) {
+		var helpers = ToolMan.helpers()
+		var coordinates = ToolMan.coordinates()
+		
+		var item = dragEvent.group.element
+		var xmouse = dragEvent.transformedMouseOffset
+		var moveTo = null
+		
+		var previous = helpers.previousItem(item, item.nodeName)
+		while (previous != null) {
+			var bottomRight = coordinates.bottomRightOffset(previous)
+			if (xmouse.y <= bottomRight.y && xmouse.x <= bottomRight.x) {
+				moveTo = previous
+			}
+			previous = helpers.previousItem(previous, item.nodeName)
+		}
+		if (moveTo != null) {
+			helpers.moveBefore(item, moveTo)
+			return
+		}
+		
+		var next = helpers.nextItem(item, item.nodeName)
+		while (next != null) {
+			var topLeft = coordinates.topLeftOffset(next)
+			if (topLeft.y <= xmouse.y && topLeft.x <= xmouse.x) {
+				moveTo = next
+			}
+			next = helpers.nextItem(next, item.nodeName)
+		}
+		if (moveTo != null) {
+			helpers.moveBefore(item, helpers.nextItem(moveTo, item.nodeName))
+			return
+		}
+	},
+	
+	_onDragEnd : function(dragEvent) {
+		ToolMan.coordinates().create(0, 0).reposition(dragEvent.group.element)
+	}
+}
Index: admin/js/tool-man/events.js
===================================================================
--- admin/js/tool-man/events.js	(revision 0)
+++ admin/js/tool-man/events.js	(revision 0)
@@ -0,0 +1,43 @@
+/* Copyright (c) 2005 Tim Taylor Consulting (see LICENSE.txt) */
+
+ToolMan._eventsFactory = {
+	fix : function(event) {
+		if (!event) event = window.event
+		
+		if (event.target) {
+			if (event.target.nodeType == 3) event.target = event.target.parentNode
+		} else if (event.srcElement) {
+			event.target = event.srcElement
+		}
+		
+		return event
+	},
+	
+	register : function(element, type, func) {
+		if (element.addEventListener) {
+			element.addEventListener(type, func, false)
+		} else if (element.attachEvent) {
+			if (!element._listeners) element._listeners = new Array()
+			if (!element._listeners[type]) element._listeners[type] = new Array()
+			var workaroundFunc = function() {
+				func.apply(element, new Array())
+			}
+			element._listeners[type][func] = workaroundFunc
+			element.attachEvent('on' + type, workaroundFunc)
+		}
+	},
+	
+	unregister : function(element, type, func) {
+		if (element.removeEventListener) {
+			element.removeEventListener(type, func, false)
+		} else if (element.detachEvent) {
+			if (element._listeners 
+					&& element._listeners[type] 
+					&& element._listeners[type][func]) {
+						
+				element.detachEvent('on' + type, 
+						element._listeners[type][func])
+			}
+		}
+	}
+}
Index: admin/media.php
===================================================================
--- admin/media.php	(revision 1678)
+++ admin/media.php	(revision 1681)
@@ -231,5 +231,4 @@
 $core->auth->user_prefs->addWorkspace('interface');
 $user_ui_enhanceduploader = $core->auth->user_prefs->interface->enhanceduploader;
-
 
 if (!isset($core->media)) {
@@ -343,17 +342,18 @@
 if ($core_media_writable)
 {
-	echo '<div class="two-cols">';
+	echo 
+	'<h3>'.sprintf(__('In %s:'),($d == '' ? '“'.__('Media manager').'”' : '“'.$d.'”')).'</h3>'.
+	'<div class="media-action-box">';
 	
 	if ($user_ui_enhanceduploader) {
 		echo
-		'<div class="col enhanced_uploader">';
+		'<div class="enhanced_uploader">';
 	} else {
 		echo
-		'<div class="col">';
-	}
-
-	echo
-	'<div class="fieldset">'.
-	'<h3>'.__('Add files').'</h3>'.
+		'<div>';
+	}
+
+	echo
+	'<h4>'.__('Add files').'</h4>'.
 	'<p>'.__('Please take care to publish media that you own and that are not protected by copyright.').'</p>'.
 	'<p class="max-sizer form-note info">&nbsp;'.__('Maximum file size allowed:').' '.files::size(DC_MAX_UPLOAD_SIZE).'</p>'.
@@ -391,11 +391,12 @@
 	'<p style="clear:both;">'.form::hidden(array('d'),$d).'</p>'.
 	'</form>'.
-	'</div></div>';
-
-	echo
-	'<div class="col">'.
-	'<form class="clear fieldset" action="'.html::escapeURL($page_url).'" method="post">'.
+	'</div>'.
+	'</div>';
+
+	echo
+	'<div class="media-action-box">'.
+	'<form action="'.html::escapeURL($page_url).'" method="post">'.
 	'<div id="new-dir-f">'.
-	'<h3>'.__('New directory').'</h3>'.
+	'<h4>'.__('Create new directory').'</h4>'.
 	$core->formNonce().
 	'<p><label for="newdir">'.__('Directory Name:').'</label>'.
@@ -404,16 +405,17 @@
 	form::hidden(array('d'),html::escapeHTML($d)).'</p>'.
 	'</div>'.
-	'</form></div>';
-	
-	echo '</div>';
-}
+	'</form>'.
+	'</div>';
+	}
 
 # Empty remove form (for javascript actions)
 echo
-'<form id="media-remove-hide" action="'.html::escapeURL($page_url).'" method="post"><div class="clear">'.
+'<form id="media-remove-hide" action="'.html::escapeURL($page_url).'" method="post" class="hidden">'.
+'<div>'.
 form::hidden('rmyes',1).form::hidden('d',html::escapeHTML($d)).
 form::hidden('remove','').
 $core->formNonce().
-'</div></form>';
+'</div>'.
+'</form>';
 
 # Get zip directory
@@ -422,6 +424,10 @@
 {
 	echo
-	'<p class="zip-dl"><a class="submit" href="'.html::escapeURL($page_url).'&amp;zipdl=1">'.
-	__('Download this directory as a zip file').'</a></p>';
+	'<div class="media-action-box">'.
+	'<h4>'.__('Backup content').'</h4>'.
+	'<p>'.__('Compress this directory with its content as a zip file and download it.').'</p>'.
+	'<p><a class="submit" href="'.html::escapeURL($page_url).'&amp;zipdl=1">'.
+	__('Download').'</a></p>'.
+	'</div>';
 }
 
Index: admin/post.php
===================================================================
--- admin/post.php	(revision 1711)
+++ admin/post.php	(revision 1714)
@@ -518,5 +518,5 @@
 		"post_excerpt" =>
 			'<p class="area" id="excerpt-area"><label for="post_excerpt">'.__('Excerpt:').'<span class="form-note">'.
-			__('Add an introduction to the post.').'</span></label> '.
+			__('Introduction to the post.').'</span></label> '.
 			form::textarea('post_excerpt',50,5,html::escapeHTML($post_excerpt)).
 			'</p>',
@@ -530,5 +530,5 @@
 		"post_notes" =>
 			'<p class="area" id="notes-area"><label for="post_notes">'.__('Personal notes:').'<span class="form-note">'.
-			__('Add unpublished notes.').'</span></label>'.
+			__('Unpublished notes.').'</span></label>'.
 			form::textarea('post_notes',50,5,html::escapeHTML($post_notes)).
 			'</p>'
Index: admin/services.php
===================================================================
--- admin/services.php	(revision 1538)
+++ admin/services.php	(revision 1699)
@@ -26,4 +26,5 @@
 $core->rest->addFunction('setPostMeta',array('dcRestMethods','setPostMeta'));
 $core->rest->addFunction('searchMeta',array('dcRestMethods','searchMeta'));
+$core->rest->addFunction('setSectionFold',array('dcRestMethods','setSectionFold'));
 
 $core->rest->serve();
@@ -412,4 +413,35 @@
 		return $rsp;
 	}
+	
+	public static function setSectionFold($core,$get,$post)
+	{
+		if (empty($post['section'])) {
+			throw new Exception('No section name');
+		}
+		if ($core->auth->user_prefs->toggles === null) {
+			$core->auth->user_prefs->addWorkspace('toggles');
+		}
+		$section = $post['section'];
+		$status = isset($post['value']) && ($post['value'] != 0);
+		if ($core->auth->user_prefs->toggles->prefExists('unfolded_sections')) {
+			$toggles = explode(',',trim($core->auth->user_prefs->toggles->unfolded_sections));
+		} else {
+			$toggles = array();
+		}
+		$k = array_search($section,$toggles);
+		if ($status) { // true == Fold section ==> remove it from unfolded list
+			if ($k !== false) {
+				unset($toggles[$k]);
+			} 
+		} else { // false == unfold section ==> add it to unfolded list
+			if ($k === false) {
+				$toggles[]=$section;
+			}; 
+		}
+		$core->auth->user_prefs->toggles->put('unfolded_sections',join(',',$toggles));
+		return true;
+	}
+		
+	
 }
 ?>
Index: admin/style/default.css
===================================================================
--- admin/style/default.css	(revision 1678)
+++ admin/style/default.css	(revision 1698)
@@ -797,5 +797,5 @@
 	border: none;
 }
-.media-item {
+.media-item, .media-action-box {
 	position: relative;
 	border: 1px solid #ccc;
@@ -803,9 +803,7 @@
 	padding: 1em;
 	width: 300px;
-	height: 120px;
 	display: inline-block;
 	vertical-align: top;
-}
-div.media-list .media-item {
+	min-height: 120px
 }
 a.media-icon {
@@ -905,4 +903,39 @@
 	margin-left: 0;
 }
+/* in blog_pref */
+.user-perm {
+	margin: 2em 0px;
+	background: transparent url(user.png) no-repeat left top;
+	width: 320px;
+	display: inline-block;
+	vertical-align: top;
+}
+.user-perm h4, .user-perm h5, .user-perm p, .user-perm ul, .user-perm li {
+	margin: .5em 0 .33em;
+	padding: 0;
+}
+.user-perm h4 {
+	padding-left: 28px;
+}
+.user-perm ul {
+	list-style-type: inside;
+}
+.user-perm li {
+	margin-left: 1em;
+	padding-left: 0;
+}
+.user-perm h5 {
+	margin:	1em 0 0 0;
+}
+li.user_super, li.user_admin {
+	margin-left: 0;
+	padding-left: 1em;
+	list-style: none;
+	background: transparent url(../images/superadmin.png) no-repeat -2px 2px;
+}
+li.user_admin {
+	background-image: url(../images/admin.png);
+}
+
 /* -------------------------------------------------------------------- Themes */
 #themes {
@@ -1162,4 +1195,19 @@
 .success a {
 	color: #666;
+}
+.dc-update {
+	padding: 1em 48px 0.5em 48px;
+	margin-bottom: 1em;
+	border-radius: 8px;
+	background: #A2CBE9 url(msg-success.png) no-repeat .7em .7em;
+}
+.dc-update a {
+	color: #000;
+	border-color: #000;
+	margin-right: 1em;
+}
+a.info {
+	margin-left: 2em;
+	font-weight: bold;
 }
 /* ------------------------------------------------------------------ navigation */
Index: admin/style/jsToolBar/jsToolBar.css
===================================================================
--- admin/style/jsToolBar/jsToolBar.css	(revision 1636)
+++ admin/style/jsToolBar/jsToolBar.css	(revision 1697)
@@ -48,4 +48,5 @@
 	width: 12em;
 	margin-right: .6rem;
+	border: 1px solid #ccc; 
 }
 .jstElements button {
@@ -71,7 +72,6 @@
 	background: #dfdfdf;
 }
-
-.jstSpacer {
-	width : 2px;
+span.jstSpacer {
+	width : 6px;
 	height: 28px;
 	margin-right: 0;
Index: inc/admin/lib.dc.page.php
===================================================================
--- inc/admin/lib.dc.page.php	(revision 1655)
+++ inc/admin/lib.dc.page.php	(revision 1699)
@@ -114,4 +114,5 @@
 		echo
 		self::jsCommon().
+		self::jsToggles().
 		$head;
 
@@ -253,4 +254,5 @@
 		echo
 		self::jsCommon().
+		self::jsToggles().
 		$head;
 
@@ -444,4 +446,25 @@
 	}
 
+	public static function jsToggles()
+	{
+		if($GLOBALS['core']->auth->user_prefs->toggles) {
+			$unfolded_sections = explode(',',$GLOBALS['core']->auth->user_prefs->toggles->unfolded_sections);
+			foreach ($unfolded_sections as $k=>&$v) {
+				if ($v == '') {
+					unset($unfolded_sections[$k]);
+				} else {
+					$v = "'".html::escapeJS($v)."':true";
+				}
+			}
+		} else {
+			$unfolded_sections=array();
+		}
+		return '<script type="text/javascript">'."\n".
+					"//<![CDATA[\n".
+					'dotclear.unfolded_sections = {'.join(",",$unfolded_sections)."};\n".
+					"\n//]]>\n".
+				"</script>\n";
+	}
+	
 	public static function jsCommon()
 	{
@@ -544,5 +567,5 @@
 		self::jsVar('dotclear.msg.load_enhanced_uploader',
 			__('Loading enhanced uploader, please wait.')).
-		"\n//]]>\n".
+			"\n//]]>\n".
 		"</script>\n";
 	}
@@ -792,4 +815,16 @@
 }
 
+public static function jsToolMan()
+{
+	return
+	'<script type="text/javascript" src="js/tool-man/core.js"></script>'.
+	'<script type="text/javascript" src="js/tool-man/events.js"></script>'.
+	'<script type="text/javascript" src="js/tool-man/css.js"></script>'.
+	'<script type="text/javascript" src="js/tool-man/coordinates.js"></script>'.
+	'<script type="text/javascript" src="js/tool-man/drag.js"></script>'.
+	'<script type="text/javascript" src="js/tool-man/dragsort.js"></script>'.
+	'<script type="text/javascript" src="js/dragsort-tablerows.js"></script>';
+}
+
 public static function jsMetaEditor()
 {
Index: inc/core/class.dc.blog.php
===================================================================
--- inc/core/class.dc.blog.php	(revision 1620)
+++ inc/core/class.dc.blog.php	(revision 1689)
@@ -199,5 +199,5 @@
 	/**
 	Updates comments and trackbacks counters in post table. Should be called
-	every time comments or trackbacks are added, removed or changed there status.
+	every time comments or trackbacks are added, removed or changed their status.
 	
 	@param	ids		<b>mixed</b>		Comment(s) ID(s)
@@ -1880,4 +1880,5 @@
 	- cat_id: (integer or array) Get comments belonging to entries of given category ID
 	- comment_id: (integer) Get comment with given ID
+	- comment_site: (string) Get comments with given comment_site
 	- comment_status: (integer) Get comments with given comment_status
 	- comment_trackback: (integer) Get only comments (0) or trackbacks (1)
@@ -1969,4 +1970,9 @@
 		if (isset($params['comment_id']) && $params['comment_id'] !== '') {
 			$strReq .= 'AND comment_id = '.(integer) $params['comment_id'].' ';
+		}
+		
+		if (isset($params['comment_site'])) {
+			$comment_site = $this->con->escape(str_replace('*','%',$params['comment_site']));
+			$strReq .= "AND comment_site LIKE '".$comment_site."' ";
 		}
 		
Index: inc/core/class.dc.core.php
===================================================================
--- inc/core/class.dc.core.php	(revision 1593)
+++ inc/core/class.dc.core.php	(revision 1698)
@@ -345,9 +345,10 @@
 	}
 	
-	public function setPostType($type,$admin_url,$public_url)
+	public function setPostType($type,$admin_url,$public_url,$label='')
 	{
 		$this->post_types[$type] = array(
 			'admin_url' => $admin_url,
-			'public_url' => $public_url
+			'public_url' => $public_url,
+			'label' => ($label != '' ? $label : $type)
 		);
 	}
@@ -817,5 +818,5 @@
 		$strReq =
 		'SELECT U.user_id AS user_id, user_super, user_name, user_firstname, '.
-		'user_displayname, permissions '.
+		'user_displayname, user_email, permissions '.
 		'FROM '.$this->prefix.'user U '.
 		'JOIN '.$this->prefix.'permissions P ON U.user_id = P.user_id '.
@@ -826,5 +827,5 @@
 			'UNION '.
 			'SELECT U.user_id AS user_id, user_super, user_name, user_firstname, '.
-			"user_displayname, NULL AS permissions ".
+			"user_displayname, user_email, NULL AS permissions ".
 			'FROM '.$this->prefix.'user U '.
 			'WHERE user_super = 1 ';
@@ -841,4 +842,5 @@
 				'firstname' => $rs->user_firstname,
 				'displayname' => $rs->user_displayname,
+				'email' => $rs->user_email,
 				'super' => (boolean) $rs->user_super,
 				'p' => $this->auth->parsePermissions($rs->permissions)
Index: inc/core/class.dc.trackback.php
===================================================================
--- inc/core/class.dc.trackback.php	(revision 1674)
+++ inc/core/class.dc.trackback.php	(revision 1689)
@@ -14,7 +14,7 @@
 /**
 @ingroup DC_CORE
-@brief Trackbacks sender and server
-
-Sends and receives trackbacks. Also handles trackbacks auto discovery.
+@brief Trackbacks/Pingbacks sender and server
+
+Sends and receives trackbacks/pingbacks. Also handles trackbacks/pingbacks auto discovery.
 */
 class dcTrackback
@@ -146,4 +146,50 @@
 	}
 	//@}
+	
+	private function pingAlreadyDone($post_id, $from_url)
+	{
+		$params = array(
+			'post_id' => $post_id,
+			'comment_site' => $from_url,
+			'comment_trackback' => 1,
+		);
+		
+		$rs = $this->core->blog->getComments($params, true);
+		if ($rs && !$rs->isEmpty()) {
+			return ($rs->f(0));
+		}
+		
+		return false;
+	}
+	
+	private function addBacklink($post_id, $url, $blog_name, $title, $excerpt, &$comment)
+	{
+		if (empty($blog_name)) {
+			$blog_name = 'Anonymous blog';
+		}
+		
+		$comment =
+		"<!-- TB -->\n".
+		'<p><strong>'.($title ? $title : $blog_name)."</strong></p>\n".
+		'<p>'.$excerpt.'</p>';
+		
+		$cur = $this->core->con->openCursor($this->core->prefix.'comment');
+		$cur->comment_author = (string) $blog_name;
+		$cur->comment_site = (string) $url;
+		$cur->comment_content = (string) $comment;
+		$cur->post_id = $post_id;
+		$cur->comment_trackback = 1;
+		$cur->comment_status = $this->core->blog->settings->system->trackbacks_pub ? 1 : -1;
+		$cur->comment_ip = http::realIP();
+		
+		# --BEHAVIOR-- publicBeforeTrackbackCreate
+		$this->core->callBehavior('publicBeforeTrackbackCreate',$cur);
+		if ($cur->post_id) {
+			$comment_id = $this->core->blog->addComment($cur);
+			
+			# --BEHAVIOR-- publicAfterTrackbackCreate
+			$this->core->callBehavior('publicAfterTrackbackCreate',$cur,$comment_id);
+		}
+	}
 	
 	/// @name Receive trackbacks
@@ -176,5 +222,5 @@
 		$charset = '';
 		$comment = '';
-		
+				
 		$err = false;
 		$msg = '';
@@ -209,4 +255,10 @@
 				$msg = 'Trackbacks are not allowed for this post or weblog.';
 			}
+
+			$url = trim(html::clean($url));
+			if ($this->pingAlreadyDone($post->post_id, $url)) {
+				$err = true;
+				$msg = 'The trackback has already been registered';
+			}
 		}
 		
@@ -216,8 +268,5 @@
 			
 			if (!$charset) {
-				$charset = mb_detect_encoding($title.' '.$excerpt.' '.$blog_name,
-				'UTF-8,ISO-8859-1,ISO-8859-2,ISO-8859-3,'.
-				'ISO-8859-4,ISO-8859-5,ISO-8859-6,ISO-8859-7,ISO-8859-8,'.
-				'ISO-8859-9,ISO-8859-10,ISO-8859-13,ISO-8859-14,ISO-8859-15');
+				$charset = self::detectCharset($title.' '.$excerpt.' '.$blog_name);
 			}
 			
@@ -244,34 +293,7 @@
 			$blog_name = text::cutString($blog_name,60);
 			
-			$url = trim(html::clean($url));
-			
-			if (!$blog_name) {
-				$blog_name = 'Anonymous blog';
-			}
-			
-			$comment =
-			"<!-- TB -->\n".
-			'<p><strong>'.($title ? $title : $blog_name)."</strong></p>\n".
-			'<p>'.$excerpt.'</p>';
-			
-			$cur = $this->core->con->openCursor($this->core->prefix.'comment');
-			$cur->comment_author = (string) $blog_name;
-			$cur->comment_site = (string) $url;
-			$cur->comment_content = (string) $comment;
-			$cur->post_id = $post_id;
-			$cur->comment_trackback = 1;
-			$cur->comment_status = $this->core->blog->settings->system->trackbacks_pub ? 1 : -1;
-			$cur->comment_ip = http::realIP();
-			
 			try
 			{
-				# --BEHAVIOR-- publicBeforeTrackbackCreate
-				$this->core->callBehavior('publicBeforeTrackbackCreate',$cur);
-				if ($cur->post_id) {
-					$comment_id = $this->core->blog->addComment($cur);
-					
-					# --BEHAVIOR-- publicAfterTrackbackCreate
-					$this->core->callBehavior('publicAfterTrackbackCreate',$cur,$comment_id);
-				}
+				$this->addBacklink($post_id, $url, $blog_name, $title, $excerpt, $comment);
 			}
 			catch (Exception $e)
@@ -281,15 +303,4 @@
 			}
 		}
-		
-		
-		$debug_trace =
-		"  <debug>\n".
-		'    <title>'.$title."</title>\n".
-		'    <excerpt>'.$excerpt."</excerpt>\n".
-		'    <url>'.$url."</url>\n".
-		'    <blog_name>'.$blog_name."</blog_name>\n".
-		'    <charset>'.$charset."</charset>\n".
-		'    <comment>'.$comment."</comment>\n".
-		"  </debug>\n";
 		
 		$resp =
@@ -303,5 +314,13 @@
 		
 		if (!empty($_POST['__debug'])) {
-			$resp .= $debug_trace;
+			$resp .= 
+			"  <debug>\n".
+			'    <title>'.$title."</title>\n".
+			'    <excerpt>'.$excerpt."</excerpt>\n".
+			'    <url>'.$url."</url>\n".
+			'    <blog_name>'.$blog_name."</blog_name>\n".
+			'    <charset>'.$charset."</charset>\n".
+			'    <comment>'.$comment."</comment>\n".
+			"  </debug>\n";
 		}
 		
@@ -362,4 +381,8 @@
 		}
 
+		if ($this->pingAlreadyDone($posts->post_id, $from_url)) {
+			throw new Exception(__('Don\'t repeat yourself, please.'), 48);
+		}
+		
 		# OK. We've found our champion. Time to check the remote part.
 		try {
@@ -381,9 +404,10 @@
 			$remote_content = $http->getContent();
 
-			$charset = mb_detect_encoding($remote_content,
-				'UTF-8,ISO-8859-1,ISO-8859-2,ISO-8859-3,'.
-				'ISO-8859-4,ISO-8859-5,ISO-8859-6,ISO-8859-7,ISO-8859-8,'.
-				'ISO-8859-9,ISO-8859-10,ISO-8859-13,ISO-8859-14,ISO-8859-15');
-
+			$charset = self::getCharsetFromRequest($http->getHeader('content-type'));
+
+			if (!$charset) {
+				$charset = self::detectCharset($remote_content);
+			}
+			
 			if (strtolower($charset) != 'utf-8') {
 				$remote_content = iconv($charset,'UTF-8',$remote_content);
@@ -416,32 +440,11 @@
 			}
 			if ($excerpt) {
-				$excerpt = '(&#8230;) '.text::cutString(html::escapeHTML($excerpt),255).' (&#8230;)';
+				$excerpt = '(&#8230;) '.text::cutString(html::escapeHTML($excerpt),200).' (&#8230;)';
 			}
 			else {
-				$excerpt = '(??)';
-			}
-
-			$comment =
-			"<!-- TB -->\n".
-			'<p><strong>'.$title."</strong></p>\n".
-			'<p>'.$excerpt.'</p>';
-			
-			$cur = $this->core->con->openCursor($this->core->prefix.'comment');
-			$cur->comment_author = 'Anonymous blog';
-			$cur->comment_site = (string) $from_url;
-			$cur->comment_content = (string) $comment;
-			$cur->post_id = $posts->post_id;
-			$cur->comment_trackback = 1;
-			$cur->comment_status = $this->core->blog->settings->system->trackbacks_pub ? 1 : -1;
-			$cur->comment_ip = http::realIP();
-			
-			# --BEHAVIOR-- publicBeforeTrackbackCreate
-			$this->core->callBehavior('publicBeforeTrackbackCreate',$cur);
-			if ($cur->post_id) {
-				$comment_id = $this->core->blog->addComment($cur);
-				
-				# --BEHAVIOR-- publicAfterTrackbackCreate
-				$this->core->callBehavior('publicAfterTrackbackCreate',$cur,$comment_id);
-			}
+				$excerpt = '(&#8230;)';
+			}
+
+			$this->addBacklink($posts->post_id, $from_url, '', $title, $excerpt, $comment);
 		}
 		catch (Exception $e) {
@@ -464,9 +467,12 @@
 	}
 	
-	private static function getCharsetFromRequest()
-	{
-		if (isset($_SERVER['CONTENT_TYPE']))
-		{
-			if (preg_match('|charset=([a-zA-Z0-9-]+)|',$_SERVER['CONTENT_TYPE'],$m)) {
+	private static function getCharsetFromRequest($header = '')
+	{
+		if (!$header && isset($_SERVER['CONTENT_TYPE'])) {
+			$header = $_SERVER['CONTENT_TYPE'];
+		}
+		
+		if ($header) {
+			if (preg_match('|charset=([a-zA-Z0-9-]+)|',$header,$m)) {
 				return $m[1];
 			}
@@ -475,5 +481,13 @@
 		return null;
 	}
-	
+
+	private static function detectCharset($string)
+	{
+		return mb_detect_encoding($remote_content,
+				'UTF-8,ISO-8859-1,ISO-8859-2,ISO-8859-3,'.
+				'ISO-8859-4,ISO-8859-5,ISO-8859-6,ISO-8859-7,ISO-8859-8,'.
+				'ISO-8859-9,ISO-8859-10,ISO-8859-13,ISO-8859-14,ISO-8859-15');
+	}
+		
 	/// @name Trackbacks auto discovery
 	//@{
Index: inc/core/class.dc.xmlrpc.php
===================================================================
--- inc/core/class.dc.xmlrpc.php	(revision 1674)
+++ inc/core/class.dc.xmlrpc.php	(revision 1688)
@@ -1644,5 +1644,14 @@
 			throw new Exception(__('No valid source URL provided? Try again!'), 0);
 		}
-		
+
+		if (!(filter_var($to_url, FILTER_VALIDATE_URL) && preg_match('!^https?://!',$to_url))) {
+			throw new Exception(__('No valid target URL provided? Try again!'), 0);
+		}
+
+		if (html::sanitizeURL(urldecode($from_url)) == html::sanitizeURL(urldecode($to_url))) {
+			throw new Exception(__('LOL!'), 0);
+		}
+		
+		# Time to get things done...
 		$this->setBlog(true);
 		$tb = new dcTrackback($this->core);
Index: inc/prepend.php
===================================================================
--- inc/prepend.php	(revision 1468)
+++ inc/prepend.php	(revision 1694)
@@ -220,5 +220,5 @@
 $core->url->register('xmlrpc','xmlrpc','^xmlrpc/(.+)$',array('dcUrlHandlers','xmlrpc'));
 
-$core->setPostType('post','post.php?id=%d',$core->url->getURLFor('post','%s'));
+$core->setPostType('post','post.php?id=%d',$core->url->getURLFor('post','%s'),'Posts');
 
 # Store upload_max_filesize in bytes
Index: locales/fr/main.po
===================================================================
--- locales/fr/main.po	(revision 1678)
+++ locales/fr/main.po	(revision 1697)
@@ -3038,9 +3038,9 @@
 msgstr "Billet précédent"
 
-msgid "Add an introduction to the post."
-msgstr "Ajoute une introduction au billet."
-
-msgid "Add unpublished notes."
-msgstr "Ajoute des notes non publiées."
+msgid "Introduction to the post."
+msgstr "Introduction au billet."
+
+msgid "Unpublished notes."
+msgstr "Notes non publiées."
 
 msgid "Edit basename:"
@@ -3286,4 +3286,28 @@
 msgstr "Modifier le %1$s de %2$s"
 
-msgid "Backup"
-msgstr "Sauvegarde"
+msgid "Backup content"
+msgstr "Sauvegarder le contenu"
+
+msgid "Create new directory"
+msgstr "Créer un répertoire"
+
+msgid "In %s:"
+msgstr "Dans %s :"
+
+msgid "Download"
+msgstr "Télécharger"
+
+msgid "Compress this directory with its content as a zip file and download it."
+msgstr "Compressez ce dossier et son contenu dans un fichier zip et téléchargez-le."
+
+msgid "Permissions:"
+msgstr "Permissions :"
+
+msgid "Publications on this blog:"
+msgstr "Publications sur ce blog :"
+
+msgid "Posts"
+msgstr "Billets"
+
+msgid "%1$s: %2$s"
+msgstr "%1$s : %2$s"
Index: locales/fr/plugins.po
===================================================================
--- locales/fr/plugins.po	(revision 1669)
+++ locales/fr/plugins.po	(revision 1694)
@@ -1987,2 +1987,5 @@
 msgid "Drag widgets here to remove them from this sidebar."
 msgstr "Glisser les widgets ici pour les retirer du volet."
+
+msgid "Pages"
+msgstr "Pages"
Index: plugins/attachments/js/post.js
===================================================================
--- plugins/attachments/js/post.js	(revision 1606)
+++ plugins/attachments/js/post.js	(revision 1699)
@@ -1,5 +1,5 @@
 $(function() {
 	$('h5.s-attachments').toggleWithLegend($('.s-attachments').not('h5'),{
-		cookie: 'dcx_attachments',
+		user_pref: 'dcx_attachments',
 		legend_click: true
 	});
Index: plugins/pages/_prepend.php
===================================================================
--- plugins/pages/_prepend.php	(revision 1179)
+++ plugins/pages/_prepend.php	(revision 1694)
@@ -17,5 +17,5 @@
 $core->url->register('pagespreview','pagespreview','^pagespreview/(.+)$',array('urlPages','pagespreview'));
 
-$core->setPostType('page','plugin.php?p=pages&act=page&id=%d',$core->url->getURLFor('pages','%s'));
+$core->setPostType('page','plugin.php?p=pages&act=page&id=%d',$core->url->getURLFor('pages','%s'),'Pages');
 
 # We should put this as settings later
Index: plugins/pings/post.js
===================================================================
--- plugins/pings/post.js	(revision 1606)
+++ plugins/pings/post.js	(revision 1699)
@@ -13,5 +13,5 @@
 		}
 		$('h5.ping-services').toggleWithLegend($('p.ping-services'),{
-			cookie: 'dcx_ping_services',
+			user_pref: 'dcx_ping_services',
 			legend_click: true
 		});
Index: plugins/tags/js/post.js
===================================================================
--- plugins/tags/js/post.js	(revision 1606)
+++ plugins/tags/js/post.js	(revision 1699)
@@ -60,5 +60,5 @@
 
 	$('h5 .s-tags').toggleWithLegend($('.s-tags').not('label'),{
-		cookie: 'post_tags',
+		user_pref: 'post_tags',
 		legend_clik: true
 	});
