Index: /extensions/SmartAlbums/admin/album.php
===================================================================
--- /extensions/SmartAlbums/admin/album.php	(revision 19445)
+++ /extensions/SmartAlbums/admin/album.php	(revision 19446)
@@ -72,4 +72,9 @@
 if (isset($_POST['submitFilters']))
 {
+  if (defined('SMART_DEBUG'))
+  {
+    var_dump($_POST['filters']);
+  }
+  
   // test if it was a Smart Album
   $query = '
@@ -98,20 +103,20 @@
     pwg_query('DELETE FROM '.CATEGORY_FILTERS_TABLE.' WHERE category_id = '.$cat_id.';');
     
+    $inserts = array();
     foreach ($_POST['filters'] as $filter)
     {
       if (($filter = smart_check_filter($filter)) != false)
       {
-        $query = '
-INSERT IGNORE INTO '.CATEGORY_FILTERS_TABLE.' 
-  VALUES(
-    '.$cat_id.', 
-    "'.$filter['type'].'", 
-    "'.$filter['cond'].'", 
-    "'.$filter['value'].'"
-  )
-;';
-      pwg_query($query);
+        $filter['category_id'] = $cat_id;
+        $inserts[] = $filter;
       }
     }
+    
+    mass_inserts(
+      CATEGORY_FILTERS_TABLE, 
+      array('category_id', 'type', 'cond', 'value'), 
+      $inserts,
+      array('ignore'=>true)
+      );
     
     $associated_images = smart_make_associations($cat_id);
@@ -143,10 +148,10 @@
     'name' => l10n('Date'),
     'options' => array(
-      'the_post'      => l10n('Added on'),
-      'before_post'   => l10n('Added before'),
-      'after_post'    => l10n('Added after'),
-      'the_taken'     => l10n('Created on'),
-      'before_taken'  => l10n('Created before'),
-      'after_taken'   => l10n('Created after'),
+      'the_post'     => l10n('Added on'),
+      'before_post'  => l10n('Added before'),
+      'after_post'   => l10n('Added after'),
+      'the_taken'    => l10n('Created on'),
+      'before_taken' => l10n('Created before'),
+      'after_taken'  => l10n('Created after'),
       ),
     ),
@@ -160,4 +165,5 @@
       'not_begin'   => l10n('Doesn\'t begin with'),
       'not_end'     => l10n('Doesn\'t end with'),
+      'regex'       => l10n('Regular expression'),
       ),
     ),
@@ -171,4 +177,12 @@
       ),
     ),
+  'dimensions' => array(
+    'name' => l10n('Dimensions'),
+    'options' => array(
+      'width'  => l10n('Width'),
+      'height' => l10n('Height'),
+      'ratio'  => l10n('Ratio').' ('.l10n('Width').'/'.l10n('Height').')',
+      ),
+    ),
   'author' => array(
     'name' => l10n('Author'),
@@ -178,4 +192,5 @@
       'not_is'  => l10n('Is not'),
       'not_in'  => l10n('Is not in'),
+      'regex'   => l10n('Regular expression'),
       ),
     ),
@@ -205,9 +220,10 @@
 $template->assign('options', $options);
 
+
 /* get filters for this album */
 $query = '
 SELECT * 
   FROM '.CATEGORY_FILTERS_TABLE.' 
-  WHERE category_id = '.$cat_id.' 
+  WHERE category_id = '.$cat_id.'
   ORDER BY 
     type ASC, 
@@ -216,13 +232,17 @@
 $result = pwg_query($query);
 
+$template->assign('filter_mode', 'and');
+
 while ($filter = pwg_db_fetch_assoc($result))
 {
-  // get tags name and id
-  if ($filter['type'] == 'tags')
+  if ($filter['type'] == 'mode')
+  {
+    $template->assign('filter_mode', $filter['value']);
+    continue;
+  }
+  else if ($filter['type'] == 'tags')
   {
     $query = '
-SELECT
-    id,
-    name
+SELECT id, name
   FROM '.TAGS_TABLE.'
   WHERE id IN('.$filter['value'].')
@@ -230,22 +250,20 @@
     $filter['value'] = get_taglist($query); 
   }
-  else if ($filter['type'] == 'album')
-  {
-    $filter['value'] = explode(',', $filter['value']); 
-  }
   
-  $template->append('filters', array(
-    'TYPE' => $filter['type'],
-    'COND' => $filter['cond'],
-    'VALUE' => $filter['value'],
-    'CAPTION' => $options[ $filter['type'] ]['name'],
+  $template->append('filters', $filter);
+}
+
+
+/* format types */
+$template->assign('format_options', array(
+  'portrait' => l10n('Portrait'),
+  'square'   => l10n('Square'),
+  'lanscape' => l10n('Landscape'),
+  'panorama' => l10n('Panorama'),
   ));
-}
 
 /* all tags */
 $query = '
-SELECT
-    id,
-    name
+SELECT id, name
   FROM '.TAGS_TABLE.'
 ;';
@@ -262,4 +280,88 @@
 ;';
 display_select_cat_wrapper($query, array(), 'all_albums');
+
+// +-----------------------------------------------------------------------+
+// |                              dimensions                               |
+// +-----------------------------------------------------------------------+
+
+$widths = array();
+$heights = array();
+$ratios = array();
+
+// get all width, height and ratios
+$query = '
+SELECT
+  DISTINCT width, height
+  FROM '.IMAGES_TABLE.'
+  WHERE width IS NOT NULL
+    AND height IS NOT NULL
+;';
+$result = pwg_query($query);
+
+while ($row = pwg_db_fetch_assoc($result))
+{
+  $widths[] = $row['width'];
+  $heights[] = $row['height'];
+  $ratios[] = floor($row['width'] * 100 / $row['height']) / 100;
+}
+
+$widths = array_unique($widths);
+sort($widths);
+
+$heights = array_unique($heights);
+sort($heights);
+
+$ratios = array_unique($ratios);
+sort($ratios);
+
+$dimensions['bounds'] = array(
+  'min_width' => $widths[0],
+  'max_width' => $widths[count($widths)-1],
+  'min_height' => $heights[0],
+  'max_height' => $heights[count($heights)-1],
+  'min_ratio' => $ratios[0],
+  'max_ratio' => $ratios[count($ratios)-1],
+  );
+
+// find ratio categories
+$ratio_categories = array(
+  'portrait' => array(),
+  'square' => array(),
+  'landscape' => array(),
+  'panorama' => array(),
+  );
+
+foreach ($ratios as $ratio)
+{
+  if ($ratio < 0.95)
+  {
+    $ratio_categories['portrait'][] = $ratio;
+  }
+  else if ($ratio >= 0.95 and $ratio <= 1.05)
+  {
+    $ratio_categories['square'][] = $ratio;
+  }
+  else if ($ratio > 1.05 and $ratio < 2)
+  {
+    $ratio_categories['landscape'][] = $ratio;
+  }
+  else if ($ratio >= 2)
+  {
+    $ratio_categories['panorama'][] = $ratio;
+  }
+}
+
+foreach (array_keys($ratio_categories) as $ratio_category)
+{
+  if (count($ratio_categories[$ratio_category]) > 0)
+  {
+    $dimensions['ratio_'.$ratio_category] = array(
+      'min' => $ratio_categories[$ratio_category][0],
+      'max' => $ratio_categories[$ratio_category][count($ratio_categories[$ratio_category]) - 1]
+      );
+  }
+}
+
+$template->assign('dimensions', $dimensions);
 
 /* get image number */
@@ -289,8 +391,5 @@
   'level_options' => get_privacy_level_options(),
   'F_ACTION' => $self_url,
-  'CATEGORIES_NAV' => get_cat_display_name_cache(
-    $category['uppercats'],
-    SMART_ADMIN.'-album&amp;cat_id='
-    ),
+  'CATEGORIES_NAV' => get_cat_display_name_cache($category['uppercats'], SMART_ADMIN.'-album&amp;cat_id='),
 ));
 
Index: /extensions/SmartAlbums/admin/cat_list.php
===================================================================
--- /extensions/SmartAlbums/admin/cat_list.php	(revision 19445)
+++ /extensions/SmartAlbums/admin/cat_list.php	(revision 19446)
@@ -21,6 +21,5 @@
     permalink,
     dir,
-    rank,
-    status
+    smart_update
   FROM '.CATEGORIES_TABLE.' AS cat
   INNER JOIN '.CATEGORY_FILTERS_TABLE.' AS cf
@@ -124,13 +123,16 @@
 // +-----------------------------------------------------------------------+
 
-// get the categories containing images directly 
-$categories_with_images = array();
+$categories_count_images = array();
 if ( count($categories) )
 {
   $query = '
-SELECT DISTINCT category_id 
+SELECT 
+    category_id, 
+    COUNT(image_id) AS total_images
   FROM '.IMAGE_CATEGORY_TABLE.'
-  WHERE category_id IN ('.implode(',', array_keys($categories)).')';
-  $categories_with_images = array_flip( array_from_query($query, 'category_id') );
+  WHERE category_id IN ('.implode(',', array_keys($categories)).')
+  GROUP BY category_id
+;';
+  $categories_count_images = simple_hash_from_query($query, 'category_id', 'total_images');
 }
 
@@ -141,24 +143,14 @@
   $tpl_cat =
     array(
-      'NAME'       => get_cat_display_name_from_id($category['id'], $base_url.'album-'),
-      'ID'         => $category['id'],
-      'RANK'       => $category['rank']*10,
+      'NAME'        => get_cat_display_name_from_id($category['id'], $base_url.'album-'),
+      'ID'          => $category['id'],
+      'IMG_COUNT'   => !empty($categories_count_images[ $category['id'] ]) ? $categories_count_images[ $category['id'] ] : 0,
+      'LAST_UPDATE' => format_date($category['smart_update'], true),
 
-      'U_JUMPTO'   => make_index_url(
-        array(
-          'category' => $category
-          )
-        ),
-
-      'U_EDIT'     => $base_url.'album-'.$category['id'],
-      'U_DELETE'   => $self_url.'&amp;delete='.$category['id'].'&amp;pwg_token='.get_pwg_token(),
-      'U_SMART'    => $self_url.'&amp;smart_generate='.$category['id'],
+      'U_JUMPTO'    => make_index_url(array('category' => $category)),
+      'U_EDIT'      => SMART_ADMIN.'-album&amp;cat_id='.$category['id'],
+      'U_DELETE'    => $self_url.'&amp;delete='.$category['id'].'&amp;pwg_token='.get_pwg_token(),
+      'U_SMART'     => $self_url.'&amp;smart_generate='.$category['id'],
     );
-
-  if ( array_key_exists($category['id'], $categories_with_images) )
-  {
-    $tpl_cat['U_MANAGE_ELEMENTS'] =
-      $base_url.'batch_manager&amp;cat='.$category['id'];
-  }
   
   $template->append('categories', $tpl_cat);
Index: /extensions/SmartAlbums/admin/config.php
===================================================================
--- /extensions/SmartAlbums/admin/config.php	(revision 19445)
+++ /extensions/SmartAlbums/admin/config.php	(revision 19446)
@@ -5,6 +5,18 @@
 if (isset($_POST['submit']))
 {
-  $conf['SmartAlbums']['update_on_upload'] = isset($_POST['update_on_upload']);
-  $conf['SmartAlbums']['smart_is_forbidden'] = isset($_POST['smart_is_forbidden']);
+  if ( $_POST['update_timeout'] == 0 or !preg_match('#^[0-9.]+$#', $_POST['update_timeout']) )
+  {
+    array_push($page['errors'], l10n('Invalid number of days'));
+    $_POST['update_timeout'] = $conf['SmartAlbums']['update_timeout'];
+  }
+    
+  $conf['SmartAlbums'] = array(
+    'show_list_messages' => $conf['SmartAlbums']['show_list_messages'],
+    'last_update' =>        $conf['SmartAlbums']['last_update'],
+    'update_on_upload' =>   isset($_POST['update_on_upload']),
+    'update_on_date' =>     isset($_POST['update_on_date']),
+    'update_timeout' =>     $_POST['update_timeout'],
+    'smart_is_forbidden' => isset($_POST['smart_is_forbidden']),
+    );
   
   conf_update_param('SmartAlbums', serialize($conf['SmartAlbums']));
Index: /extensions/SmartAlbums/admin/template/album.tpl
===================================================================
--- /extensions/SmartAlbums/admin/template/album.tpl	(revision 19445)
+++ /extensions/SmartAlbums/admin/template/album.tpl	(revision 19446)
@@ -1,29 +1,17 @@
 {combine_css path=$SMART_PATH|@cat:"admin/template/style.css"}
-
+{combine_script id='sprintf' load='footer' path=$SMART_PATH|@cat:"admin/template/sprintf.js"}
 {include file='include/datepicker.inc.tpl'}
-{combine_script id='jquery.tokeninput' load='async' require='jquery' path='themes/default/js/plugins/jquery.tokeninput.js'}
-
+{combine_script id='jquery.tokeninput' load='footer' require='jquery' path='themes/default/js/plugins/jquery.tokeninput.js'}
 {combine_css path="themes/default/js/plugins/chosen.css"}
 {combine_script id='jquery.chosen' load='footer' path='themes/default/js/plugins/chosen.jquery.min.js'}
-
-{footer_script require='jquery.tokeninput,jquery.chosen'}
-var lang = new Array();
-var options = new Array();
-
-{foreach from=$options item=details key=mode}
-  lang['{$mode}_filter'] = '{$details.name|escape:javascript}';
-  {capture assign="option_content"}{html_options options=$details.options}{/capture}
-  options['{$mode}'] = "{$option_content|escape:javascript}";
-{/foreach}
-
-lang['Select albums...'] = '{'Select albums...'|@translate}';
-lang['For "Is (not) in", separate each author by a comma'] = '{'For "Is (not) in", separate each author by a comma'|@translate|escape:javascript}';
-lang['remove this filter'] = '{'remove this filter'|@translate|escape:javascript}';
-{capture assign="option_content"}{html_options options=$level_options selected=0}{/capture}
-options['level_options'] = "{$option_content|escape:javascript}";
-{capture assign="option_content"}{html_options options=$all_albums selected=0}{/capture}
-options['album_options'] = "{$option_content|escape:javascript}";
-
-{literal}
+{combine_css path="themes/default/js/ui/theme/jquery.ui.slider.css"}
+{combine_script id='jquery.ui.slider' require='jquery.ui' load='footer' path='themes/default/js/ui/minified/jquery.ui.slider.min.js'}
+
+{footer_script}{literal}
+var count=0;
+var limit_count=0;
+var level_count=0;
+
+// MAIN EVENT HANDLERS
 $('#addFilter').change(function() {
   add_filter($(this).attr('value'));
@@ -32,7 +20,10 @@
   
 $('#removeFilters').click(function() {
-  $('#filterList li').each(function() {
+  $('#filtersList li').each(function() {
     $(this).remove();
   });
+  
+  limit_level=0;
+  level_count=0;
   return false;
 });
@@ -50,70 +41,183 @@
 
 
-function add_filter(type) {
-  // add line
-  $('<li class="filter_'+ type +'" id="filter_'+ i +'"></li>').appendTo('#filterList');
-  
-  //set content
-  content = 
-    ' <span class="filter-title">'+
-      ' <a href="#" class="removeFilter" title="'+ lang['remove this filter'] +'"><span>[x]</span></a>'+
-      ' <input type="hidden" name="filters['+ i +'][type]" value="'+ type +'"/>&nbsp;'+ lang[type +'_filter'] +
-    '</span>'+
-    ' <span class="filter-cond">'+
-      ' <select name="filters['+ i +'][cond]">'+ options[type] +'</select>'+
-    '</span>'+
-    ' <span class="filter-value">';
-  
-  if (type == 'tags') {
-    content+= ' <select name="filters['+ i +'][value]" class="tagSelect"></select>';
-  }
-  else if (type == 'album') {
-    content+= ' <select name="filters['+ i +'][value][]" class="albumSelect" multiple="multiple" data-placeholder="'+ lang['Select albums...'] +'">'+ options['album_options'] +'</select>';
+// ADD FILTER FUNCTIONS
+function add_filter(type, cond, value) {
+  count++;
+  
+  content = $("#filtersRepo #filter_"+type).html().replace(/iiii/g, count);
+  $block = $(content);
+  $("#filtersList").append($block);
+  
+  if (cond) {
+    select_cond($block, type, cond);
+  }
+  
+  if (value) {
+    if (type == "tags") {
+      $block.find(".filter-value .tagSelect").html(value);
+    }
+    else if (type == "album") {
+      select_options($block, value);
+    }
+    else if (type == "level") {
+      select_options($block, value);
+    }
+    else if (type != "dimensions") {
+      $block.find(".filter-value input").val(value);
+    }
+  }
+  
+  init_jquery_handlers($block);
+  
+  if (type == "dimensions") {
+    select_dimensions($block, cond, value);
+  }
+  
+  if (type == 'limit') {
+    limit_count=1;
+    $("#addFilter option[value='limit']").attr('disabled','disabled');
   }
   else if (type == 'level') {
-    content+= ' <select name="filters['+ i +'][value]">'+ options['level_options'] +'</select>';
+    level_count=1;
+    $("#addFilter option[value='level']").attr('disabled','disabled');
+  }
+}
+
+function select_cond($block, type, cond) {
+  $block.find(".filter-cond option").removeAttr('selected');
+  $block.find(".filter-cond option[value='"+cond+"']").attr('selected', 'selected');
+}
+
+function select_dimensions($block, cond, value) {
+  if (!cond) cond = 'width';
+  
+  $block.find(".filter-value span:not(.filter_dimension_info)").hide();
+  $block.find(".filter-value .dimension_"+cond).show();
+  
+  if (value) {
+    values = value.split(',');
   }
   else {
-    content+= ' <input type="text" name="filters['+ i +'][value]" size="30"/>';
-  }
-  
-  if (type == 'author') {
-    content+= ' <i>'+ lang['For "Is (not) in", separate each author by a comma'] +'</i>';
-  }
-    
-  content+= '</span>';
-  
-  $('#filter_'+ i).html(content);
-  
-  // reinit handlers
-  init_jquery_handlers();
-  i++;
-}
-
-function init_jquery_handlers() {  
-  $('.removeFilter').click(function() {
+    values = $block.find(".filter_dimension_"+cond+"_slider").slider("values");
+  }
+  $block.find(".filter_dimension_"+cond+"_slider").slider("values", values);
+}
+
+function select_options($block, value) {  
+  values = value.split(',');
+  for (j in values) {
+    $block.find(".filter-value option[value='"+ values[j] +"']").attr('selected', 'selected');
+  }
+}
+
+
+// DECLARE JQUERY PLUGINS AND VERSATILE HANDLERS
+function init_jquery_handlers($block) {
+  // remove filter
+  $block.find(".removeFilter").click(function() {
+    type = $(this).next("input").val();
+    if (type == 'limit') {
+      limit_count=1;
+      $("#addFilter option[value='limit']").removeAttr('disabled');
+    }
+    else if (type == 'level') {
+      level_count=1;
+      $("#addFilter option[value='level']").removeAttr('disabled');
+    }
+    
     $(this).parents('li').remove();
     return false;
   });
 
-  $('.filter_date input[type="text"]').each(function() {
-    $(this).datepicker({dateFormat:'yy-mm-dd', firstDay:1});
-  });
-  
-  jQuery(".tagSelect").tokenInput(
-  {/literal}
-    [{foreach from=$all_tags item=tag name=tags}{ldelim}"name":"{$tag.name|@escape:'javascript'}","id":"{$tag.id}"{rdelim}{if !$smarty.foreach.tags.last},{/if}{/foreach}],
-    {ldelim}
-      hintText: '{'Type in a search term'|@translate}',
-      noResultsText: '{'No results'|@translate}',
-      searchingText: '{'Searching...'|@translate}',
-      animateDropdown: false,
-      preventDuplicates: true,
-      allowCreation: false
-  {literal}
-    }
-  );
-  
-  jQuery(".albumSelect").chosen();
+  // date filter
+  if ($block.hasClass('filter_date')) {
+    $block.find("input[type='text']").each(function() {
+      $(this).datepicker({dateFormat:'yy-mm-dd', firstDay:1});
+    });
+  }
+
+  // tags filter
+  if ($block.hasClass('filter_tags')) {
+    $block.find(".tagSelect").tokenInput(
+    {/literal}
+      [{foreach from=$all_tags item=tag name=tags}{ldelim}"name":"{$tag.name|@escape:'javascript'}","id":"{$tag.id}"{rdelim}{if !$smarty.foreach.tags.last},{/if}{/foreach}],
+      {ldelim}
+        hintText: '{'Type in a search term'|@translate}',
+        noResultsText: '{'No results'|@translate}',
+        searchingText: '{'Searching...'|@translate}',
+        animateDropdown: false,
+        preventDuplicates: true,
+        allowCreation: false
+    {literal}
+    });
+  }
+  
+  // album filter
+  if ($block.hasClass('filter_album')) {
+    $block.find(".albumSelect").chosen();
+  }
+  
+  // dimension filter
+  if ($block.hasClass('filter_dimensions')) {
+    $block.find(".filter-cond select").change(function() {
+      select_dimensions($block, $(this).attr("value"));
+    });
+    {/literal}
+    
+    $block.find(".filter_dimension_width_slider").slider({ldelim}
+      range: true,
+      min: {$dimensions.bounds.min_width},
+      max: {$dimensions.bounds.max_width},
+      values: [{$dimensions.bounds.min_width}, {$dimensions.bounds.max_width}],
+      slide: function(event, ui) {ldelim}
+        change_dimension_info($block, ui.values, "{'between %d and %d pixels'|@translate}");
+      },
+      change: function(event, ui) {ldelim}
+        change_dimension_info($block, ui.values, "{'between %d and %d pixels'|@translate}");
+      }
+    });
+    
+    $block.find(".filter_dimension_height_slider").slider({ldelim}
+      range: true,
+      min: {$dimensions.bounds.min_height},
+      max: {$dimensions.bounds.max_height},
+      values: [{$dimensions.bounds.min_height}, {$dimensions.bounds.max_height}],
+      slide: function(event, ui) {ldelim}
+        change_dimension_info($block, ui.values, "{'between %d and %d pixels'|@translate}");
+      },
+      change: function(event, ui) {ldelim}
+        change_dimension_info($block, ui.values, "{'between %d and %d pixels'|@translate}");
+      }
+    });
+    
+    $block.find(".filter_dimension_ratio_slider").slider({ldelim}
+      range: true,
+      step: 0.01,
+      min: {$dimensions.bounds.min_ratio},
+      max: {$dimensions.bounds.max_ratio},
+      values: [{$dimensions.bounds.min_ratio}, {$dimensions.bounds.max_ratio}],
+      slide: function(event, ui) {ldelim}
+        change_dimension_info($block, ui.values, "{'between %.2f and %.2f'|@translate}");
+      },
+      change: function(event, ui) {ldelim}
+        change_dimension_info($block, ui.values, "{'between %.2f and %.2f'|@translate}");
+      }
+    });
+    {literal}
+    
+    $block.find("a.dimensions-choice").click(function() {
+      $block.find(".filter_dimension_"+ $(this).data("type") +"_slider").slider("values", 
+        [$(this).data("min"), $(this).data("max")]
+      );
+    });
+  }
+}
+
+
+// GENERAL FUNCTIONS
+function change_dimension_info($block, values, text) {
+  $block.find("input[name$='[value][min]']").val(values[0]);
+  $block.find("input[name$='[value][max]']").val(values[1]);
+  $block.find(".filter_dimension_info").html(sprintf(text, values[0], values[1]));
 }
 
@@ -136,9 +240,9 @@
   }
 }
-  
-init_jquery_handlers();
 {/literal}
+
 {if isset($new_smart)}doBlink('.new_smart', 0, 3);{/if}
 {/footer_script}
+
 
 <div class="titrePage">
@@ -153,68 +257,56 @@
     <legend>{'Filters'|@translate}</legend>
       
-    <ul id="filterList">
-      {counter start=0 assign=i}
-      {foreach from=$filters item=filter}
-        <li class="filter_{$filter.TYPE}" id="filter_{$i}">
-          <span class="filter-title">
-            <a href="#" class="removeFilter" title="{'remove this filter'|@translate}"><span>[x]</span></a>
-            <input type="hidden" name="filters[{$i}][type]" value="{$filter.TYPE}"/>
-            {$filter.CAPTION}
-          </span>
-          
-          <span class="filter-cond">
-            <select name="filters[{$i}][cond]">
-              {html_options options=$options[$filter.TYPE].options selected=$filter.COND}
-            </select>
-          </span>
-          
-          <span class="filter-value">
-          {if $filter.TYPE == 'tags'}
-            <select name="filters[{$i}][value]" class="tagSelect">
-            {foreach from=$filter.VALUE item=tag}
-              <option value="{$tag.id}" class="selected">{$tag.name}</option>
-            {/foreach}
-            </select>
-          {elseif $filter.TYPE == 'album'}
-            <select name="filters[{$i}][value][]" class="albumSelect" multiple="multiple" data-placeholder="{'Select albums...'|@translate}">
-              {html_options options=$all_albums selected=$filter.VALUE}
-            </select>
-          {elseif $filter.TYPE == 'level'}
-            <select name="filters[{$i}][value]">
-              {html_options options=$level_options selected=$filter.VALUE}
-            </select>
-          {else}
-            <input type="text" name="filters[{$i}][value]" value="{$filter.VALUE}" size="30"/>
-          {/if}
-          {if $filter.TYPE == 'author'}
-            <i>{'For "Is (not) in", separate each author by a comma'|@translate}</i>
-          {/if}
-          </span>
-        </li>
-        {counter}
-      {/foreach}
+    <ul id="filtersList">
+    {foreach from=$filters item=filter}{strip}
+      {if $filter.type == 'tags'}
+        {capture assign='value'}{foreach from=$filter.value item=tag}<option value="{$tag.id}" class="selected">{$tag.name}</option>{/foreach}{/capture}
+      {else}
+        {assign var='value' value=$filter.value}
+      {/if}
       
-      {footer_script}var i={$i};{/footer_script}
+      {if $filter.type == 'limit'}
+        {footer_script}
+        limit_count=1;
+        $("#addFilter option[value='limit']").attr('disabled','disabled');
+        {/footer_script}
+      {elseif $filter.type == 'level'}
+        {footer_script}
+        level_count=1;
+        $("#addFilter option[value='level']").attr('disabled','disabled');
+        {/footer_script}
+      {/if}
+      
+      {footer_script}add_filter('{$filter.type}', '{$filter.cond}', '{$value|escape:javascript}');{/footer_script}
+    {/strip}{/foreach}
     </ul>
-
+    
+    <div>
+      <b>{'Mode'|@translate} :</b>
+      <label><input type="radio" name="filters[0][value]" value="and" {if $filter_mode=='and'}checked="checked"{/if}> AND</label>
+      <label><input type="radio" name="filters[0][value]" value="or" {if $filter_mode=='or'}checked="checked"{/if}> OR</label>
+      <input type="hidden" name="filters[0][type]" value="mode">
+      <input type="hidden" name="filters[0][cond]" value="mode">
+    </div>
+    
     <p class="actionButtons">
-        <select id="addFilter">
-          <option value="-1">{'Add a filter'|@translate}</option>
-          <option disabled="disabled">------------------</option>
-          <option value="tags">{'Tags'|@translate}</option>
-          <option value="date">{'Date'|@translate}</option>
-          <option value="name">{'Photo name'|@translate}</option>
-          <option value="album">{'Album'|@translate}</option>
-          <option value="author">{'Author'|@translate}</option>
-          <option value="hit">{'Hits'|@translate}</option>
-          <option value="rating_score">{'Rating score'|@translate}</option>
-          <option value="level">{'Privacy level'|@translate}</option>
-          <option value="limit">{'Max. number of photos'|@translate}</option>
-        </select>
-        <a id="removeFilters">{'Remove all filters'|@translate}</a>
-        {if isset($new_smart)}<span class="new_smart">{'Add filters here'|@translate}</span>{/if}
+      <select id="addFilter">
+        <option value="-1">{'Add a filter'|@translate}</option>
+        <option disabled="disabled">------------------</option>
+        <option value="tags">{'Tags'|@translate}</option>
+        <option value="date">{'Date'|@translate}</option>
+        <option value="name">{'Photo name'|@translate}</option>
+        <option value="album">{'Album'|@translate}</option>
+        <option value="dimensions">{'Dimensions'|@translate}</option>
+        <option value="author">{'Author'|@translate}</option>
+        <option value="hit">{'Hits'|@translate}</option>
+        <option value="rating_score">{'Rating score'|@translate}</option>
+        <option value="level">{'Privacy level'|@translate}</option>
+        <option value="limit">{'Max. number of photos'|@translate}</option>
+      </select>
+      <a id="removeFilters">{'Remove all filters'|@translate}</a>
+      {if isset($new_smart)}<span class="new_smart">{'Add filters here'|@translate}</span>{/if}
     </p>
   </fieldset>
-
+  
   <p class="actionButtons" id="applyFilterBlock">
     <input class="submit" type="submit" value="{'Submit'|@translate}" name="submitFilters"/>
@@ -225,2 +317,241 @@
 </form>
 </div>
+
+<div id="filtersRepo" style="display:none;">
+  <!-- tags -->
+  <div id="filter_tags">
+  <li id="filter_iiii" class="filter_tags">
+    <span class="filter-title">
+      <a href="#" class="removeFilter" title="{'remove this filter'|@translate}"><span>[x]</span></a>
+      <input type="hidden" name="filters[iiii][type]" value="tags"/>
+      {$options.tags.name}
+    </span>
+    
+    <span class="filter-cond">
+      <select name="filters[iiii][cond]">
+        {html_options options=$options.tags.options}
+      </select>
+    </span>
+    
+    <span class="filter-value">
+      <select name="filters[iiii][value]" class="tagSelect">
+      </select>
+    </span>
+  </li>
+  </div>
+  
+  <!-- date -->
+  <div id="filter_date">
+  <li id="filter_iiii" class="filter_date">
+    <span class="filter-title">
+      <a href="#" class="removeFilter" title="{'remove this filter'|@translate}"><span>[x]</span></a>
+      <input type="hidden" name="filters[iiii][type]" value="date"/>
+      {$options.date.name}
+    </span>
+    
+    <span class="filter-cond">
+      <select name="filters[iiii][cond]">
+        {html_options options=$options.date.options}
+      </select>
+    </span>
+    
+    <span class="filter-value">
+      <input type="text" name="filters[iiii][value]" size="30"/>
+    </span>
+  </li>
+  </div>
+  
+  <!-- name -->
+  <div id="filter_name">
+  <li id="filter_iiii" class="filter_name">
+    <span class="filter-title">
+      <a href="#" class="removeFilter" title="{'remove this filter'|@translate}"><span>[x]</span></a>
+      <input type="hidden" name="filters[iiii][type]" value="name"/>
+      {$options.name.name}
+    </span>
+    
+    <span class="filter-cond">
+      <select name="filters[iiii][cond]">
+        {html_options options=$options.name.options}
+      </select>
+    </span>
+    
+    <span class="filter-value">
+      <input type="text" name="filters[iiii][value]" size="30"/>
+    </span>
+  </li>
+  </div>
+  
+  <!-- album -->
+  <div id="filter_album">
+  <li id="filter_iiii" class="filter_album">
+    <span class="filter-title">
+      <a href="#" class="removeFilter" title="{'remove this filter'|@translate}"><span>[x]</span></a>
+      <input type="hidden" name="filters[iiii][type]" value="album"/>
+      {$options.album.name}
+    </span>
+    
+    <span class="filter-cond">
+      <select name="filters[iiii][cond]">
+        {html_options options=$options.album.options}
+      </select>
+    </span>
+    
+    <span class="filter-value">
+      <select name="filters[iiii][value][]" class="albumSelect" multiple="multiple" data-placeholder="{'Select albums...'|@translate}">
+        {html_options options=$all_albums}
+      </select>
+    </span>
+  </li>
+  </div>
+  
+  <!-- dimensions -->
+  <div id="filter_dimensions">
+  <li id="filter_iiii" class="filter_dimensions">
+    <span class="filter-title">
+      <a href="#" class="removeFilter" title="{'remove this filter'|@translate}"><span>[x]</span></a>
+      <input type="hidden" name="filters[iiii][type]" value="dimensions"/>
+      {$options.dimensions.name}
+    </span>
+
+    <span class="filter-cond">
+      <select name="filters[iiii][cond]">
+        {html_options options=$options.dimensions.options}
+      </select>
+    </span>
+
+    <span class="filter-value">
+      <span class="filter_dimension_info"></span>
+      <span class="dimension_width">
+        | <a class="dimensions-choice" data-type="width" data-min="{$dimensions.bounds.min_width}" data-max="{$dimensions.bounds.max_width}">{'Reset'|@translate}</a>
+        <div class="filter_dimension_width_slider"></div>
+      </span>
+
+      <span class="filter-value dimension_height">
+        | <a class="dimensions-choice" data-type="height" data-min="{$dimensions.bounds.min_height}" data-max="{$dimensions.bounds.max_height}">{'Reset'|@translate}</a>
+        <div class="filter_dimension_height_slider"></div>
+      </span>
+
+      <span class="filter-value dimension_ratio">
+{if isset($dimensions.ratio_portrait)}
+        | <a class="dimensions-choice" data-type="ratio" data-min="{$dimensions.ratio_portrait.min}" data-max="{$dimensions.ratio_portrait.max}">{'Portrait'|@translate}</a>
+{/if}
+{if isset($dimensions.ratio_square)}
+        | <a class="dimensions-choice" data-type="ratio" data-min="{$dimensions.ratio_square.min}" data-max="{$dimensions.ratio_square.max}">{'square'|@translate}</a>
+{/if}
+{if isset($dimensions.ratio_landscape)}
+        | <a class="dimensions-choice" data-type="ratio" data-min="{$dimensions.ratio_landscape.min}" data-max="{$dimensions.ratio_landscape.max}">{'Landscape'|@translate}</a>
+{/if}
+{if isset($dimensions.ratio_panorama)}
+        | <a class="dimensions-choice" data-type="ratio" data-min="{$dimensions.ratio_panorama.min}" data-max="{$dimensions.ratio_panorama.max}">{'Panorama'|@translate}</a>
+{/if}
+        | <a class="dimensions-choice" data-type="ratio" data-min="{$dimensions.bounds.min_ratio}" data-max="{$dimensions.bounds.max_ratio}">{'Reset'|@translate}</a>
+        <div class="filter_dimension_ratio_slider"></div>
+      </span>
+    </span>
+
+    <input type="hidden" name="filters[iiii][value][min]" value="">
+    <input type="hidden" name="filters[iiii][value][max]" value="">
+  </li>
+  </div>
+  
+  <!-- author -->
+  <div id="filter_author">
+  <li id="filter_iiii" class="filter_author">
+    <span class="filter-title">
+      <a href="#" class="removeFilter" title="{'remove this filter'|@translate}"><span>[x]</span></a>
+      <input type="hidden" name="filters[iiii][type]" value="author"/>
+      {$options.author.name}
+    </span>
+    
+    <span class="filter-cond">
+      <select name="filters[iiii][cond]">
+        {html_options options=$options.author.options}
+      </select>
+    </span>
+    
+    <span class="filter-value">
+      <input type="text" name="filters[iiii][value]" size="30"/>
+      <i>{'For "Is (not) in", separate each author by a comma'|@translate}</i>
+    </span>
+  </li>
+  </div>
+  
+  <!-- hit -->
+  <div id="filter_hit">
+  <li id="filter_iiii" class="filter_hit">
+    <span class="filter-title">
+      <a href="#" class="removeFilter" title="{'remove this filter'|@translate}"><span>[x]</span></a>
+      <input type="hidden" name="filters[iiii][type]" value="hit"/>
+      {$options.hit.name}
+    </span>
+    
+    <span class="filter-cond">
+      <select name="filters[iiii][cond]">
+        {html_options options=$options.hit.options}
+      </select>
+    </span>
+    
+    <span class="filter-value">
+      <input type="text" name="filters[iiii][value]" size="5"/>
+    </span>
+  </li>
+  </div>
+  
+  <!-- rating_score -->
+  <div id="filter_rating_score">
+  <li id="filter_iiii" class="filter_rating_score">
+    <span class="filter-title">
+      <a href="#" class="removeFilter" title="{'remove this filter'|@translate}"><span>[x]</span></a>
+      <input type="hidden" name="filters[iiii][type]" value="rating_score"/>
+      {$options.rating_score.name}
+    </span>
+    
+    <span class="filter-cond">
+      <select name="filters[iiii][cond]">
+        {html_options options=$options.rating_score.options}
+      </select>
+    </span>
+    
+    <span class="filter-value">
+      <input type="text" name="filters[iiii][value]" size="5"/>
+    </span>
+  </li>
+  </div>
+  
+  <!-- level -->
+  <div id="filter_level">
+  <li id="filter_iiii" class="filter_level">
+    <span class="filter-title">
+      <a href="#" class="removeFilter" title="{'remove this filter'|@translate}"><span>[x]</span></a>
+      <input type="hidden" name="filters[iiii][type]" value="level"/>
+      {$options.level.name}
+    </span>
+    
+    <input type="hidden" name="filters[iiii][cond]" value="level"/>
+    
+    <span class="filter-value">
+      <select name="filters[iiii][value]">
+        {html_options options=$level_options}
+      </select>
+    </span>
+  </li>
+  </div>
+  
+  <!-- limit -->
+  <div id="filter_limit">
+  <li id="filter_iiii" class="filter_limit">
+    <span class="filter-title">
+      <a href="#" class="removeFilter" title="{'remove this filter'|@translate}"><span>[x]</span></a>
+      <input type="hidden" name="filters[iiii][type]" value="limit"/>
+      {$options.limit.name}
+    </span>
+    
+    <input type="hidden" name="filters[iiii][cond]" value="limit"/>
+    
+    <span class="filter-value">
+      <input type="text" name="filters[iiii][value]" size="5"/>
+    </span>
+  </li>
+  </div>
+</div>
Index: /extensions/SmartAlbums/admin/template/cat_list.tpl
===================================================================
--- /extensions/SmartAlbums/admin/template/cat_list.tpl	(revision 19445)
+++ /extensions/SmartAlbums/admin/template/cat_list.tpl	(revision 19446)
@@ -60,12 +60,10 @@
         <strong><a href="{$category.U_CHILDREN}" title="{'manage sub-albums'|@translate}">{$category.NAME}</a></strong>
         <img src="{$SMART_PATH}admin/template/lightning.png">
+        {'%d photos'|@translate|sprintf:$category.IMG_COUNT}
       </p>
 
       <p class="albumActions">
         <a href="{$category.U_EDIT}">{'Edit'|@translate}</a>
-        | <a href="{$category.U_SMART}">{'Regenerate photos list of this SmartAlbum'|@translate}</a>
-        {if isset($category.U_MANAGE_ELEMENTS) }
-        | <a href="{$category.U_MANAGE_ELEMENTS}">{'manage album photos'|@translate}</a>
-        {/if}
+        | <a href="{$category.U_SMART}" title="{$category.LAST_UPDATE}">{'Regenerate photos list of this SmartAlbum'|@translate}</a>
         {if isset($category.U_DELETE) }
         | <a href="{$category.U_DELETE}" onclick="return confirm('{'Are you sure?'|@translate|@escape:javascript}');">{'delete album'|@translate}</a>
Index: /extensions/SmartAlbums/admin/template/config.tpl
===================================================================
--- /extensions/SmartAlbums/admin/template/config.tpl	(revision 19445)
+++ /extensions/SmartAlbums/admin/template/config.tpl	(revision 19446)
@@ -23,11 +23,11 @@
 
 <div class="titrePage">
-	<h2>SmartAlbums</h2>
+  <h2>SmartAlbums</h2>
 </div>
 
 <form method="post" action="" class="properties"> 
-	<fieldset id="commentsConf">
-		<ul>			
-			<li>
+  <fieldset id="commentsConf">
+    <ul>
+      <li>
         <label>
           <input type="checkbox" name="update_on_upload" value="true" {if $update_on_upload}checked="checked"{/if}/>
@@ -35,5 +35,12 @@
           <a class="showInfo" title="{'can cause slowdowns'|@translate}">i</a>
         </label>
-			</li>
+      </li>
+      <li>
+        <label>
+          <input type="checkbox" name="update_on_date" value="true" {if $update_on_date}checked="checked"{/if}/>
+          {assign var=input value='<input type="text" name="update_timeout" size="2" value="'|cat:$update_timeout|cat:'"/>'}
+          <b>{'Update albums every %s days'|@translate|sprintf:$input}</b>
+        </label>
+      </li>
       <li>
         <label>
@@ -42,8 +49,8 @@
           <a class="showInfo" title="{'SmartAlbums are considered private for everyone, and a user can see it\'s content only if available in another album he has access to.'|@translate}">i</a>
         </label>
-			</li>
-		</ul>
-	</fieldset>
-		
-	<p class="formButtons"><input class="submit" type="submit" value="{'Submit'|@translate}" name="submit" /></p>
+      </li>
+    </ul>
+  </fieldset>
+    
+  <p class="formButtons"><input class="submit" type="submit" value="{'Submit'|@translate}" name="submit" /></p>
 </form>
Index: /extensions/SmartAlbums/admin/template/sprintf.js
===================================================================
--- /extensions/SmartAlbums/admin/template/sprintf.js	(revision 19446)
+++ /extensions/SmartAlbums/admin/template/sprintf.js	(revision 19446)
@@ -0,0 +1,46 @@
+function str_repeat(i, m) {
+        for (var o = []; m > 0; o[--m] = i);
+        return o.join('');
+}
+
+function sprintf() {
+        var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = '';
+        while (f) {
+                if (m = /^[^\x25]+/.exec(f)) {
+                        o.push(m[0]);
+                }
+                else if (m = /^\x25{2}/.exec(f)) {
+                        o.push('%');
+                }
+                else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
+                        if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) {
+                                throw('Too few arguments.');
+                        }
+                        if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) {
+                                throw('Expecting number but found ' + typeof(a));
+                        }
+                        switch (m[7]) {
+                                case 'b': a = a.toString(2); break;
+                                case 'c': a = String.fromCharCode(a); break;
+                                case 'd': a = parseInt(a); break;
+                                case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
+                                case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
+                                case 'o': a = a.toString(8); break;
+                                case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
+                                case 'u': a = Math.abs(a); break;
+                                case 'x': a = a.toString(16); break;
+                                case 'X': a = a.toString(16).toUpperCase(); break;
+                        }
+                        a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a);
+                        c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
+                        x = m[5] - String(a).length - s.length;
+                        p = m[5] ? str_repeat(c, x) : '';
+                        o.push(s + (m[4] ? a + p : p + a));
+                }
+                else {
+                        throw('Huh ?!');
+                }
+                f = f.substring(m[0].length);
+        }
+        return o.join('');
+}
Index: /extensions/SmartAlbums/admin/template/style.css
===================================================================
--- /extensions/SmartAlbums/admin/template/style.css	(revision 19445)
+++ /extensions/SmartAlbums/admin/template/style.css	(revision 19446)
@@ -3,10 +3,15 @@
   vertical-align:bottom;
 }
-#batchManagerGlobal #filterList li.token-input-token {
+#batchManagerGlobal #filtersList li.token-input-token {
   margin-bottom:0px !important;
   padding-top:0;
 }
-#batchManagerGlobal #filterList li.token-input-input-token {
+#batchManagerGlobal #filtersList li.token-input-input-token {
   margin-bottom:-3px !important; 
+}
+
+#batchManagerGlobal .filter_dimensions .ui-slider-horizontal {
+    margin: 5px 0 10px;
+    width: 500px;
 }
 
@@ -19,12 +24,13 @@
 }
 
-#filterList li {
+#filtersList li {
   padding:2px;
   border-bottom:1px dotted rgba(100, 100, 100, 0.5);
+  display:block;
 }
-#filterList li span {
+#filtersList li>span {
   display:inline-block;
 }
-#filterList li span * {
+#filtersList li span * {
   vertical-align:middle;
 }
@@ -39,8 +45,4 @@
 .filter-cond {
   width:220px;
-}
-
-.filter_limit .filter-cond, .filter_level .filter-cond {
-  display:none !important;
 }
 
Index: /extensions/SmartAlbums/include/functions.inc.php
===================================================================
--- /extensions/SmartAlbums/include/functions.inc.php	(revision 19445)
+++ /extensions/SmartAlbums/include/functions.inc.php	(revision 19446)
@@ -29,8 +29,9 @@
         );
     }
-    mass_inserts_ignore(
+    mass_inserts(
       IMAGE_CATEGORY_TABLE, 
       array_keys($datas[0]), 
-      $datas
+      $datas,
+      array('ignore'=>true)
       );
   }
@@ -49,4 +50,11 @@
     set_random_representant(array($cat_id));
   }
+  
+  $query = '
+UPDATE '.CATEGORIES_TABLE.'
+  SET smart_update = NOW()
+  WHERE id = '.$cat_id.'
+;';
+  pwg_query($query);
   
   return $images;
@@ -64,5 +72,5 @@
   if ( defined('SMART_NOT_UPDATE') OR !$conf['SmartAlbums']['update_on_upload'] ) return;
   
-  /* get categories with smart filters */
+  // get categories with smart filters
   $query = '
 SELECT DISTINCT id
@@ -72,5 +80,5 @@
 ;';
   
-  /* regenerate photo list */
+  // regenerate photo list
   $smart_cats = array_from_query($query, 'id');
   array_map('smart_make_associations', $smart_cats);
@@ -115,5 +123,7 @@
     return array();
   }
-    
+  
+  $mode = 'and';
+  
   /* build constrains */
   ## generate 'join', 'where' arrays and 'limit' string to create the SQL query
@@ -249,4 +259,7 @@
             $where[] = 'name NOT LIKE "%'.$filter['value'].'"';
             break;
+          case 'regex':
+            $where[] = 'name REGEXP "'.$filter['value'].'"';
+            break;
         }
         
@@ -327,4 +340,23 @@
       }
       
+      // dimensions
+      case 'dimensions':
+      {
+        $filter['value'] = explode(',', $filter['value']);
+        
+        switch ($filter['cond'])
+        {
+          case 'width':
+            $where[] = 'width >= '.$filter['value'][0].' AND width <= '.$filter['value'][1];
+            break;
+          case 'height':
+            $where[] = 'height >= '.$filter['value'][0].' AND height <= '.$filter['value'][1];
+            break;
+          case 'ratio':
+            $where[] = 'width/height >= '.$filter['value'][0].' AND width/height < '.($filter['value'][1]+0.01);
+            break;
+        }
+      }
+      
       // author
       case 'author':
@@ -348,4 +380,7 @@
             $where[] = 'author NOT IN('.$filter['value'].')';
             break;
+          case 'regex':
+            $where[] = 'author REGEXP "'.$filter['value'].'"';
+            break;
         }
         
@@ -396,4 +431,11 @@
       {
         $limit = '0, '.$filter['value'];
+        break;
+      }
+      
+      // mode
+      case 'mode':
+      {
+        $mode = $filter['value'];
         break;
       }
@@ -415,5 +457,5 @@
       $MainQuery.= '
   WHERE
-    '.implode("\n    AND ", $where);
+    '.implode("\n    ".$mode." ", $where);
     }
 
@@ -424,5 +466,10 @@
 ;';
 
-  // file_put_contents(SMART_PATH.'query.sql', $MainQuery);
+  if (defined('SMART_DEBUG'))
+  {
+    file_put_contents(SMART_PATH.'dump_filters.txt', print_r($filters, true));
+    file_put_contents(SMART_PATH.'dump_query.sql', $MainQuery);
+  }
+  
   return array_from_query($MainQuery, 'id');
 }
@@ -442,112 +489,161 @@
   if (!isset($level_is_set)) $level_is_set = false;
   
-  # tags
-  if ($filter['type'] == 'tags')
-  {
-    if ($filter['value'] == null)
+  switch ($filter['type'])
+  {
+    # tags
+    case 'tags':
+    {
+      if ($filter['value'] == null)
+      {
+        $error = true;
+        array_push($page['errors'], l10n('No tag selected'));
+      }
+      else
+      {
+        $filter['value'] = implode(',', get_tag_ids($filter['value']));
+      }
+      break;
+    }
+    # date
+    case 'date':
+    {
+      if (!preg_match('#([0-9]{4})-([0-9]{2})-([0-9]{2})#', $filter['value']))
+      {
+        $error = true;
+        array_push($page['errors'], l10n('Date string is malformed'));
+      }
+      break;
+    }
+    # name
+    case 'name':
+    {
+      if (empty($filter['value']))
+      {
+        $error = true;
+        array_push($page['errors'], l10n('Name is empty'));
+      }
+      else if ( $filter['cond']=='regex' and @preg_match('/'.$filter['value'].'/', null)===false )
+      {
+        $error = true;
+        array_push($page['errors'], l10n('Regex is malformed'));
+      }
+      break;
+    }
+    # album
+    case 'album':
+    {
+      if (@$filter['value'] == null)
+      {
+        $error = true;
+        array_push($page['errors'], l10n('No album selected'));
+      }
+      else
+      {
+        $filter['value'] = implode(',', $filter['value']);
+      }
+      break;
+    }
+    # dimensions
+    case 'dimensions':
+    {
+      if ( empty($filter['value']['min']) or empty($filter['value']['max']) )
+      {
+        $error = true;
+      }
+      else
+      {
+        $filter['value'] = $filter['value']['min'].','.$filter['value']['max'];
+      }
+      break;
+    }
+    # author
+    case 'author':
+    {
+      if (empty($filter['value']))
+      {
+        $error = true;
+        array_push($page['errors'], l10n('Author is empty'));
+      }
+      else if ( $filter['cond']=='regex' and @preg_match('/'.$filter['value'].'/', null)===false )
+      {
+        $error = true;
+        array_push($page['errors'], l10n('Regex is malformed'));
+      }
+      else
+      {
+        $filter['value'] = preg_replace('#([ ]?),([ ]?)#', ',', $filter['value']);
+      }
+      break;
+    }
+    # hit
+    case 'hit':
+    {
+      if (!preg_match('#([0-9]{1,})#', $filter['value']))
+      {
+        $error = true;
+        array_push($page['errors'], l10n('Hits must be an integer'));
+      }
+      break;
+    }
+    # rating_score
+    case 'rating_score':
+    {
+      if (!preg_match('#([0-9]{1,})#', $filter['value']))
+      {
+        $error = true;
+        array_push($page['errors'], l10n('Rating score must be an integer'));
+      }
+      break;
+    }
+    # level
+    case 'level':
+    {
+      if ($level_is_set == true) // only one level is allowed, first is saved
+      {
+        $error = true;
+        array_push($page['errors'], l10n('You can\'t use more than one level filter'));
+      }
+      else
+      {
+        $filter['cond'] = 'level';
+        $level_is_set = true;
+      }
+      break;
+    }
+    # limit
+    case 'limit':
+    {
+      if ($limit_is_set == true) // only one limit is allowed, first is saved
+      {
+        $error = true;
+        array_push($page['errors'], l10n('You can\'t use more than one limit filter'));
+      }
+      else if (!preg_match('#([0-9]{1,})#', $filter['value']))
+      {
+        $error = true;
+        array_push($page['errors'], l10n('Limit must be an integer'));
+      }
+      else 
+      {
+        $filter['cond'] = 'level';
+        $limit_is_set = true;
+      }
+      break;
+    }
+    # mode
+    case 'mode':
+    {
+      $filter['cond'] = 'mode';
+      break;
+    }
+    
+    default:
     {
       $error = true;
-      array_push($page['errors'], l10n('No tag selected'));
-    }
-    else
-    {
-      $filter['value'] = implode(',', get_tag_ids($filter['value']));
-    }
-  }
-  # date
-  else if ($filter['type'] == 'date')
-  {
-    if (!preg_match('#([0-9]{4})-([0-9]{2})-([0-9]{2})#', $filter['value']))
-    {
-      $error = true;
-      array_push($page['errors'], l10n('Date string is malformed'));
-    }
-  }
-  # name
-  else if ($filter['type'] == 'name')
-  {
-    if (empty($filter['value']))
-    {
-      $error = true;
-      array_push($page['errors'], l10n('Name is empty'));
-    }
-  }
-  # album
-  else if ($filter['type'] == 'album')
-  {
-    if (@$filter['value'] == null)
-    {
-      $error = true;
-      array_push($page['errors'], l10n('No album selected'));
-    }
-    else
-    {
-      $filter['value'] = implode(',', $filter['value']);
-    }
-  }
-  # author
-  else if ($filter['type'] == 'author')
-  {
-    if (empty($filter['value']))
-    {
-      $error = true;
-      array_push($page['errors'], l10n('Author is empty'));
-    }
-    else
-    {
-      $filter['value'] = preg_replace('#([ ]?),([ ]?)#', ',', $filter['value']);
-    }
-  }
-  # hit
-  else if ($filter['type'] == 'hit')
-  {
-    if (!preg_match('#([0-9]{1,})#', $filter['value']))
-    {
-      $error = true;
-      array_push($page['errors'], l10n('Hits must be an integer'));
-    }
-  }
-  # rating_score
-  else if ($filter['type'] == 'rating_score')
-  {
-    if (!preg_match('#([0-9]{1,})#', $filter['value']))
-    {
-      $error = true;
-      array_push($page['errors'], l10n('Rating score must be an integer'));
-    }
-  }
-  # level
-  else if ($filter['type'] == 'level')
-  {
-    if ($level_is_set == true) // only one level is allowed, first is saved
-    {
-      $error = true;
-      array_push($page['errors'], l10n('You can\'t use more than one level filter'));
-    }
-    else
-    {
-      $level_is_set = true;
-    }
-  }
-  # limit
-  else if ($filter['type'] == 'limit')
-  {
-    if (!preg_match('#([0-9]{1,})#', $filter['value']))
-    {
-      $error = true;
-      array_push($page['errors'], l10n('Limit must be an integer'));
-    }
-    else if ($limit_is_set == true) // only one limit is allowed, first is saved
-    {
-      $error = true;
-      array_push($page['errors'], l10n('You can\'t use more than one limit filter'));
-    }
-    else
-    {
-      $limit_is_set = true;
-    }
-  }
-  
-  # out
+      break;
+    }
+  }
+  
+  
   if ($error == false)
   {
@@ -560,67 +656,4 @@
 }
 
-
-/**
- * inserts multiple lines in a table, ignore duplicate entries
- * @param string table_name
- * @param array dbfields
- * @param array inserts
- * @return void
- */
-function mass_inserts_ignore($table_name, $dbfields, $datas)
-{
-  if (count($datas) != 0)
-  {
-    $first = true;
-
-    $query = 'SHOW VARIABLES LIKE \'max_allowed_packet\'';
-    list(, $packet_size) = pwg_db_fetch_row(pwg_query($query));
-    $packet_size = $packet_size - 2000; // The last list of values MUST not exceed 2000 character*/
-    $query = '';
-
-    foreach ($datas as $insert)
-    {
-      if (strlen($query) >= $packet_size)
-      {
-        pwg_query($query);
-        $first = true;
-      }
-
-      if ($first)
-      {
-        $query = '
-INSERT IGNORE INTO '.$table_name.'
-  ('.implode(',', $dbfields).')
-  VALUES';
-        $first = false;
-      }
-      else
-      {
-        $query .= '
-  , ';
-      }
-
-      $query .= '(';
-      foreach ($dbfields as $field_id => $dbfield)
-      {
-        if ($field_id > 0)
-        {
-          $query .= ',';
-        }
-
-        if (!isset($insert[$dbfield]) or $insert[$dbfield] === '')
-        {
-          $query .= 'NULL';
-        }
-        else
-        {
-          $query .= "'".$insert[$dbfield]."'";
-        }
-      }
-      $query .= ')';
-    }
-    pwg_query($query);
-  }
-}
 
 /**
@@ -636,3 +669,30 @@
 }
 
+/**
+ * update images list periodically
+ */
+function smart_periodic_update()
+{
+  global $conf;
+  
+  // we only search for old albums every hour, nevermind which user is connected
+  if ($conf['SmartAlbums']['last_update'] > time() - 3600) return;
+  
+  $conf['SmartAlbums']['last_update'] = time();
+  conf_update_param('SmartAlbums', serialize($conf['SmartAlbums']));
+  
+  // get categories with smart filters
+  $query = '
+SELECT DISTINCT id
+  FROM '.CATEGORIES_TABLE.' AS c
+    INNER JOIN '.CATEGORY_FILTERS_TABLE.' AS cf
+    ON c.id = cf.category_id
+  WHERE smart_update < DATE_SUB(NOW(), INTERVAL '.$conf['SmartAlbums']['update_timeout'].' DAY)
+;';
+  
+  // regenerate photo list
+  $smart_cats = array_from_query($query, 'id');
+  array_map('smart_make_associations', $smart_cats);
+}
+
 ?>
Index: /extensions/SmartAlbums/include/install.inc.php
===================================================================
--- /extensions/SmartAlbums/include/install.inc.php	(revision 19445)
+++ /extensions/SmartAlbums/include/install.inc.php	(revision 19446)
@@ -11,6 +11,9 @@
     $smart_default_config = serialize(array(
       'update_on_upload' => false,
+      'update_on_date' => true,
+      'update_timeout' => 3,
       'show_list_messages' => true,
       'smart_is_forbidden' => true,
+      'last_update' => 0,
       ));
     
@@ -25,7 +28,14 @@
     {
       $new_conf['smart_is_forbidden'] = true;
-      conf_update_param('SmartAlbums', serialize($new_conf));
-      $conf['SmartAlbums'] = serialize($new_conf);
     }
+    // new params in 2.1.0
+    if (!isset($new_conf['update_on_date']))
+    {
+      $new_conf['update_on_date'] = true;
+      $new_conf['update_timeout'] = 3;
+      $new_conf['last_update'] = 0;
+    }
+    conf_update_param('SmartAlbums', serialize($new_conf));
+    $conf['SmartAlbums'] = serialize($new_conf);
   }
   
@@ -45,4 +55,11 @@
   {      
     pwg_query('ALTER TABLE `' . IMAGE_CATEGORY_TABLE . '` ADD `smart` ENUM(\'true\', \'false\') NOT NULL DEFAULT \'false\';');
+  }
+  
+  // new column on category table
+  $result = pwg_query('SHOW COLUMNS FROM `' . CATEGORIES_TABLE . '` LIKE "smart_update";');
+  if (!pwg_db_num_rows($result))
+  {      
+    pwg_query('ALTER TABLE `' . CATEGORIES_TABLE . '` ADD `smart_update` DATETIME NOT NULL;');
   }
   
@@ -68,5 +85,5 @@
     foreach ($name_changes as $old => $new)
     {
-      pwg_query('UPDATE `' . $prefixeTable . 'category_filters` SET cond = "'.$new.'" WHERE cond = "'.$old.'";');
+      pwg_query('UPDATE `' . $prefixeTable . 'category_filters` SET cond = "'.$new.'" WHERE type = "date" AND cond = "'.$old.'";');
     }
   }
Index: /extensions/SmartAlbums/language/en_UK/plugin.lang.php
===================================================================
--- /extensions/SmartAlbums/language/en_UK/plugin.lang.php	(revision 19445)
+++ /extensions/SmartAlbums/language/en_UK/plugin.lang.php	(revision 19446)
@@ -23,4 +23,5 @@
 $lang['Doesn\'t begin with'] = 'Doesn\'t begin with';
 $lang['Doesn\'t end with'] = 'Doesn\'t end with';
+$lang['Regular expression'] = 'Regular expression';
 $lang['Is'] = 'Is';
 $lang['Is in'] = 'Is in';
@@ -44,4 +45,5 @@
 $lang['Limit must be an integer'] = 'Limit must be an integer';
 $lang['Rating score must be an integer'] = 'Rating score must be an integer';
+$lang['Regex is malformed'] = 'Regex is malformed';
 $lang['You can\'t use more than one limit filter'] = 'You can\'t use more than one limit filter';
 $lang['You can\'t use more than one level filter'] = 'You can\'t use more than one level filter';
@@ -60,4 +62,5 @@
 $lang['Exclude SmartAlbums from permissions management'] = 'Exclude SmartAlbums from permissions management';
 $lang['SmartAlbums are considered private for everyone, and a user can see it\'s content only if available in another album he has access to.'] = 'SmartAlbums are considered private for everyone, and a user can see it\'s content only if available in another album he has access to.';
+$lang['Update albums every %s days'] = 'Update albums every %s days';
 
 ?>
Index: /extensions/SmartAlbums/language/fr_FR/plugin.lang.php
===================================================================
--- /extensions/SmartAlbums/language/fr_FR/plugin.lang.php	(revision 19445)
+++ /extensions/SmartAlbums/language/fr_FR/plugin.lang.php	(revision 19446)
@@ -23,4 +23,5 @@
 $lang['Doesn\'t begin with'] = 'Ne commence pas par';
 $lang['Doesn\'t end with'] = 'Ne fini pas par';
+$lang['Regular expression'] = 'Expression régulière';
 $lang['Is'] = 'Est';
 $lang['Is in'] = 'Est dans';
@@ -44,4 +45,5 @@
 $lang['Limit must be an integer'] = 'La nombre d\'éléments doit être un entier';
 $lang['Rating score must be an integer'] = 'Le score doit être un entier';
+$lang['Regex is malformed'] = 'Regex malformée';
 $lang['You can\'t use more than one limit filter'] = 'Vous ne pouvez choisir qu\'une seule limite de nombre d\'éléments';
 $lang['You can\'t use more than one level filter'] = 'Vous ne pouvez choisir qu\'un seul niveau de confidentialité';
@@ -60,4 +62,5 @@
 $lang['Exclude SmartAlbums from permissions management'] = 'Exclure les SmartAlbums de la gestion des droits d\'accès';
 $lang['SmartAlbums are considered private for everyone, and a user can see it\'s content only if available in another album he has access to.'] = 'Les SmartAlbums sont considérés comme privés pour tout le monde, et un utilisateur ne peut voir leur contenu que si disponible dans un autre album auquel il a accès.';
+$lang['Update albums every %s days'] = 'Mettre à jour les albums tous les %s jours';
 
 ?>
Index: /extensions/SmartAlbums/main.inc.php
===================================================================
--- /extensions/SmartAlbums/main.inc.php	(revision 19445)
+++ /extensions/SmartAlbums/main.inc.php	(revision 19446)
@@ -12,12 +12,14 @@
 global $prefixeTable;
 
-define('SMART_PATH',    PHPWG_PLUGINS_PATH . 'SmartAlbums/');
+define('SMART_PATH',     PHPWG_PLUGINS_PATH . 'SmartAlbums/');
 define('CATEGORY_FILTERS_TABLE', $prefixeTable . 'category_filters');
-define('SMART_ADMIN',   get_root_url() . 'admin.php?page=plugin-SmartAlbums');
-define('SMART_VERSION', '2.0.4');
+define('SMART_ADMIN',    get_root_url() . 'admin.php?page=plugin-SmartAlbums');
+define('SMART_VERSION',  'auto');
+//define('SMART_DEBUG',    true);
 
 
+add_event_handler('init', 'smart_init');
+add_event_handler('init', 'smart_periodic_update');
 add_event_handler('invalidate_user_cache', 'smart_make_all_associations');
-add_event_handler('init', 'smart_init');
 
 if (defined('IN_ADMIN'))
@@ -41,4 +43,5 @@
   
   if (
+    SMART_VERSION == 'auto' or
     $pwg_loaded_plugins['SmartAlbums']['version'] == 'auto' or
     version_compare($pwg_loaded_plugins['SmartAlbums']['version'], SMART_VERSION, '<')
@@ -48,5 +51,5 @@
     smart_albums_install();
     
-    if ($pwg_loaded_plugins['SmartAlbums']['version'] != 'auto')
+    if ( $pwg_loaded_plugins['SmartAlbums']['version'] != 'auto' and SMART_VERSION != 'auto' )
     {
       $query = '
Index: /extensions/SmartAlbums/maintain.inc.php
===================================================================
--- /extensions/SmartAlbums/maintain.inc.php	(revision 19445)
+++ /extensions/SmartAlbums/maintain.inc.php	(revision 19446)
@@ -24,4 +24,5 @@
   pwg_query('DROP TABLE `' . $prefixeTable . 'category_filters`;');
   pwg_query('ALTER TABLE `' . IMAGE_CATEGORY_TABLE . '` DROP `smart`;');
+  pwg_query('ALTER TABLE `' . CATEGORIES_TABLE . '` DROP `smart_update`;');
   pwg_query('DELETE FROM `' . CONFIG_TABLE . '` WHERE param = \'SmartAlbums\' LIMIT 1;');
 }
