Index: .hgsubstate
===================================================================
--- .hgsubstate	(revision 1321)
+++ .hgsubstate	(revision 1485)
@@ -1,1 +1,1 @@
-106856ffd55945d2be60e5f623c587c3fb356447 inc/libs/clearbricks
+716e99c54652d08c2e4395d5179e817e0f98182d inc/libs/clearbricks
Index: .hgtags
===================================================================
--- .hgtags	(revision 1219)
+++ .hgtags	(revision 1351)
@@ -13,2 +13,3 @@
 514d5121a12586b7ff320924d44028b677a01020 2.4.4
 baf879af43beea9764d920c2facd7226e90c5cf3 2.5.1
+cfad9f2a9f6ec03206ff5db91985208e833a7528 2.5.2
Index: CHANGELOG
===================================================================
--- CHANGELOG	(revision 1216)
+++ CHANGELOG	(revision 1350)
@@ -1,2 +1,19 @@
+Dotclear 2.5.2 - 2013-08-14
+===========================================================
+* Security fix: Fixed potential XSS
+* Bugfix: l10n Clearbricks library
+* <tpl:LoopPosition> now works <tpl:Attachments>
+* Dotclear update check may now be forced (ignoring cache)
+* Enforce integration of daInstaller plugin
+* Tags link button is now available on page editor
+* Default cache age is now 1 week instead of 2 hours
+* Quick entry dashboard module is not activated by default on new installation
+* New template {{tpl:BlogParentThemeURL}} (return URL of parent theme of blog's theme if any, URL of blog's theme otherwise)
+* Fix post comments number on comments deletion
+* Fix order of backup files
+* Minor enhancements
+* Various bug fixes
+* Various cosmetic adjustments
+
 Dotclear 2.5.1 - 2013-07-20
 ===========================================================
Index: Makefile
===================================================================
--- Makefile	(revision 1046)
+++ Makefile	(revision 1451)
@@ -69,4 +69,5 @@
 	find $(DC)/admin/js/jquery/*.js -exec ./build-tools/min-js.php \{\} \;
 	find $(DC)/admin/js/jsToolBar/*.js -exec ./build-tools/min-js.php \{\} \;
+	find $(DC)/admin/js/jsUpload/*.js -exec ./build-tools/min-js.php \{\} \;
 	find $(DC)/admin/js/tool-man/*.js -exec ./build-tools/min-js.php \{\} \;
 	find $(DC)/plugins -name '*.js' -exec ./build-tools/min-js.php \{\} \;
Index: admin/categories.php
===================================================================
--- admin/categories.php	(revision 1179)
+++ admin/categories.php	(revision 1421)
@@ -31,5 +31,5 @@
 		$mov_cat = $mov_cat ? $mov_cat : null;
 		if ($mov_cat !== null) {
-			$c = $core->blog->getCategory((integer) $_POST['del_cat']);
+			$c = $core->blog->getCategory((integer) $_POST['mov_cat']);
 			if ($c->isEmpty()) {
 				throw new Exception(__('This category does not exist.'));
@@ -141,4 +141,15 @@
 echo '</div>';
 
+$categories_combo = array();
+if (!$rs->isEmpty())
+{
+	while ($rs->fetch()) {
+		$catparents_combo[] = $categories_combo[] = new formSelectOption(
+			str_repeat('&nbsp;&nbsp;',$rs->level-1).($rs->level-1 == 0 ? '' : '&bull; ').html::escapeHTML($rs->cat_title),
+			$rs->cat_id
+		);
+	}
+}
+
 echo '<div class="col">'.
 
@@ -146,15 +157,8 @@
 '<fieldset><legend>'.__('Add a new category').'</legend>'.
 '<p><label class="required" for="cat_title"><abbr title="'.__('Required field').'">*</abbr> '.__('Title:').' '.
-form::field('cat_title',30,255).'</label></p>'.
+form::field('cat_title',30,255,'','maximal').'</label></p>'.
 '<p><label for="new_cat_parent">'.__('Parent:').' '.
-'<select id="new_cat_parent" name="new_cat_parent">'.
-'<option value="0">'.__('Top level').'</option>';
-while ($rs->fetch()) {
-	echo '<option value="'.$rs->cat_id.'">'.
-		str_repeat('&nbsp;&nbsp;',$rs->level-1).($rs->level-1 == 0 ? '' : '&bull; ').
-		html::escapeHTML($rs->cat_title).'</option>';
-}
-echo
-'</select></label></p>'.
+form::combo('new_cat_parent',array_merge(array(__('(No cat)') => 0),$categories_combo),'','maximal').
+'</label></p>'.
 '<p><input type="submit" value="'.__('Create').'" /></p>'.
 $core->formNonce().
@@ -164,30 +168,11 @@
 if (!$rs->isEmpty())
 {
-	$cats = array();
-	$dest = array('&nbsp;' => '');
-	$l = $rs->level;
-	$full_name = array($rs->cat_title);
-	while ($rs->fetch())
-	{
-		if ($rs->level < $l) {
-			$full_name = array();
-		} elseif ($rs->level == $l) {
-			array_pop($full_name);
-		}
-		$full_name[] = html::escapeHTML($rs->cat_title);
-		
-		$cats[implode(' / ',$full_name)] = $rs->cat_id;
-		$dest[implode(' / ',$full_name)] = $rs->cat_id;
-		
-		$l = $rs->level;
-	}
-	
 	echo
 	'<form action="categories.php" method="post" id="delete-category">'.
 	'<fieldset><legend>'.__('Remove a category').'</legend>'.
 	'<p><label for="del_cat">'.__('Choose a category to remove:').' '.
-	form::combo('del_cat',$cats).'</label></p> '.
+	form::combo('del_cat',$categories_combo,'','maximal').'</label></p> '.
 	'<p><label for="mov_cat">'.__('And choose the category which will receive its entries:').' '.
-	form::combo('mov_cat',$dest).'</label></p> '.
+	form::combo('mov_cat',array_merge(array(__('(No cat)') => ''),$categories_combo),'','maximal').'</label></p> '.
 	'<p><input type="submit" value="'.__('Delete').'" class="delete" /></p>'.
 	$core->formNonce().
Index: admin/index.php
===================================================================
--- admin/index.php	(revision 1535)
+++ admin/index.php	(revision 1536)
@@ -326,16 +326,18 @@
 	if ($core->auth->check('usage,contentadmin',$core->blog->id))
 	{
-		$categories_combo = array('&nbsp;' => '');
+		# Getting categories
+		$categories_combo = array(__('(No cat)') => '');
 		try {
 			$categories = $core->blog->getCategories(array('post_type'=>'post'));
-			while ($categories->fetch()) {
-				$categories_combo[] = new formSelectOption(
-					str_repeat('&nbsp;&nbsp;',$categories->level-1).
-					($categories->level-1 == 0 ? '' : '&bull; ').html::escapeHTML($categories->cat_title),
-					$categories->cat_id
-				);
+			if (!$categories->isEmpty()) {
+				while ($categories->fetch()) {
+					$catparents_combo[] = $categories_combo[] = new formSelectOption(
+						str_repeat('&nbsp;&nbsp;',$categories->level-1).($categories->level-1 == 0 ? '' : '&bull; ').html::escapeHTML($categories->cat_title),
+						$categories->cat_id
+					);
+				}
 			}
 		} catch (Exception $e) { }
-	
+		
 		echo
 		'<div id="quick">'.
@@ -352,4 +354,15 @@
 		'<p><label for="cat_id" class="classic">'.__('Category:').' '.
 		form::combo('cat_id',$categories_combo).'</label></p>'.
+		($core->auth->check('categories', $core->blog->id)
+			? '<div>'.
+			'<p id="new_cat">'.__('Add a new category').'</p>'.
+			'<p class="form-note info clear">'.__('This category will be created when you will save your post.').'</p>'.
+			'<p><label for="new_cat_title">'.__('Title:').' '.
+			form::field('new_cat_title',30,255,'','maximal').'</label></p>'.
+			'<p><label for="new_cat_parent">'.__('Parent:').' '.
+			form::combo('new_cat_parent',$categories_combo,'','maximal').
+			'</label></p>'.
+			'</div>'
+			: '').
 		'<p><input type="submit" value="'.__('Save').'" name="save" /> '.
 		($core->auth->check('publish',$core->blog->id)
Index: admin/js/_index.js
===================================================================
--- admin/js/_index.js	(revision 1140)
+++ admin/js/_index.js	(revision 1418)
@@ -61,4 +61,8 @@
 			});
 		}
+		$('#new_cat').toggleWithLegend(
+			$('#new_cat').parent().children().not('#new_cat'),
+			{} // no cookie on new category as we don't use this every day
+		);
 	}
 
Index: admin/js/_post.js
===================================================================
--- admin/js/_post.js	(revision 1256)
+++ admin/js/_post.js	(revision 1417)
@@ -123,4 +123,8 @@
 			hide: $('#post_notes').val() == ''
 		});
+		$('#new_cat').toggleWithLegend(
+			$('#new_cat').parent().children().not('#new_cat'),
+			{} // no cookie on new category as we don't use this every day
+		);
 		$('#post_lang').parent().toggleWithLegend($('#post_lang'),{
 			cookie: 'dcx_post_lang'
Index: admin/js/_posts_actions.js
===================================================================
--- admin/js/_posts_actions.js	(revision 1035)
+++ admin/js/_posts_actions.js	(revision 1419)
@@ -6,3 +6,7 @@
 		matchContains: true
 	});
+	$('#new_cat').toggleWithLegend(
+		$('#new_cat').parent().children().not('#new_cat'),
+		{} // no cookie on new category as we don't use this every day
+	);
 });
Index: admin/js/_update.js
===================================================================
--- admin/js/_update.js	(revision 1462)
+++ admin/js/_update.js	(revision 1462)
@@ -0,0 +1,5 @@
+$(function() {
+	$('form input[type=submit][name=b_del]').click(function(){
+ 		return window.confirm(dotclear.msg.confirm_delete_backup);
+ 	});
+});
Index: admin/js/_users_actions.js
===================================================================
--- admin/js/_users_actions.js	(revision 935)
+++ admin/js/_users_actions.js	(revision 1449)
@@ -1,4 +1,5 @@
 jQuery.fn.updatePermissionsForm = function() {
 	return this.each(function() {
+		
 		var perms = {};
 		var re = /^perm\[(.+?)\]\[(.+?)\]$/;
@@ -12,4 +13,5 @@
 				continue;
 			}
+			var prop;
 			prop = e.name.match(re);
 			if (!prop) {
@@ -20,37 +22,70 @@
 			}
 			perms[prop[1]][prop[2]] = e;
+			
+			// select related permissions for admin
+			if (prop[2] == 'admin') {
+				if (e.checked) {
+					admin(e,perms,re);
+				}
+				$(e).click(function(){
+					admin(this,perms,re);
+				});
+			// select related permissions for content admin
+			} else if (prop[2] == 'contentadmin') {
+				if (e.checked) {
+					contentadmin(e,perms,re);
+				}
+				$(e).click(function(){
+					contentadmin(this,perms,re);
+				});
+			// select related permissions for media admin
+			} else if (prop[2] == 'media_admin') {
+				if (e.checked) {
+					mediaadmin(e,perms,re);
+				}
+				$(e).click(function(){
+					mediaadmin(this,perms,re);
+				});
+			}
 		}
 		
-		// Update elements status
-		var E;
-		for (blog in perms) {
-			for (perm in perms[blog]) {
-				E = perms[blog][perm];
-				E.onclick = function() {};
-				
-				if (perm == 'admin' && !E.disabled) {
-					perms[blog]['usage'].disabled = E.checked;
-					perms[blog]['publish'].disabled = E.checked;
-					perms[blog]['delete'].disabled = E.checked;
-					perms[blog]['contentadmin'].disabled = E.checked;
-					perms[blog]['categories'].disabled = E.checked;
-					perms[blog]['media'].disabled = E.checked;
-					perms[blog]['media_admin'].disabled = E.checked;
-					E.onclick = function() { $(this.form).updatePermissionsForm(); };
-				} else if (perm == 'contentadmin' && !E.disabled) {
-					perms[blog]['usage'].checked = E.checked;
-					perms[blog]['publish'].checked = E.checked;
-					perms[blog]['delete'].checked = E.checked;
-					perms[blog]['usage'].disabled = E.checked;
-					perms[blog]['publish'].disabled = E.checked;
-					perms[blog]['delete'].disabled = E.checked;
-					E.onclick = function() { $(this.form).updatePermissionsForm(); };
-				} else if (perm == 'media_admin' && !E.disabled) {
-					perms[blog]['media'].checked = E.checked;
-					perms[blog]['media'].disabled = E.checked;
-					E.onclick = function() { $(this.form).updatePermissionsForm(); };
-				}
-			}
+		function admin(E,perms,re) {
+					P = E.name.match(re);
+					
+					perms[P[1]]['usage'].checked = E.checked;
+					perms[P[1]]['publish'].checked = E.checked;
+					perms[P[1]]['delete'].checked = E.checked;
+					perms[P[1]]['contentadmin'].checked = E.checked;
+					perms[P[1]]['categories'].checked = E.checked;
+					perms[P[1]]['media'].checked = E.checked;
+					perms[P[1]]['media_admin'].checked = E.checked;
+					perms[P[1]]['usage'].disabled = E.checked;
+					perms[P[1]]['publish'].disabled = E.checked;
+					perms[P[1]]['delete'].disabled = E.checked;
+					perms[P[1]]['contentadmin'].disabled = E.checked;
+					perms[P[1]]['categories'].disabled = E.checked;
+					perms[P[1]]['media'].disabled = E.checked;
+					perms[P[1]]['media_admin'].disabled = E.checked;
 		}
+		
+		function contentadmin(E,perms,re) {
+					P = E.name.match(re);
+					
+					perms[P[1]]['usage'].checked = E.checked;
+					perms[P[1]]['publish'].checked = E.checked;
+					perms[P[1]]['delete'].checked = E.checked;
+					perms[P[1]]['usage'].disabled = E.checked;
+					perms[P[1]]['publish'].disabled = E.checked;
+					perms[P[1]]['delete'].disabled = E.checked;
+		}
+		
+		function mediaadmin(E,perms,re) {
+					P = E.name.match(re);
+					
+					perms[P[1]]['media'].checked = E.checked;
+					perms[P[1]]['media'].disabled = E.checked;
+		}
+		
+		
 	});
 };
Index: admin/js/jquery/jquery.pwstrength.js
===================================================================
--- admin/js/jquery/jquery.pwstrength.js	(revision 1371)
+++ admin/js/jquery/jquery.pwstrength.js	(revision 1371)
@@ -0,0 +1,66 @@
+/**
+ * jquery.pwstrength http://matoilic.github.com/jquery.pwstrength
+ *
+ * @version v0.1.1
+ * @author Mato Ilic <info@matoilic.ch>
+ * @copyright 2013 Mato Ilic
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ */
+;(function($) {
+    $.pwstrength = function(password) {
+        var score = 0, length = password.length, upperCase, lowerCase, digits, nonAlpha;
+        
+        if(length < 5) score += 0;
+        else if(length < 8) score += 5;
+        else if(length < 16) score += 10;
+        else score += 15;
+        
+        lowerCase = password.match(/[a-z]/g);
+        if(lowerCase) score += 1;
+        
+        upperCase = password.match(/[A-Z]/g);
+        if(upperCase) score += 5;
+        
+        if(upperCase && lowerCase) score += 2;
+        
+        digits = password.match(/\d/g);
+        if(digits && digits.length > 1) score += 5;
+        
+        nonAlpha = password.match(/\W/g)
+        if(nonAlpha) score += (nonAlpha.length > 1) ? 15 : 10;
+        
+        if(upperCase && lowerCase && digits && nonAlpha) score += 15;
+
+        if(password.match(/\s/)) score += 10;
+
+        if(score < 15) return 0;
+        if(score < 20) return 1;
+        if(score < 35) return 2;
+        if(score < 50) return 3;
+        return 4;
+    };
+    
+    function updateIndicator(event) {
+        var strength = $.pwstrength($(this).val()), options = event.data, klass;
+        klass = options.classes[strength];
+        
+        options.indicator.removeClass(options.indicator.data('pwclass'));
+        options.indicator.data('pwclass', klass);
+        options.indicator.addClass(klass);
+        options.indicator.find(options.label).html(options.texts[strength]);
+    }
+    
+    $.fn.pwstrength = function(options) {
+        var options = $.extend({
+            label: '.label',
+            classes: ['pw-very-weak', 'pw-weak', 'pw-mediocre', 'pw-strong', 'pw-very-strong'],
+            texts: ['very weak', 'weak', 'mediocre', 'strong', 'very strong']
+        }, options || {});
+        options.indicator = $('#' + this.data('indicator'));
+        
+        return this.keyup(options, updateIndicator);
+    };
+})(jQuery);
Index: admin/post.php
===================================================================
--- admin/post.php	(revision 1288)
+++ admin/post.php	(revision 1488)
@@ -49,16 +49,4 @@
 	$post_status = -2;
 }
-
-# Getting categories
-$categories_combo = array('&nbsp;' => '');
-try {
-	$categories = $core->blog->getCategories(array('post_type'=>'post'));
-	while ($categories->fetch()) {
-		$categories_combo[] = new formSelectOption(
-			str_repeat('&nbsp;&nbsp;',$categories->level-1).($categories->level-1 == 0 ? '' : '&bull; ').html::escapeHTML($categories->cat_title),
-			$categories->cat_id
-		);
-	}
-} catch (Exception $e) { }
 
 # Status combo
@@ -218,4 +206,22 @@
 if (!empty($_POST) && !empty($_POST['save']) && $can_edit_post && !$bad_dt)
 {
+	# Create category
+	if (!empty($_POST['new_cat_title']) && $core->auth->check('categories', $core->blog->id)) {
+	
+		$cur_cat = $core->con->openCursor($core->prefix.'category');
+		$cur_cat->cat_title = $_POST['new_cat_title'];
+		$cur_cat->cat_url = '';
+		
+		$parent_cat = !empty($_POST['new_cat_parent']) ? $_POST['new_cat_parent'] : '';
+		
+		# --BEHAVIOR-- adminBeforeCategoryCreate
+		$core->callBehavior('adminBeforeCategoryCreate', $cur_cat);
+		
+		$cat_id = $core->blog->addCategory($cur_cat, (integer) $parent_cat);
+		
+		# --BEHAVIOR-- adminAfterCategoryCreate
+		$core->callBehavior('adminAfterCategoryCreate', $cur_cat, $cat_id);
+	}
+	
 	$cur = $core->con->openCursor($core->prefix.'post');
 	
@@ -283,4 +289,18 @@
 	}
 }
+
+# Getting categories
+$categories_combo = array(__('(No cat)') => '');
+try {
+	$categories = $core->blog->getCategories(array('post_type'=>'post'));
+	if (!$categories->isEmpty()) {
+		while ($categories->fetch()) {
+			$catparents_combo[] = $categories_combo[] = new formSelectOption(
+				str_repeat('&nbsp;&nbsp;',$categories->level-1).($categories->level-1 == 0 ? '' : '&bull; ').html::escapeHTML($categories->cat_title),
+				$categories->cat_id
+			);
+		}
+	}
+} catch (Exception $e) { }
 
 /* DISPLAY
@@ -443,4 +463,16 @@
 	'</label></p>'.
 	
+	($core->auth->check('categories', $core->blog->id) ?
+		'<div>'.
+		'<p id="new_cat">'.__('Add a new category').'</p>'.
+		'<p class="form-note info clear">'.__('This category will be created when you will save your post.').'</p>'.
+		'<p><label for="new_cat_title">'.__('Title:').' '.
+		form::field('new_cat_title',30,255,'','maximal').'</label></p>'.
+		'<p><label for="new_cat_parent">'.__('Parent:').' '.
+		form::combo('new_cat_parent',$categories_combo,'','maximal').
+		'</label></p>'.
+		'</div>'
+	: '').
+	
 	'<p><label for="post_status">'.__('Entry status:').
 	form::combo('post_status',$status_combo,$post_status,'','',!$can_publish).
Index: admin/posts_actions.php
===================================================================
--- admin/posts_actions.php	(revision 1179)
+++ admin/posts_actions.php	(revision 1421)
@@ -16,4 +16,5 @@
 
 $params = array();
+$action = '';
 
 /* Actions
@@ -126,7 +127,26 @@
 	elseif ($action == 'category' && isset($_POST['new_cat_id']))
 	{
-		try
-		{
-			$core->blog->updPostsCategory($posts_ids,$_POST['new_cat_id']);
+		$new_cat_id = $_POST['new_cat_id'];
+		
+		try
+		{
+			if (!empty($_POST['new_cat_title']) && $core->auth->check('categories', $core->blog->id))
+			{
+				$cur_cat = $core->con->openCursor($core->prefix.'category');
+				$cur_cat->cat_title = $_POST['new_cat_title'];
+				$cur_cat->cat_url = '';
+				
+				$parent_cat = !empty($_POST['new_cat_parent']) ? $_POST['new_cat_parent'] : '';
+				
+				# --BEHAVIOR-- adminBeforeCategoryCreate
+				$core->callBehavior('adminBeforeCategoryCreate', $cur_cat);
+				
+				$new_cat_id = $core->blog->addCategory($cur_cat, (integer) $parent_cat);
+				
+				# --BEHAVIOR-- adminAfterCategoryCreate
+				$core->callBehavior('adminAfterCategoryCreate', $cur_cat, $new_cat_id);
+			}
+			
+			$core->blog->updPostsCategory($posts_ids, $new_cat_id);
 			
 			http::redirect($redir);
@@ -246,15 +266,15 @@
 	echo '<h2 class="page-title">'.__('Change category for entries').'</h2>';
 	
-	# categories list
 	# Getting categories
-	$categories_combo = array('&nbsp;' => '');
+	$categories_combo = array(__('(No cat)') => '');
 	try {
 		$categories = $core->blog->getCategories(array('post_type'=>'post'));
-		while ($categories->fetch()) {
-			$categories_combo[] = new formSelectOption(
-				str_repeat('&nbsp;&nbsp;',$categories->level-1).
-				($categories->level-1 == 0 ? '' : '&bull; ').html::escapeHTML($categories->cat_title),
-				$categories->cat_id
-			);
+		if (!$categories->isEmpty()) {
+			while ($categories->fetch()) {
+				$catparents_combo[] = $categories_combo[] = new formSelectOption(
+					str_repeat('&nbsp;&nbsp;',$categories->level-1).($categories->level-1 == 0 ? '' : '&bull; ').html::escapeHTML($categories->cat_title),
+					$categories->cat_id
+				);
+			}
 		}
 	} catch (Exception $e) { }
@@ -265,4 +285,16 @@
 	form::combo('new_cat_id',$categories_combo,'').
 	'</label> ';
+	
+	if ($core->auth->check('categories', $core->blog->id)) {
+		echo 
+		'<div>'.
+		'<p id="new_cat">'.__('Add a new category').'</p>'.
+		'<p><label for="new_cat_title">'.__('Title:').' '.
+		form::field('new_cat_title',30,255,'','maximal').'</label></p>'.
+		'<p><label for="new_cat_parent">'.__('Parent:').' '.
+		form::combo('new_cat_parent',$categories_combo,'','maximal').
+		'</label></p>'.
+		'</div>';
+	}
 	
 	echo
Index: admin/preferences.php
===================================================================
--- admin/preferences.php	(revision 1179)
+++ admin/preferences.php	(revision 1375)
@@ -344,4 +344,17 @@
 	($user_acc_nodragdrop ? '' : dcPage::jsLoad('js/_preferences-dragdrop.js')).
 	dcPage::jsLoad('js/jquery/jquery-ui.custom.js').
+	dcPage::jsLoad('js/jquery/jquery.pwstrength.js').
+		'<script type="text/javascript">'."\n".
+		"//<![CDATA[\n".
+		"\$(function() {\n".
+		"	\$('#new_pwd').pwstrength({texts: ['".
+				sprintf(__('Password strength: %s'),__('very weak'))."', '".
+				sprintf(__('Password strength: %s'),__('weak'))."', '".
+				sprintf(__('Password strength: %s'),__('mediocre'))."', '".
+				sprintf(__('Password strength: %s'),__('strong'))."', '".
+				sprintf(__('Password strength: %s'),__('very strong'))."']});\n".
+		"});\n".
+		"\n//]]>\n".
+		"</script>\n".
 	dcPage::jsPageTabs($default_tab).
 	dcPage::jsConfirmClose('user-form').
@@ -416,14 +429,20 @@
 	'<legend>'.__('Change your password').'</legend>'.
 	
-	'<p><label for="new_pwd">'.__('New password:').
-	form::password('new_pwd',20,255).'</label></p>'.
+	'<div class="pw-table">'.
+	'<p class="pw-cell"><label for="new_pwd">'.__('New password:').'</label>'.
+	form::password('new_pwd',20,255,'','','',false,' data-indicator="pwindicator" ').'</p>'.
+    '<div id="pwindicator">'.
+    '    <div class="bar"></div>'.
+    '    <p class="label no-margin"></p>'.
+    '</div>'.
+    '</div>'.
 	
-	'<p><label for="new_pwd_c">'.__('Confirm password:').
-	form::password('new_pwd_c',20,255).'</label></p>'.
+	'<p><label for="new_pwd_c">'.__('Confirm password:').'</label>'.
+	form::password('new_pwd_c',20,255).'</p>'.
 	'</fieldset>'.
 	
 	'<p>'.__('If you have changed this user email or password you must provide your current password to save these modifications.').'</p>'.
-	'<p><label for="cur_pwd">'.__('Your password:').
-	form::password('cur_pwd',20,255).'</label></p>';
+	'<p><label for="cur_pwd">'.__('Your password:').'</label>'.
+	form::password('cur_pwd',20,255).'</p>';
 }
 
Index: admin/style/default.css
===================================================================
--- admin/style/default.css	(revision 1179)
+++ admin/style/default.css	(revision 1375)
@@ -901,5 +901,4 @@
 	margin: 0;
 }
-
 /* paragraphe pour bouton Nouveau bidule */
 p.top-add {
@@ -1384,2 +1383,42 @@
 	color: white;
 }
+
+/* --------------------------------------------------------------- password indcator */
+.pw-table {
+	display: table;
+	margin-bottom: 1em;
+}
+.pw-cell {
+	display: table-cell;
+	margin-bottom: 1em;
+}
+#pwindicator {
+	display: table-cell;
+	vertical-align: bottom;
+	padding-left: 1.5em;
+	height: 3.8em;
+}
+#pwindicator .bar {
+	height: 6px;
+	margin-bottom: 4px;
+}
+.pw-very-weak .bar {
+	background: #900;
+	width: 30px;
+}
+.pw-weak .bar {
+	background: #c00;
+	width: 60px;
+}
+.pw-mediocre .bar {
+	background: #f60;
+	width: 90px;
+}
+.pw-strong .bar {
+	background: #060;
+	width: 120px;
+}
+.pw-very-strong .bar {
+	background: #0c0;
+	width: 150px;
+}
Index: admin/update.php
===================================================================
--- admin/update.php	(revision 1287)
+++ admin/update.php	(revision 1479)
@@ -106,5 +106,8 @@
 					throw new Exception(
 						sprintf(__('Downloaded Dotclear archive seems to be corrupted. '.
-						'Try <a %s>download it</a> again.'),'href="'.$p_url.'?step=download"')
+						'Try <a %s>download it</a> again.'),'href="'.$p_url.'?step=download"').
+						' '.
+						__('If this problem persists try to '.
+						'<a href="http://dotclear.org/download">update manually</a>.')
 					);
 				}
@@ -168,5 +171,8 @@
 -------------------------------------------------------- */
 dcPage::open(__('Dotclear update'),
-	(!$step ? dcPage::jsPageTabs($default_tab) : '')
+	(!$step ? 
+		dcPage::jsPageTabs($default_tab).
+		dcPage::jsLoad('js/_update.js')
+	: '')
 );
 
Index: inc/admin/lib.dc.page.php
===================================================================
--- inc/admin/lib.dc.page.php	(revision 1256)
+++ inc/admin/lib.dc.page.php	(revision 1462)
@@ -160,5 +160,5 @@
 		if ($core->error->flag()) {
 			echo
-			'<div class="error"><p><strong>'.(count($core->error->getErrors()) > 1 ? __('Errors:') : __('Error:')).'</p></strong>'.
+			'<div class="error"><p><strong>'.(count($core->error->getErrors()) > 1 ? __('Errors:') : __('Error:')).'</strong></p>'.
 			$core->error->toHTML().
 			'</div>';
@@ -466,4 +466,6 @@
 		self::jsVar('dotclear.msg.confirm_delete_theme',
 			__('Are you sure you want to delete "%s" theme?')).
+		self::jsVar('dotclear.msg.confirm_delete_backup',
+			__('Are you sure you want to delete this backup?')).
 		self::jsVar('dotclear.msg.zip_file_content',
 			__('Zip file content')).
Index: inc/admin/lib.pager.php
===================================================================
--- inc/admin/lib.pager.php	(revision 1179)
+++ inc/admin/lib.pager.php	(revision 1364)
@@ -87,5 +87,5 @@
 			html::escapeHTML($this->rs->cat_title));
 		} else {
-			$cat_title = __('None');
+			$cat_title = __('(No cat)');
 		}
 		
Index: inc/core/class.dc.blog.php
===================================================================
--- inc/core/class.dc.blog.php	(revision 1262)
+++ inc/core/class.dc.blog.php	(revision 1429)
@@ -645,18 +645,55 @@
 	private function checkCategory($title,$url,$id=null)
 	{
-		$strReq = 'SELECT cat_id '.
-				'FROM '.$this->prefix.'category '.
-				"WHERE cat_url = '".$this->con->escape($url)."' ".
-				"AND blog_id = '".$this->con->escape($this->id)."' ";
-		
-		if ($id !== null) {
-			$strReq .= 'AND cat_id <> '.(integer) $id.' ';
-		}
+		# Let's check if URL is taken...
+		$strReq = 
+			'SELECT cat_url FROM '.$this->prefix.'category '.
+			"WHERE cat_url = '".$this->con->escape($url)."' ".
+			($id ? 'AND cat_id <> '.(integer) $id. ' ' : '').
+			"AND blog_id = '".$this->con->escape($this->id)."' ".
+			'ORDER BY cat_url DESC';
 		
 		$rs = $this->con->select($strReq);
 		
-		if (!$rs->isEmpty()) {
-			throw new Exception(__('Category URL must be unique.'));
-		}
+		if (!$rs->isEmpty())
+		{
+			if ($this->con->driver() == 'mysql') {
+				$clause = "REGEXP '^".$this->con->escape($url)."[0-9]+$'";
+			} elseif ($this->con->driver() == 'pgsql') {
+				$clause = "~ '^".$this->con->escape($url)."[0-9]+$'";
+			} else {
+				$clause = "LIKE '".$this->con->escape($url)."%'";
+			}
+			$strReq = 
+				'SELECT cat_url FROM '.$this->prefix.'category '.
+				"WHERE cat_url ".$clause.' '.
+				($id ? 'AND cat_id <> '.(integer) $id. ' ' : '').
+				"AND blog_id = '".$this->con->escape($this->id)."' ".
+				'ORDER BY cat_url DESC ';
+			
+			$rs = $this->con->select($strReq);
+			$a = array();
+			while ($rs->fetch()) {
+				$a[] = $rs->cat_url;
+			}
+			
+			natsort($a);
+			$t_url = end($a);
+			
+			if (preg_match('/(.*?)([0-9]+)$/',$t_url,$m)) {
+				$i = (integer) $m[2];
+				$url = $m[1];
+			} else {
+				$i = 1;
+			}
+			
+			return $url.($i+1);
+		}
+		
+		# URL is empty?
+		if ($url == '') {
+			throw new Exception(__('Empty category URL'));
+		}
+		
+		return $url;
 	}
 	
@@ -680,5 +717,5 @@
 		
 		# Check if title or url are unique
-		$this->checkCategory($cur->cat_title,$cur->cat_url,$id);
+		$cur->cat_url = $this->checkCategory($cur->cat_title,$cur->cat_url,$id);
 		
 		if ($cur->cat_desc !== null) {
Index: inc/core/class.dc.media.php
===================================================================
--- inc/core/class.dc.media.php	(revision 1270)
+++ inc/core/class.dc.media.php	(revision 1381)
@@ -30,4 +30,5 @@
 	
 	public $thumb_tp = '%s/.%s_%s.jpg';	///< <b>string</b> Thumbnail file pattern
+	public $thumb_tp_alpha = '%s/.%s_%s.png'; ///< <b>string</b> Thumbnail file pattern (with alpha layer)
 	
 	/**
@@ -298,6 +299,7 @@
 			$f->media_thumb = array();
 			$p = path::info($f->relname);
-			$thumb = sprintf($this->thumb_tp,$this->root.'/'.$p['dirname'],$p['base'],'%s');
-			$thumb_url = sprintf($this->thumb_tp,$this->root_url.$p['dirname'],$p['base'],'%s');
+			$alpha = ($p['extension'] == 'png') || ($p['extension'] == 'PNG');
+			$thumb = sprintf(($alpha ? $this->thumb_tp_alpha : $this->thumb_tp),$this->root.'/'.$p['dirname'],$p['base'],'%s');
+			$thumb_url = sprintf(($alpha ? $this->thumb_tp_alpha : $this->thumb_tp),$this->root_url.$p['dirname'],$p['base'],'%s');
 			
 			# Cleaner URLs
@@ -921,5 +923,6 @@
 		
 		$p = path::info($file);
-		$thumb = sprintf($this->thumb_tp,$p['dirname'],$p['base'],'%s');
+		$alpha = ($p['extension'] == 'png') || ($p['extension'] == 'PNG');
+		$thumb = sprintf(($alpha ? $this->thumb_tp_alpha : $this->thumb_tp),$p['dirname'],$p['base'],'%s');
 		
 		try
@@ -940,5 +943,5 @@
 					$rate = ($s[0] < 100 ? 95 : ($s[0] < 600 ? 90 : 85));
 					$img->resize($s[0],$s[0],$s[1]);
-					$img->output('jpeg',$thumb_file,$rate);
+					$img->output(($alpha ? 'png' : 'jpeg'),$thumb_file,$rate);
 					$img->loadImage($file);
 				}
@@ -959,8 +962,10 @@
 		{
 			$p = path::info($file->relname);
-			$thumb_old = sprintf($this->thumb_tp,$p['dirname'],$p['base'],'%s');
+			$alpha = ($p['extension'] == 'png') || ($p['extension'] == 'PNG');
+			$thumb_old = sprintf(($alpha ? $this->thumb_tp_alpha : $this->thumb_tp),$p['dirname'],$p['base'],'%s');
 			
 			$p = path::info($newFile->relname);
-			$thumb_new = sprintf($this->thumb_tp,$p['dirname'],$p['base'],'%s');
+			$alpha = ($p['extension'] == 'png') || ($p['extension'] == 'PNG');
+			$thumb_new = sprintf(($alpha ? $this->thumb_tp_alpha : $this->thumb_tp),$p['dirname'],$p['base'],'%s');
 			
 			foreach ($this->thumb_sizes as $suffix => $s) {
@@ -975,5 +980,6 @@
 	{
 		$p = path::info($f);
-		$thumb = sprintf($this->thumb_tp,'',$p['base'],'%s');
+		$alpha = ($p['extension'] == 'png') || ($p['extension'] == 'PNG');
+		$thumb = sprintf(($alpha ? $this->thumb_tp_alpha : $this->thumb_tp),'',$p['base'],'%s');
 		
 		foreach ($this->thumb_sizes as $suffix => $s) {
Index: inc/prepend.php
===================================================================
--- inc/prepend.php	(revision 1233)
+++ inc/prepend.php	(revision 1352)
@@ -123,5 +123,5 @@
 # Constants
 define('DC_ROOT',path::real(dirname(__FILE__).'/..'));
-define('DC_VERSION','2.5.2-dev');
+define('DC_VERSION','2.5.3-dev');
 define('DC_DIGESTS',dirname(__FILE__).'/digests');
 define('DC_L10N_ROOT',dirname(__FILE__).'/../locales');
Index: locales/en/help/user_pref.html
===================================================================
--- locales/en/help/user_pref.html	(revision 243)
+++ locales/en/help/user_pref.html	(revision 1487)
@@ -8,5 +8,5 @@
 <h3>My profile</h3>
 <dl>
-  <dt>Name, First name</dt>
+  <dt>Last name, First name</dt>
   <dd>If the display name is empty, the author's name will be displayed
   as his Firstname and Name as set here.</dd>
@@ -37,5 +37,5 @@
 <dl>  
   <dt>Preferred format</dt>
-  <dd>To choose the prefered post syntax. Wiki is a simplified syntax
+  <dd>To choose the preferred post syntax. Wiki is a simplified syntax
   and will be converted to valid xhtml&nbsp;; Unless you have a perfect
   understanding of html, we advise you to choose the wiki syntax.</dd>
@@ -59,6 +59,5 @@
   
   <dt>Activate enhanced uploader in media manager</dt>
-  <dd>This option allows you to upload several files at the same time on your server.
-  Needs the presence of Flash plugin in your browser.</dd>
+  <dd>This option allows you to upload several files at the same time on your server.</dd>
   
   <dt>Dashboard modules</dt>
Index: locales/en/main.po
===================================================================
--- locales/en/main.po	(revision 1256)
+++ locales/en/main.po	(revision 1479)
@@ -1917,4 +1917,7 @@
 msgstr ""
 
+msgid "If this problem persists try to <a href="http://dotclear.org/download">update manually</a>."
+msgstr ""
+
 msgid "The following files of your Dotclear installation have been modified so we won't try to update your installation. Please try to <a href=\"http://dotclear.org/download\">update manually</a>."
 msgstr ""
@@ -2139,4 +2142,7 @@
 msgstr ""
 
+msgid "Are you sure you want to delete this backup?"
+msgstr ""
+
 msgid "Zip file content"
 msgstr ""
@@ -2790,2 +2796,5 @@
 msgid "The following error was encountered while trying to read the database:"
 msgstr ""
+
+msgid "(No cat)"
+msgstr "(none)"
Index: locales/fr/main.po
===================================================================
--- locales/fr/main.po	(revision 1288)
+++ locales/fr/main.po	(revision 1488)
@@ -1990,4 +1990,7 @@
 msgstr "L'archive de Dotclear téléchargée semble être corrompue. Essayer de la <a %s>télécharger</a> à nouveau."
 
+msgid "If this problem persists try to <a href="http://dotclear.org/download">update manually</a>."
+msgstr "Si le problème persiste essayer de <a href="http://dotclear.org/download">mettre à jour manuellement</a>."
+
 msgid "The following files of your Dotclear installation have been modified so we won't try to update your installation. Please try to <a href=\"http://dotclear.org/download\">update manually</a>."
 msgstr "Comme les fichiers suivants de votre installation de Dotclear ont été modifiés, votre installation ne peut être mise à jour. Merci de <a href=\"http://fr.dotclear.org/download\">mettre à jour manuellement</a>."
@@ -2210,5 +2213,5 @@
 
 msgid "Users with posts cannot be deleted."
-msgstr "Les utilisateurs ayant écrit des billets ne peuvent être effacées."
+msgstr "Les utilisateurs ayant écrit des billets ne peuvent être supprimés."
 
 #, php-format
@@ -2251,4 +2254,7 @@
 msgid "Are you sure you want to delete \"%s\" theme?"
 msgstr "Êtes-vous certain de vouloir supprimer le thème \"%s\" ?"
+
+msgid "Are you sure you want to delete this backup?"
+msgstr "Êtes-vous certain de vouloir supprimer cette sauvegarde ?"
 
 msgid "Zip file content"
@@ -2940,2 +2946,26 @@
 msgid "Invalid publication date"
 msgstr "Date de publication invalide"
+
+msgid "(No cat)"
+msgstr "(aucune)"
+
+msgid "Password strength: %s"
+msgstr "Force du mot de passe : %s"
+
+msgid "very weak"
+msgstr "très faible"
+
+msgid "weak"
+msgstr "faible"
+
+msgid "mediocre"
+msgstr "moyen"
+
+msgid "strong"
+msgstr "fort"
+
+msgid "very strong"
+msgstr "très fort"
+
+msgid "This category will be created when you will save your post."
+msgstr "Cette catégorie sera créée lorsque vous enregistrerez votre billet."
Index: locales/fr/plugins.po
===================================================================
--- locales/fr/plugins.po	(revision 1286)
+++ locales/fr/plugins.po	(revision 1445)
@@ -1166,4 +1166,10 @@
 msgstr "Pages"
 
+msgid "My first page"
+msgstr "Ma première page"
+
+msgid "This is your first page. When you\'re ready to blog, log in to edit or delete it."
+msgstr "Ceci est votre première page. Quand vous serez prêt à bloguer, connectez-vous pour l'éditer ou la supprimer."
+
 #, php-format
 msgid "%d page"
Index: plugins/antispam/filters/class.dc.filter.iplookup.php
===================================================================
--- plugins/antispam/filters/class.dc.filter.iplookup.php	(revision 1179)
+++ plugins/antispam/filters/class.dc.filter.iplookup.php	(revision 1361)
@@ -44,19 +44,13 @@
 		}
 
-		$match = array();
-
 		$bls = $this->getServers();
 		$bls = preg_split('/\s*,\s*/',$bls);
 
-		foreach ($bls as $bl)
-		{
+		foreach ($bls as $bl) {
 			if ($this->dnsblLookup($ip,$bl)) {
-				$match[] = $bl;
+				// Pass by reference $status to contain matching DNSBL
+				$status = $bl;
+				return true;
 			}
-		}
-
-		if (!empty($match)) {
-			$status = substr(implode(', ',$match),0,128);
-			return true;
 		}
 	}
Index: plugins/antispam/filters/class.dc.filter.linkslookup.php
===================================================================
--- plugins/antispam/filters/class.dc.filter.linkslookup.php	(revision 1179)
+++ plugins/antispam/filters/class.dc.filter.linkslookup.php	(revision 1366)
@@ -64,18 +64,8 @@
 	private function getLinks($text)
 	{
-		$res = array();
-		
-		# href attribute on "a" tags
-		if (preg_match_all('/<a ([^>]+)>/ms', $text, $match, PREG_SET_ORDER))
-		{
-			for ($i = 0; $i<count($match); $i++)
-			{
-				if (preg_match('/href="(http:\/\/[^"]+)"/ms', $match[$i][1], $matches)) {
-					$res[] = $matches[1];
-				}
-			}
-		}
-		
-		return $res;
+		// href attribute on "a" tags is second match
+		preg_match_all('|<a.*?href="(http.*?)"|', $text, $parts);
+
+		return $parts[1];
 	}
 }
Index: plugins/antispam/inc/lib.dc.antispam.php
===================================================================
--- plugins/antispam/inc/lib.dc.antispam.php	(revision 1179)
+++ plugins/antispam/inc/lib.dc.antispam.php	(revision 1478)
@@ -75,5 +75,5 @@
 		if (($count = self::countSpam($core)) > 0) {
 			$str = ($count > 1) ? __('(including %d spam comments)') : __('(including %d spam comment)');
-			$icons['comments'][0] .= '</a> <br /><a href="comments.php?status=-2"><span>'.sprintf($str,$count).'</span>';
+			$icons['comments'][0] .= '</span></a> <br /><a href="comments.php?status=-2"><span>'.sprintf($str,$count);
 		}
 	}
@@ -83,5 +83,5 @@
 		if (($count = self::countSpam($core)) > 0) {
 			$str = ($count > 1) ? __('(including %d spam comments)') : __('(including %d spam comment)');
-			return '</a> <br /><a href="comments.php?status=-2"><span>'.sprintf($str,$count).'</span>';
+			return '</span></a> <br /><a href="comments.php?status=-2"><span>'.sprintf($str,$count);
 		} else {
 			return '';
Index: plugins/attachments/_admin.php
===================================================================
--- plugins/attachments/_admin.php	(revision 1179)
+++ plugins/attachments/_admin.php	(revision 1467)
@@ -36,5 +36,5 @@
 				'<img src="'.$f->media_icon.'" alt="" title="'.$f->basename.'" /></a>'.
 				'<ul>'.
-				'<li><a class="media-link" href="media_item.php?id='.$f->media_id.'"'.
+				'<li><a class="media-link" href="media_item.php?id='.$f->media_id.'" '.
 				'title="'.$f->basename.'">'.$ftitle.'</a></li>'.
 				'<li>'.$f->media_dtstr.'</li>'.
Index: plugins/pages/_define.php
===================================================================
--- plugins/pages/_define.php	(revision 1179)
+++ plugins/pages/_define.php	(revision 1442)
@@ -16,5 +16,5 @@
 	/* Description*/	"Serve entries as simple web pages",
 	/* Author */		"Olivier Meunier",
-	/* Version */		'1.2',
+	/* Version */		'1.3',
 	array(
 		'permissions' =>	'contentadmin,pages',
@@ -22,3 +22,2 @@
 	)
 );
-?>
Index: plugins/pages/_install.php
===================================================================
--- plugins/pages/_install.php	(revision 1494)
+++ plugins/pages/_install.php	(revision 1494)
@@ -0,0 +1,53 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+
+if (!defined('DC_CONTEXT_ADMIN')){return;}
+
+$version = $core->plugins->moduleInfo('pages','version');
+if (version_compare($core->getVersion('pages'),$version,'>=')) {
+	return;
+}
+
+/* Create first page
+-------------------------------------------------------- */
+$core->blog->settings->addNamespace('pages');
+
+$params = array(
+	'post_type' => 'page',
+	'no_content' => true
+);
+$counter = $core->blog->getPosts($params,true);
+
+If( $counter->f(0) == 0 && $core->blog->settings->pages->firstpage == null ) {
+	
+	$core->blog->settings->pages->put('firstpage',true, 'boolean');
+
+	$cur = $core->con->openCursor($core->prefix.'post');
+	$cur->user_id = $core->auth->userID();
+	$cur->post_type = 'page';
+	$cur->post_format = 'xhtml';
+	$cur->post_lang = $core->blog->settings->system->lang;
+	$cur->post_title = __('My first page');
+	$cur->post_content = __('<p>'.__('This is your first page. When you\'re ready to blog, log in to edit or delete it.').'</p>');
+	$cur->post_content_xhtml = $cur->post_content;
+	$cur->post_excerpt = '';
+	$cur->post_excerpt_xhtml = $cur->post_excerpt;
+	$cur->post_status = 1;
+	$cur->post_open_comment = 0;
+	$cur->post_open_tb = 0;
+	$post_id = $core->blog->addPost($cur);
+	
+}
+
+$core->setVersion('pages',$version);
+return true;
+?>
