Index: /extensions/community/add_photos.php
===================================================================
--- /extensions/community/add_photos.php	(revision 23036)
+++ /extensions/community/add_photos.php	(revision 23037)
@@ -73,6 +73,91 @@
 include_once(PHPWG_ROOT_PATH.'admin/include/photos_add_direct_process.inc.php');
 
+// +-----------------------------------------------------------------------+
+// | limits                                                                |
+// +-----------------------------------------------------------------------+
+
+// has the user reached its limits?
+$user['community_usage'] = community_get_user_limits($user['id']);
+// echo '<pre>'; print_r($user['community_usage']); echo '</pre>';
+
+// +-----------------------------------------------------------------------+
+// | set properties, moderate, notify                                      |
+// +-----------------------------------------------------------------------+
+
 if (isset($image_ids) and count($image_ids) > 0)
 {
+  $query = '
+SELECT
+    id,
+    file,
+    filesize
+  FROM '.IMAGES_TABLE.'
+  WHERE id IN ('.implode(',', $image_ids).')
+  ORDER BY id DESC
+;';
+  $images = array_from_query($query);
+
+  $nb_images_deleted = 0;
+  
+  // upload has just happened, maybe the user is over quota
+  if ($user_permissions['storage'] > 0 and $user['community_usage']['storage'] > $user_permissions['storage'])
+  {
+    foreach ($images as $image)
+    {
+      array_push(
+        $page['errors'],
+        sprintf(l10n('Photo %s rejected.'), $image['file'])
+        .' '.sprintf(l10n('Disk usage quota reached (%uMB)'), $user_permissions['storage'])
+        );
+      
+      delete_elements(array($image['id']), true);
+      foreach ($page['thumbnails'] as $tn_idx => $thumbnail)
+      {
+        if ($thumbnail['file'] == $image['file'])
+        {
+          unset($page['thumbnails'][$idx]);
+        }
+      }
+
+      $user['community_usage'] = community_get_user_limits($user['id']);
+      
+      if ($user['community_usage']['storage'] <= $user_permissions['storage'])
+      {
+        // we stop the deletions
+        break;
+      }
+    }
+  }
+
+  if ($user_permissions['nb_photos'] > 0 and $user['community_usage']['nb_photos'] > $user_permissions['nb_photos'])
+  {
+    foreach ($images as $image)
+    {
+      array_push(
+        $page['errors'],
+        sprintf(l10n('Photo %s rejected.'), $image['file'])
+        .' '.sprintf(l10n('Maximum number of photos reached (%u)'), $user_permissions['nb_photos'])
+        );
+      
+      delete_elements(array($image['id']), true);
+      foreach ($page['thumbnails'] as $tn_idx => $thumbnail)
+      {
+        if ($thumbnail['file'] == $image['file'])
+        {
+          unset($page['thumbnails'][$idx]);
+        }
+      }
+
+      $user['community_usage'] = community_get_user_limits($user['id']);
+      
+      if ($user['community_usage']['nb_photos'] <= $user_permissions['nb_photos'])
+      {
+        // we stop the deletions
+        break;
+      }
+    }
+  }
+     
+  
   // reinitialize the informations to display on the result page
   $page['infos'] = array();
@@ -112,17 +197,20 @@
       );
   }
-  
-  // $category_id is set in the photos_add_direct_process.inc.php included script
-  $category_infos = get_cat_info($category_id);
-  $category_name = get_cat_display_name($category_infos['upper_names']);
-
-  array_push(
-    $page['infos'],
-    sprintf(
-      l10n('%d photos uploaded into album "%s"'),
-      count($page['thumbnails']),
-      '<em>'.$category_name.'</em>'
-      )
-    );
+
+  if (count($page['thumbnails']) > 0)
+  {
+    // $category_id is set in the photos_add_direct_process.inc.php included script
+    $category_infos = get_cat_info($category_id);
+    $category_name = get_cat_display_name($category_infos['upper_names']);
+
+    array_push(
+      $page['infos'],
+      sprintf(
+        l10n('%d photos uploaded into album "%s"'),
+        count($page['thumbnails']),
+        '<em>'.$category_name.'</em>'
+        )
+      );
+  }
 
   // should the photos be moderated?
@@ -186,37 +274,40 @@
         );
     }
-    
-    mass_inserts(
-      COMMUNITY_PENDINGS_TABLE,
-      array_keys($inserts[0]),
-      $inserts
-      );
-
-    // find the url to the medium size
-    $page['thumbnails'] = array();
-
-    $query = '
+
+    if (count($inserts) > 0)
+    {
+      mass_inserts(
+        COMMUNITY_PENDINGS_TABLE,
+        array_keys($inserts[0]),
+        $inserts
+        );
+      
+      // find the url to the medium size
+      $page['thumbnails'] = array();
+
+      $query = '
 SELECT *
   FROM '.IMAGES_TABLE.'
   WHERE id IN ('.implode(',', $image_ids).')
 ;';
-    $result = pwg_query($query);
-    while ($row = pwg_db_fetch_assoc($result))
-    {
-      $src_image = new SrcImage($row);
-
-      $page['thumbnails'][] = array(
-        'file' => $row['file'],
-        'src' => DerivativeImage::url(IMG_THUMB, $src_image),
-        'title' => $row['name'],
-        'link' => $image_url = DerivativeImage::url(IMG_MEDIUM, $src_image),
-        'lightbox' => true,
+      $result = pwg_query($query);
+      while ($row = pwg_db_fetch_assoc($result))
+      {
+        $src_image = new SrcImage($row);
+        
+        $page['thumbnails'][] = array(
+          'file' => $row['file'],
+          'src' => DerivativeImage::url(IMG_THUMB, $src_image),
+          'title' => $row['name'],
+          'link' => $image_url = DerivativeImage::url(IMG_MEDIUM, $src_image),
+          'lightbox' => true,
+          );
+      }
+      
+      array_push(
+        $page['infos'],
+        l10n('Your photos are waiting for validation, administrators have been notified')
         );
     }
-
-    array_push(
-      $page['infos'],
-      l10n('Your photos are waiting for validation, administrators have been notified')
-      );
   }
   else
@@ -247,43 +338,134 @@
 
   invalidate_user_cache();
-
-  // let's notify administrators
-  include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
-
-  $keyargs_content = array(
-    get_l10n_args('Hi administrators,', ''),
-    get_l10n_args('', ''),
-    get_l10n_args('Album: %s', get_cat_display_name($category_infos['upper_names'], null, false)),
-    get_l10n_args('User: %s', $user['username']),
-    get_l10n_args('Email: %s', $user['email']),
+  
+  if (count($page['thumbnails']))
+  {
+    // let's notify administrators
+    include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
+
+    $keyargs_content = array(
+      get_l10n_args('Hi administrators,', ''),
+      get_l10n_args('', ''),
+      get_l10n_args('Album: %s', get_cat_display_name($category_infos['upper_names'], null, false)),
+      get_l10n_args('User: %s', $user['username']),
+      get_l10n_args('Email: %s', $user['email']),
+      );
+
+    if ($moderate)
+    {
+      $keyargs_content[] = get_l10n_args('', '');
+      
+      array_push(
+        $keyargs_content,
+        get_l10n_args(
+          'Validation page: %s',
+          get_absolute_root_url().'admin.php?page=plugin-community-pendings'
+          )
+        );
+    }
+
+    pwg_mail_notification_admins(
+      get_l10n_args('%d photos uploaded by %s', array(count($image_ids), $user['username'])),
+      $keyargs_content,
+      false
+      );
+  }
+}
+
+// +-----------------------------------------------------------------------+
+// |                             prepare form                              |
+// +-----------------------------------------------------------------------+
+
+$template->set_filenames(array('add_photos' => dirname(__FILE__).'/add_photos.tpl'));
+
+include_once(PHPWG_ROOT_PATH.'admin/include/photos_add_direct_prepare.inc.php');
+
+$quota_available = array(
+  'summary' => array(),
+  'details' => array(),
+  );
+
+// there is a limit on storage for this user
+if ($user_permissions['storage'] > 0)
+{
+  $remaining_storage = $user_permissions['storage'] - $user['community_usage']['storage'];
+  
+  if ($remaining_storage <= 0)
+  {
+    echo 'limit storage reached<br>';
+    // limit reached
+    $setup_errors[] = sprintf(
+      l10n('Disk usage quota reached (%uMB)'),
+      $user_permissions['storage']
+      );
+  }
+  else
+  {
+    $quota_available['summary'][] = $remaining_storage.'MB';
+    
+    $quota_available['details'][] = sprintf(
+      l10n('%s out of %s'),
+      $remaining_storage.'MB',
+      $user_permissions['storage']
+      );
+    
+    $template->assign(
+      array(
+        'limit_storage' => $remaining_storage*1024*1024,
+        'limit_storage_total_mb' => $user_permissions['storage'],
+        )
+      );
+  }
+}
+
+// there is a limit on number of photos for this user
+if ($user_permissions['nb_photos'] > 0)
+{
+  $remaining_nb_photos = $user_permissions['nb_photos'] - $user['community_usage']['nb_photos'];
+  
+  if ($remaining_nb_photos <= 0)
+  {
+    echo 'limit nb_photos reached<br>';
+    // limit reached
+    $setup_errors[] = sprintf(
+      l10n('Maximum number of photos reached (%u)'),
+      $user_permissions['nb_photos']
+      );
+  }
+  else
+  {
+    $quota_available['summary'][] = l10n_dec('%d photo', '%d photos', $remaining_nb_photos);
+    
+    $quota_available['details'][] = sprintf(
+      l10n('%s out of %s'),
+      l10n_dec('%d photo', '%d photos', $remaining_nb_photos),
+      $user_permissions['nb_photos']
+      );
+    
+    $template->assign('limit_nb_photos', $remaining_nb_photos);
+  }
+}
+
+if (count($quota_available['details']) > 0)
+{
+  $template->assign(
+    array(
+      'quota_summary' => sprintf(
+        l10n('Available %s.'),
+        implode(', ', $quota_available['summary'])
+        ),
+      'quota_details' => sprintf(
+        l10n('Available quota %s.'),
+        implode(', ', $quota_available['details'])
+        ),
+      )
     );
-
-  if ($moderate)
-  {
-    $keyargs_content[] = get_l10n_args('', '');
-    
-    array_push(
-      $keyargs_content,
-      get_l10n_args(
-        'Validation page: %s',
-        get_absolute_root_url().'admin.php?page=plugin-community-pendings'
-        )
-      );
-  }
-
-  pwg_mail_notification_admins(
-    get_l10n_args('%d photos uploaded by %s', array(count($image_ids), $user['username'])),
-    $keyargs_content,
-    false
-    );
-}
-
-// +-----------------------------------------------------------------------+
-// |                             prepare form                              |
-// +-----------------------------------------------------------------------+
-
-$template->set_filenames(array('add_photos' => dirname(__FILE__).'/add_photos.tpl'));
-
-include_once(PHPWG_ROOT_PATH.'admin/include/photos_add_direct_prepare.inc.php');
+}
+
+$template->assign(
+  array(
+    'setup_errors'=> $setup_errors,
+    )
+  );
 
 // we have to change the list of uploadable albums
Index: /extensions/community/add_photos.tpl
===================================================================
--- /extensions/community/add_photos.tpl	(revision 23036)
+++ /extensions/community/add_photos.tpl	(revision 23037)
@@ -12,4 +12,46 @@
 {footer_script}{literal}
 jQuery(document).ready(function(){
+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('');
+}
+
   function checkUploadStart() {
     var nbErrors = 0;
@@ -167,8 +209,18 @@
 {/literal}
 {if $upload_mode eq 'html'}
+  {if isset($limit_nb_photos)}
+  var limit_nb_photos = {$limit_nb_photos};
+  {/if}
 {literal}
+
   function addUploadBox() {
     var uploadBox = '<p class="file"><input type="file" size="60" name="image_upload[]"></p>';
     jQuery(uploadBox).appendTo("#uploadBoxes");
+
+    if (typeof limit_nb_photos != 'undefined') {
+      if (jQuery("input[name^=image_upload]").size() >= limit_nb_photos) {
+        jQuery("#addUploadBox").hide();
+      }
+    }
   }
 
@@ -176,4 +228,11 @@
 
   jQuery("#addUploadBox A").click(function () {
+    if (typeof limit_nb_photos != 'undefined') {
+      if (jQuery("input[name^=image_upload]").size() >= limit_nb_photos) {
+        alert('tu rigoles mon gaillard !');
+        return false;
+      }
+    }
+
     addUploadBox();
   });
@@ -191,4 +250,8 @@
 var buttonText = "{'Select files'|@translate}";
 var sizeLimit = Math.round({$upload_max_filesize} / 1024); /* in KBytes */
+var sumQueueFilesize = 0;
+  {if isset($limit_storage)}
+var limit_storage = {$limit_storage};
+  {/if}
 
 {literal}
@@ -209,7 +272,42 @@
     'fileSizeLimit'  : sizeLimit,
     'progressData'   : 'percentage',
+{/literal}
+  {if isset($limit_nb_photos)}
+    'queueSizeLimit' : {$limit_nb_photos},
+  {/if}
+{literal}
     requeueErrors   : false,
-    'onSelect'       : function(event,ID,fileObj) {
+    'onSelect'       : function(file) {
+      console.log('filesize = '+file.size+'bytes');
+
+      if (typeof limit_storage != 'undefined') {
+        if (sumQueueFilesize + file.size > limit_storage) {
+          jQuery.jGrowl(
+            '<p></p>'+sprintf(
+              '{/literal}{'File %s too big (%uMB), quota of %uMB exceeded'|@translate}{literal}',
+              file.name,
+              Math.round(file.size/(1024*1024)),
+              limit_storage/(1024*1024)
+            ),
+            {
+              theme:  'error',
+              header: 'ERROR',
+              life:   4000,
+              sticky: false
+            }
+          );
+
+          jQuery('#uploadify').uploadifyCancel(file.id);
+          return false;
+        }
+        else {
+          sumQueueFilesize += file.size;
+        }
+      }
+
       jQuery("#fileQueue").show();
+    },
+    'onCancel' : function(file) {
+      console.log('The file ' + file.name + ' was cancelled ('+file.size+')');
     },
     'onQueueComplete'  : function(stats) {
@@ -432,5 +530,6 @@
       <legend>{'Select files'|@translate}</legend>
 
-    <p id="uploadWarningsSummary">{$upload_max_filesize_shorthand}B. {$upload_file_types}. {if isset($max_upload_resolution)}{$max_upload_resolution}Mpx{/if} <a class="showInfo" title="{'Learn more'|@translate}">i</a></p>
+    <p id="uploadWarningsSummary">{$upload_max_filesize_shorthand}B. {$upload_file_types}. {if isset($max_upload_resolution)}{$max_upload_resolution}Mpx.{/if} {$quota_summary}
+<a class="showInfo" title="{'Learn more'|@translate}">i</a></p>
 
     <p id="uploadWarnings">
@@ -440,4 +539,5 @@
 {'Approximate maximum resolution: %dM pixels (that\'s %dx%d pixels).'|@translate|@sprintf:$max_upload_resolution:$max_upload_width:$max_upload_height}
   {/if}
+{$quota_details}
     </p>
 
Index: /extensions/community/admin_pendings.php
===================================================================
--- /extensions/community/admin_pendings.php	(revision 23036)
+++ /extensions/community/admin_pendings.php	(revision 23037)
@@ -180,5 +180,5 @@
     $category_for_image[ $row['image_id'] ] = get_cat_display_name_cache(
       $row['uppercats'],
-      'admin.php?page=cat_modify&amp;cat_id=',
+      'admin.php?page=album-',
       false,
       true,
Index: /extensions/community/admin_permissions.php
===================================================================
--- /extensions/community/admin_permissions.php	(revision 23036)
+++ /extensions/community/admin_permissions.php	(revision 23037)
@@ -73,4 +73,14 @@
   check_input_parameter('moderated', $_POST, false, '/^(true|false)$/');
 
+  if (-1 != $_POST['nb_photos'])
+  {
+    check_input_parameter('nb_photos', $_POST, false, PATTERN_ID);
+  }
+
+  if (-1 != $_POST['storage'])
+  {
+    check_input_parameter('storage', $_POST, false, PATTERN_ID);
+  }
+
   // creating the permission
   $insert = array(
@@ -82,4 +92,6 @@
     'create_subcategories' => isset($_POST['create_subcategories']) ? 'true' : 'false',
     'moderated' => $_POST['moderated'],
+    'nb_photos' => $_POST['nb_photos'],
+    'storage' => $_POST['storage'],
     );
 
@@ -222,4 +234,6 @@
         'create_subcategories' => get_boolean($row['create_subcategories']),
         'moderated' => get_boolean($row['moderated']),
+        'nb_photos' => empty($row['nb_photos']) ? -1 : $row['nb_photos'],
+        'storage' => empty($row['storage']) ? -1 : $row['storage'],
         )
       );
@@ -231,4 +245,6 @@
     array(
       'moderated' => true,
+      'nb_photos' => -1,
+      'storage' => -1,
       )
     );
@@ -445,5 +461,27 @@
     $highlight = true;
   }
-  
+
+  $nb_photos = false;
+  $nb_photos_tooltip = null;
+  if (!empty($permission['nb_photos']) and $permission['nb_photos'] > 0)
+  {
+    $nb_photos = $permission['nb_photos'];
+    $nb_photos_tooltip = sprintf(
+      l10n('up to %d photos (for each user)'),
+      $nb_photos
+      );
+  }
+
+  $storage = false;
+  $storage_tooltip = null;
+  if (!empty($permission['storage']) and $permission['storage'] > 0)
+  {
+    $storage = $permission['storage'];
+    $storage_tooltip = sprintf(
+      l10n('up to %dMB (for each user)'),
+      $storage
+      );
+  }
+
   
   $template->append(
@@ -456,4 +494,8 @@
       'RECURSIVE' => get_boolean($permission['recursive']),
       'RECURSIVE_TOOLTIP' => l10n('Apply to sub-albums'),
+      'NB_PHOTOS' => $nb_photos,
+      'NB_PHOTOS_TOOLTIP' => $nb_photos_tooltip,
+      'STORAGE' => $storage,
+      'STORAGE_TOOLTIP' => $storage_tooltip,
       'CREATE_SUBCATEGORIES' => get_boolean($permission['create_subcategories']),
       'U_DELETE' => $admin_base_url.'&amp;delete='.$permission['id'],
Index: /extensions/community/admin_permissions.tpl
===================================================================
--- /extensions/community/admin_permissions.tpl	(revision 23036)
+++ /extensions/community/admin_permissions.tpl	(revision 23037)
@@ -1,2 +1,5 @@
+{combine_script id='jquery.ui.slider' require='jquery.ui' load='footer' path='themes/default/js/ui/minified/jquery.ui.slider.min.js'}
+{combine_css path="themes/default/js/ui/theme/jquery.ui.slider.css"}
+
 {literal}
 <style>
@@ -6,8 +9,51 @@
 .permissionActions img {margin-bottom:-2px}
 .rowSelected {background-color:#C2F5C2 !important}
+#community_nb_photos, #community_storage {width:400px; display:inline-block; margin-right:10px;}
 </style>
 {/literal}
 
 {footer_script}{literal}
+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('');
+}
+
 $(document).ready(function() {
   $("select[name=who]").change(function () {
@@ -52,4 +98,80 @@
     return false;
   });
+
+  /* ∞ */
+  /**
+   * find the key from a value in the startStopValues array
+   */
+  function getSliderKeyFromValue(value, values) {
+    for (var key in values) {
+      if (values[key] == value) {
+        return key;
+      }
+    }
+    return 0;
+  }
+
+  var nbPhotosValues = [5,10,20,50,100,500,1000,5000,-1];
+
+  function getNbPhotosInfoFromIdx(idx) {
+    if (idx == nbPhotosValues.length - 1) {
+      return "{/literal}{'no limit'|@translate}{literal}";
+    }
+
+    return sprintf(
+      "{/literal}{'up to %d photos (for each user)'|@translate}{literal}",
+      nbPhotosValues[idx]
+    );
+  }
+
+  /* init nb_photos info span */
+  var nbPhotos_init = getSliderKeyFromValue(jQuery('input[name=nb_photos]').val(), nbPhotosValues);
+
+  jQuery("#community_nb_photos_info").html(getNbPhotosInfoFromIdx(nbPhotos_init));
+
+  jQuery("#community_nb_photos").slider({
+    range: "min",
+    min: 0,
+    max: nbPhotosValues.length - 1,
+    value: nbPhotos_init,
+    slide: function( event, ui ) {
+      jQuery("#community_nb_photos_info").html(getNbPhotosInfoFromIdx(ui.value));
+    },
+    stop: function( event, ui ) {
+      jQuery("input[name=nb_photos]").val(nbPhotosValues[ui.value]);
+    }
+  });
+
+  var storageValues = [10,50,100,200,500,1000,5000,-1];
+
+  function getStorageInfoFromIdx(idx) {
+    if (idx == storageValues.length - 1) {
+      return "{/literal}{'no limit'|@translate}{literal}";
+    }
+
+    return sprintf(
+      "{/literal}{'up to %dMB (for each user)'|@translate}{literal}",
+      storageValues[idx]
+    );
+  }
+
+  /* init storage info span */
+  var storage_init = getSliderKeyFromValue(jQuery('input[name=storage]').val(), storageValues);
+
+  jQuery("#community_storage_info").html(getStorageInfoFromIdx(storage_init));
+
+  jQuery("#community_storage").slider({
+    range: "min",
+    min: 0,
+    max: storageValues.length - 1,
+    value: storage_init,
+    slide: function( event, ui ) {
+      jQuery("#community_storage_info").html(getStorageInfoFromIdx(ui.value));
+    },
+    stop: function( event, ui ) {
+      jQuery("input[name=storage]").val(storageValues[ui.value]);
+    }
+  });
+
 });
 {/literal}{/footer_script}
@@ -104,9 +226,23 @@
     </p>
 
+    <p style="margin-bottom:0">
+      <strong>{'How many photos?'|@translate}</strong>
+    </p>
+    <div id="community_nb_photos"></div>
+    <span id="community_nb_photos_info">{'no limit'|@translate}</span>
+    <input type="hidden" name="nb_photos" value="{$nb_photos}">
+
+    <p style="margin-top:1.5em;margin-bottom:0;">
+      <strong>{'How much disk space?'|@translate}</strong>
+    </p>
+    <div id="community_storage"></div>
+    <span id="community_storage_info">{'no limit'|@translate}</span>
+    <input type="hidden" name="storage" value="{$storage}">
+
     {if isset($edit)}
       <input type="hidden" name="edit" value="{$edit}">
     {/if}
     
-    <p style="margin:0;">
+    <p style="margin-top:1.5em;">
       <input class="submit" type="submit" name="submit_add" value="{if isset($edit)}{'Submit'|@translate}{else}{'Add'|@translate}{/if}"/>
       <a href="{$F_ADD_ACTION}">{'Cancel'|@translate}</a>
@@ -128,8 +264,8 @@
     <td>{$permission.WHERE}</td>
     <td>
-      <span title="{$permission.TRUST_TOOLTIP}">{$permission.TRUST}</span>
-    {if $permission.RECURSIVE}
-, <span title="{$permission.RECURSIVE_TOOLTIP}">{'sub-albums'|@translate}</span>
-    {/if}
+      <span title="{$permission.TRUST_TOOLTIP}">{$permission.TRUST}</span>{if $permission.RECURSIVE},
+<span title="{$permission.RECURSIVE_TOOLTIP}">{'sub-albums'|@translate}</span>{/if}{if $permission.NB_PHOTOS},
+<span title="{$permission.NB_PHOTOS_TOOLTIP}">{'%d photos'|@translate|sprintf:$permission.NB_PHOTOS}</span>{/if}{if $permission.STORAGE},
+<span title="{$permission.STORAGE_TOOLTIP}">{$permission.STORAGE}MB</span>{/if}
     {if $permission.CREATE_SUBCATEGORIES}
 , {'sub-albums creation'|@translate}
Index: /extensions/community/include/functions_community.inc.php
===================================================================
--- /extensions/community/include/functions_community.inc.php	(revision 23036)
+++ /extensions/community/include/functions_community.inc.php	(revision 23037)
@@ -52,4 +52,6 @@
     'upload_categories' => array(),
     'permission_ids' => array(),
+    'nb_photos' => 0,
+    'storage' => 0,
     );
   
@@ -68,5 +70,7 @@
     category_id,
     recursive,
-    create_subcategories
+    create_subcategories,
+    nb_photos,
+    storage
   FROM '.COMMUNITY_PERMISSIONS_TABLE.'
   WHERE (type = \'any_visitor\')';
@@ -120,4 +124,30 @@
       }
     }
+
+    if ($return['nb_photos'] != -1)
+    {
+      if (empty($row['nb_photos']) or -1 == $row['nb_photos'])
+      {
+        // that means "no limit"
+        $return['nb_photos'] = -1;
+      }
+      elseif ($row['nb_photos'] > $return['nb_photos'])
+      {
+        $return['nb_photos'] = $row['nb_photos'];
+      }
+    }
+    
+    if ($return['storage'] != -1)
+    {
+      if (empty($row['storage']) or -1 == $row['storage'])
+      {
+        // that means "no limit"
+        $return['storage'] = -1;
+      }
+      elseif ($row['storage'] > $return['storage'])
+      {
+        $return['storage'] = $row['storage'];
+      }
+    }
   }
 
@@ -126,4 +156,6 @@
     $return ['upload_whole_gallery'] = true;
     $return ['create_whole_gallery'] = true;
+    $return['nb_photos'] = -1;
+    $return['storage'] = -1;
   }
 
@@ -270,3 +302,16 @@
   }
 }
+
+function community_get_user_limits($user_id)
+{
+  // how many photos and storage for this user?
+  $query = '
+SELECT
+    COUNT(id) AS nb_photos,
+    IFNULL(FLOOR(SUM(filesize)/1024), 0) AS storage
+  FROM '.IMAGES_TABLE.'
+  WHERE added_by = '.$user_id.'
+;';
+  return pwg_db_fetch_assoc(pwg_query($query));
+}
 ?>
Index: /extensions/community/include/install.inc.php
===================================================================
--- /extensions/community/include/install.inc.php	(revision 23037)
+++ /extensions/community/include/install.inc.php	(revision 23037)
@@ -0,0 +1,69 @@
+<?php
+// +-----------------------------------------------------------------------+
+// | Piwigo - a PHP based photo gallery                                    |
+// +-----------------------------------------------------------------------+
+// | Copyright(C) 2008-2013 Piwigo Team                  http://piwigo.org |
+// | Copyright(C) 2003-2008 PhpWebGallery Team    http://phpwebgallery.net |
+// | Copyright(C) 2002-2003 Pierrick LE GALL   http://le-gall.net/pierrick |
+// +-----------------------------------------------------------------------+
+// | This program is free software; you can redistribute it and/or modify  |
+// | it under the terms of the GNU General Public License as published by  |
+// | the Free Software Foundation                                          |
+// |                                                                       |
+// | This program is distributed in the hope that it will be useful, but   |
+// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
+// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
+// | General Public License for more details.                              |
+// |                                                                       |
+// | You should have received a copy of the GNU General Public License     |
+// | along with this program; if not, write to the Free Software           |
+// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
+// | USA.                                                                  |
+// +-----------------------------------------------------------------------+
+
+function community_install()
+{
+  global $conf, $prefixeTable;
+    
+  $query = '
+CREATE TABLE IF NOT EXISTS '.$prefixeTable.'community_permissions (
+  id int(11) NOT NULL AUTO_INCREMENT,
+  type varchar(255) NOT NULL,
+  group_id smallint(5) unsigned DEFAULT NULL,
+  user_id smallint(5) DEFAULT NULL,
+  category_id smallint(5) unsigned DEFAULT NULL,
+  recursive enum(\'true\',\'false\') NOT NULL DEFAULT \'true\',
+  create_subcategories enum(\'true\',\'false\') NOT NULL DEFAULT \'false\',
+  moderated enum(\'true\',\'false\') NOT NULL DEFAULT \'true\',
+  nb_photos int DEFAULT NULL,
+  storage int DEFAULT NULL,
+  PRIMARY KEY (id)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8
+;';
+  pwg_query($query);
+
+  $query = '
+CREATE TABLE IF NOT EXISTS '.$prefixeTable.'community_pendings (
+  image_id mediumint(8) unsigned NOT NULL,
+  state varchar(255) NOT NULL,
+  added_on datetime NOT NULL,
+  validated_by smallint(5) DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=utf8
+;';
+  pwg_query($query);
+
+  // column community_permissions.nb_photos added for version 2.5.d
+  $result = pwg_query('SHOW COLUMNS FROM `'.$prefixeTable.'community_permissions` LIKE "nb_photos";');
+  if (!pwg_db_num_rows($result))
+  {     
+    pwg_query('ALTER TABLE `'.$prefixeTable .'community_permissions` ADD `nb_photos` INT DEFAULT NULL;');
+  }
+  
+  // column community_permissions.storage added for version 2.5.d
+  $result = pwg_query('SHOW COLUMNS FROM `'.$prefixeTable.'community_permissions` LIKE "storage";');
+  if (!pwg_db_num_rows($result))
+  {     
+    pwg_query('ALTER TABLE `'.$prefixeTable .'community_permissions` ADD `storage` INT DEFAULT NULL;');
+  }
+}
+?>
Index: /extensions/community/language/en_UK/plugin.lang.php
===================================================================
--- /extensions/community/language/en_UK/plugin.lang.php	(revision 23036)
+++ /extensions/community/language/en_UK/plugin.lang.php	(revision 23037)
@@ -63,3 +63,15 @@
 
 $lang['Set Photo Properties'] = 'Set Photo Properties';
+$lang['Available %s.'] = 'Available %s.';
+$lang['Available quota %s.'] = 'Available quota %s.';
+$lang['%s out of %s'] = '%s out of %s';
+$lang['File %s too big (%uMB), quota of %uMB exceeded'] = 'File %s too big (%uMB), quota of %uMB exceeded';
+$lang['Disk usage quota reached (%uMB)'] = 'Disk usage quota reached (%uMB)';
+$lang['Maximum number of photos reached (%u)'] = 'Maximum number of photos reached (%u)';
+$lang['Photo %s rejected.'] = 'Photo %s rejected.';
+$lang['no limit'] = 'no limit';
+$lang['up to %d photos (for each user)'] = 'up to %d photos (for each user)';
+$lang['up to %dMB (for each user)'] = 'up to %dMB (for each user)';
+$lang['How much disk space?'] = 'How much disk space?';
+$lang['How many photos?'] = 'How many photos?';
 ?>
Index: /extensions/community/language/fr_FR/plugin.lang.php
===================================================================
--- /extensions/community/language/fr_FR/plugin.lang.php	(revision 23036)
+++ /extensions/community/language/fr_FR/plugin.lang.php	(revision 23037)
@@ -50,5 +50,5 @@
 $lang['%d photos uploaded by %s'] = '%d photos ajoutées par %s';
 $lang['Validation page: %s'] = 'Page de validation : %s';
-$lang['%d photos uploaded into album "%s"'] = '%d photos ajoutée à l\'album "%s"';
+$lang['%d photos uploaded into album "%s"'] = '%d photos ajoutées à l\'album "%s"';
 $lang['Hi administrators,'] = 'Bonjours chers administrateurs,';
 
@@ -65,3 +65,15 @@
 
 $lang['Set Photo Properties'] = 'Définir les propriétés de la photo';
+$lang['Available %s.'] = 'Disponible %s.';
+$lang['Available quota %s.'] = 'Quota disponible %s.';
+$lang['%s out of %s'] = '%s sur %s';
+$lang['File %s too big (%uMB), quota of %uMB exceeded'] = 'Fichier %s trop gros (%uMB), quota de %uMB dépassé';
+$lang['Disk usage quota reached (%uMB)'] = 'Quota d\'utilisation disque atteint (%uMB)';
+$lang['Maximum number of photos reached (%u)'] = 'Nombre maximum de photos atteint (%u)';
+$lang['Photo %s rejected.'] = 'Photo %s rejetée.';
+$lang['no limit'] = 'pas de limite';
+$lang['up to %d photos (for each user)'] = 'jusqu\'à %d photos (pour chaque utilisateur)';
+$lang['up to %dMB (for each user)'] = 'jusqu\'à %dMB (pour chaque utilisateur)';
+$lang['How much disk space?'] = 'Combien d\'espace disque ?';
+$lang['How many photos?'] = 'Combien de photos ?';
 ?>
Index: /extensions/community/main.inc.php
===================================================================
--- /extensions/community/main.inc.php	(revision 23036)
+++ /extensions/community/main.inc.php	(revision 23037)
@@ -14,11 +14,54 @@
 }
 
+global $prefixeTable;
+
+// +-----------------------------------------------------------------------+
+// | Define plugin constants                                               |
+// +-----------------------------------------------------------------------+
+
+defined('COMMUNITY_ID') or define('COMMUNITY_ID', basename(dirname(__FILE__)));
 define('COMMUNITY_PATH' , PHPWG_PLUGINS_PATH.basename(dirname(__FILE__)).'/');
-
-global $prefixeTable;
 define('COMMUNITY_PERMISSIONS_TABLE', $prefixeTable.'community_permissions');
 define('COMMUNITY_PENDINGS_TABLE', $prefixeTable.'community_pendings');
+define('COMMUNITY_VERSION', 'auto');
 
 include_once(COMMUNITY_PATH.'include/functions_community.inc.php');
+
+// init the plugin
+add_event_handler('init', 'community_init');
+/**
+ * plugin initialization
+ *   - check for upgrades
+ *   - unserialize configuration
+ *   - load language
+ */
+function community_init()
+{
+  global $conf, $pwg_loaded_plugins;
+
+  // apply upgrade if needed
+  if (
+    COMMUNITY_VERSION == 'auto' or
+    $pwg_loaded_plugins[COMMUNITY_ID]['version'] == 'auto' or
+    version_compare($pwg_loaded_plugins[COMMUNITY_ID]['version'], COMMUNITY_VERSION, '<')
+  )
+  {
+    // call install function
+    include_once(COMMUNITY_PATH.'include/install.inc.php');
+    community_install();
+
+    // update plugin version in database
+    if ( $pwg_loaded_plugins[COMMUNITY_ID]['version'] != 'auto' and COMMUNITY_VERSION != 'auto' )
+    {
+      $query = '
+UPDATE '. PLUGINS_TABLE .'
+SET version = "'. COMMUNITY_VERSION .'"
+WHERE id = "'. COMMUNITY_ID .'"';
+      pwg_query($query);
+
+      $pwg_loaded_plugins[COMMUNITY_ID]['version'] = COMMUNITY_VERSION;
+    }
+  }
+}
 
 /* Plugin admin */
Index: /extensions/community/maintain.inc.php
===================================================================
--- /extensions/community/maintain.inc.php	(revision 23036)
+++ /extensions/community/maintain.inc.php	(revision 23037)
@@ -1,3 +1,4 @@
 <?php
+defined('PHPWG_ROOT_PATH') or die('Hacking attempt!');
 
 if (!defined("COMMUNITY_PATH"))
@@ -6,32 +7,10 @@
 }
 
+include_once(COMMUNITY_PATH.'/include/install.inc.php');
+
 function plugin_install()
 {
-  global $conf, $prefixeTable;
-
-  $query = '
-CREATE TABLE '.$prefixeTable.'community_permissions (
-  id int(11) NOT NULL AUTO_INCREMENT,
-  type varchar(255) NOT NULL,
-  group_id smallint(5) unsigned DEFAULT NULL,
-  user_id smallint(5) DEFAULT NULL,
-  category_id smallint(5) unsigned DEFAULT NULL,
-  recursive enum(\'true\',\'false\') NOT NULL DEFAULT \'true\',
-  create_subcategories enum(\'true\',\'false\') NOT NULL DEFAULT \'false\',
-  moderated enum(\'true\',\'false\') NOT NULL DEFAULT \'true\',
-  PRIMARY KEY (id)
-) ENGINE=MyISAM DEFAULT CHARACTER SET utf8
-;';
-  pwg_query($query);
-
-  $query = '
-CREATE TABLE '.$prefixeTable.'community_pendings (
-  image_id mediumint(8) unsigned NOT NULL,
-  state varchar(255) NOT NULL,
-  added_on datetime NOT NULL,
-  validated_by smallint(5) DEFAULT NULL
-) ENGINE=MyISAM DEFAULT CHARACTER SET utf8
-;';
-  pwg_query($query);
+  community_install();
+  define('community_installed', true);
 }
 
@@ -50,20 +29,8 @@
 {
   global $prefixeTable;
-
-  $are_new_tables_installed = false;
-  
-  $query = 'SHOW TABLES;';
-  $result = pwg_query($query);
-  while ($row = pwg_db_fetch_row($result))
-  {
-    if ($prefixeTable.'community_permissions' == $row[0])
-    {
-      $are_new_tables_installed = true;
-    }
-  }
-
-  if (!$are_new_tables_installed)
-  {
-    plugin_install();
+  
+  if (!defined('community_installed')) // a plugin is activated just after its installation
+  {
+    community_install();
   }
 
