Index: /trunk/admin/themes/default/js/common.js
===================================================================
--- /trunk/admin/themes/default/js/common.js	(revision 25194)
+++ /trunk/admin/themes/default/js/common.js	(revision 25194)
@@ -0,0 +1,50 @@
+function array_delete(arr, item) {
+  var i = arr.indexOf(item);
+  if (i != -1) arr.splice(i, 1);
+}
+
+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: /trunk/admin/themes/default/template/user_list.tpl
===================================================================
--- /trunk/admin/themes/default/template/user_list.tpl	(revision 25193)
+++ /trunk/admin/themes/default/template/user_list.tpl	(revision 25194)
@@ -1,7 +1,245 @@
+{combine_script id='common' load='footer' path='admin/themes/default/js/common.js'}
+
+{combine_script id='jquery.dataTables' load='footer' path='themes/default/js/plugins/jquery.dataTables.js'}
+{combine_css path="themes/default/js/plugins/datatables/css/jquery.dataTables.css"}
+
+{footer_script}
+var selectedMessage_pattern = "{'%d of %d photos selected'|@translate}";
+var selectedMessage_none = "{'No photo selected, %d photos in current set'|@translate}";
+var selectedMessage_all = "{'All %d photos are selected'|@translate}";
+var applyOnDetails_pattern = "{'on the %d selected users'|@translate}";
+var missingConfirm = "{'You need to confirm deletion'|translate}";
+
+var allUsers = [{$all_users}];
+var selection = [{$selection}];
+{/footer_script}
+
+{footer_script}{literal}
+jQuery(document).ready(function() {
+  /* first column must be prefixed with the open/close icon */
+  var aoColumns = [
+    {
+      'bVisible':false
+    },
+    {
+      "mRender": function(data, type, full) {
+        return '<label><input type="checkbox" data-user_id="'+full[0]+'"> '+data+'</label>';
+      }
+    }
+  ];
+
+  for (i=2; i<jQuery("#userList thead tr th").length; i++) {
+    aoColumns.push(null);
+  }
+
+  var oTable = jQuery('#userList').dataTable({
+    "iDisplayLength": 10,
+    "bDeferRender": true,
+    "bProcessing": true,
+    "bServerSide": true,
+    "sAjaxSource": "admin/user_list_backend.php",
+    "fnDrawCallback": function( oSettings ) {
+      jQuery("#userList input[type=checkbox]").each(function() {
+        var user_id = jQuery(this).data("user_id");
+        jQuery(this).prop('checked', (selection.indexOf(user_id) != -1));
+      });
+    },
+    "aoColumns": aoColumns
+  });
+
+  /**
+   * Selection management
+   */
+  function checkSelection() {
+    if (selection.length > 0) {
+      jQuery("#forbidAction").hide();
+      jQuery("#permitAction").show();
+
+      jQuery("#applyOnDetails").text(
+        sprintf(
+          applyOnDetails_pattern,
+          selection.length
+        )
+      );
+
+      if (selection.length == allUsers.length) {
+        jQuery("#selectedMessage").text(
+          sprintf(
+            selectedMessage_all,
+            allUsers.length
+          )
+        );
+      }
+      else {
+        jQuery("#selectedMessage").text(
+          sprintf(
+            selectedMessage_pattern,
+            selection.length,
+            allUsers.length
+          )
+        );
+      }
+    }
+    else {
+      jQuery("#forbidAction").show();
+      jQuery("#permitAction").hide();
+
+      jQuery("#selectedMessage").text(
+        sprintf(
+          selectedMessage_none,
+          allUsers.length
+        )
+      );
+    }
+
+    jQuery("#applyActionBlock .infos").hide();
+  }
+
+  jQuery(document).on('change', '#userList input[type=checkbox]',  function() {
+    var user_id = jQuery(this).data("user_id");
+
+    array_delete(selection, user_id);
+
+    if (jQuery(this).is(":checked")) {
+      selection.push(user_id);
+    }
+
+    checkSelection();
+  });
+
+  jQuery("#selectAll").click(function () {
+    selection = allUsers;
+    jQuery("#userList input[type=checkbox]").prop('checked', true);
+    checkSelection();
+    return false;
+  });
+
+  jQuery("#selectNone").click(function () {
+    selection = [];
+    jQuery("#userList input[type=checkbox]").prop('checked', false);
+    checkSelection();
+    return false;
+  });
+
+  jQuery("#selectInvert").click(function () {
+    var newSelection = [];
+    for(var i in allUsers)
+    {
+      if (selection.indexOf(allUsers[i]) == -1) {
+        newSelection.push(allUsers[i]);
+      }
+    }
+    selection = newSelection;
+
+    jQuery("#userList input[type=checkbox]").each(function() {
+      var user_id = jQuery(this).data("user_id");
+      jQuery(this).prop('checked', (selection.indexOf(user_id) != -1));
+    });
+
+    checkSelection();
+    return false;
+  });
+
+  /**
+   * Action management
+   */
+  jQuery("[id^=action_]").hide();
+  
+  jQuery("select[name=selectAction]").change(function () {
+    jQuery("#applyActionBlock .infos").hide();
+
+    jQuery("[id^=action_]").hide();
+
+    jQuery("#action_"+$(this).prop("value")).show();
+  
+    if (jQuery(this).val() != -1) {
+      jQuery("#applyActionBlock").show();
+    }
+    else {
+      jQuery("#applyActionBlock").hide();
+    }
+  });
+
+  jQuery("#permitAction input, #permitAction select").click(function() {
+    jQuery("#applyActionBlock .infos").hide();
+  });
+
+  jQuery("#applyAction").click(function() {
+    var action = jQuery("select[name=selectAction]").prop("value");
+    var method = null;
+    var data = {
+      user_id: selection
+    };
+
+    switch (action) {
+      case 'delete':
+        if (!jQuery("input[name=confirm_deletion]").is(':checked')) {
+          alert(missingConfirm);
+          return false;
+        }
+        method = 'pwg.users.delete';
+        break;
+      case 'group_associate':
+        method = 'pwg.groups.addUser';
+        data.group_id = jQuery("select[name=associate]").prop("value");
+        break;
+      case 'group_dissociate':
+        method = 'pwg.groups.deleteUser';
+        data.group_id = jQuery("select[name=dissociate]").prop("value");
+        break;
+    }
+
+    jQuery.ajax({
+      url: "ws.php?format=json&method="+method,
+      type:"POST",
+      data: data,
+      beforeSend: function() {
+        jQuery("#applyActionLoading").show();
+      },
+      success:function(data) {
+        oTable.fnDraw();
+        jQuery("#applyActionLoading").hide();
+        jQuery("#applyActionBlock .infos").show();
+
+        if (action == 'delete') {
+          var allUsers_new = [];
+          for(var i in allUsers)
+          {
+            if (selection.indexOf(allUsers[i]) == -1) {
+              allUsers_new.push(allUsers[i]);
+            }
+          }
+          allUsers = allUsers_new;
+          console.log('allUsers_new.length = '+allUsers_new.length);
+          selection = [];
+          checkSelection();
+        }
+      },
+      error:function(XMLHttpRequest, textStatus, errorThrows) {
+        jQuery("#applyActionLoading").hide();
+      }
+    });
+
+    return false;
+  });
+
+});
+{/literal}{/footer_script}
+
+{literal}
+<style>
+.dataTables_wrapper, .dataTables_info {clear:none;}
+table.dataTable {clear:right;padding-top:10px;}
+.bulkAction {margin-top:10px;}
+.actionButtons {margin-left:0;}
+#applyActionBlock .infos {background-image:none; padding:2px 5px; margin:0;border-radius:5px;}
+</style>
+{/literal}
+
 <div class="titrePage">
   <h2>{'User list'|@translate}</h2>
 </div>
 
-<form class="filter" method="post" name="add_user" action="{$F_ADD_ACTION}">
+<form style="display:none" class="filter" method="post" name="add_user" action="{$F_ADD_ACTION}">
   <fieldset>
     <legend>{'Add a user'|@translate}</legend>
@@ -19,272 +257,139 @@
 </form>
 
-<form class="filter" method="get" name="filter" action="{$F_FILTER_ACTION}">
-<fieldset>
-  <legend>{'Filter'|@translate}</legend>
-  <input type="hidden" name="page" value="user_list">
-
-  <label>{'Username'|@translate} <input type="text" name="username" value="{$F_USERNAME}"></label>
-
-  <label>
-  {'status'|@translate}
-  {html_options name=status options=$status_options selected=$status_selected}
-  </label>
-
-  <label>
-  {'Group'|@translate}
-  {html_options name=group options=$group_options selected=$group_selected}
-  </label>
-
-  <label>
-  {'Sort by'|@translate}
-  {html_options name=order_by options=$order_options selected=$order_selected}
-  </label>
-
-  <label>
-  {'Sort order'|@translate}
-  {html_options name=direction options=$direction_options selected=$direction_selected}
-  </label>
-
-  <label>
-  &nbsp;
-  <input class="submit" type="submit" value="{'Submit'|@translate}">
-  </label>
-
-</fieldset>
-
-</form>
-
 <form method="post" name="preferences" action="">
 
-{if !empty($navbar) }{include file='navigation_bar.tpl'|@get_extent:'navbar'}{/if}
-
-<table class="table2" width="97%">
+<table id="userList">
   <thead>
-    <tr class="throw">
-      <td>&nbsp;</td>
-      <td>{'Username'|@translate}</td>
-      <td>{'User status'|@translate}</td>
-      <td>{'Email address'|@translate}</td>
-      <td>{'Groups'|@translate}</td>
-      <td>{'Properties'|@translate}</td>
-      {if not empty($plugin_user_list_column_titles)}
-      {foreach from=$plugin_user_list_column_titles item=title}
-      <td>{$title}</td>
-      {/foreach}
-      {/if}
-      <td>{'Actions'|@translate}</td>
+    <tr>
+      <th>id</th>
+      <th>{'Username'|@translate}</th>
+      <th>{'Status'|@translate}</th>
+      <th>{'Email address'|@translate}</th>
     </tr>
   </thead>
-
-  {foreach from=$users item=user name=users_loop}
-  <tr class="{if $smarty.foreach.users_loop.index is odd}row1{else}row2{/if}">
-    <td><input type="checkbox" name="selection[]" value="{$user.ID}" {$user.CHECKED} id="selection-{$user.ID}"></td>
-    <td><label for="selection-{$user.ID}">{$user.USERNAME}</label></td>
-    <td>{$user.STATUS}</td>
-    <td>{$user.EMAIL}</td>
-    <td>{$user.GROUPS}</td>
-    <td>{$user.PROPERTIES}</td>
-    {foreach from=$user.plugin_columns item=data}
-    <td>{$data}</td>
-    {/foreach}
-    <td style="text-align:center;">
-      <a href="{$user.U_PERM}"><img src="{$ROOT_URL}{$themeconf.admin_icon_dir}/permissions.png" style="border:none" alt="{'Permissions'|@translate}" title="{'Permissions'|@translate}"></a>
-      <a href="{$user.U_PROFILE}"><img src="{$ROOT_URL}{$themeconf.admin_icon_dir}/edit_s.png" style="border:none" alt="{'Profile'|@translate}" title="{'Profile'|@translate}"></a>
-      {foreach from=$user.plugin_actions item=data}
-      {$data}
-      {/foreach}
-      </td>
-  </tr>
-  {/foreach}
 </table>
 
-{if !empty($navbar) }{include file='navigation_bar.tpl'|@get_extent:'navbar'}{/if}
-
-{* delete the selected users ? *}
-<fieldset>
-  <legend>{'Deletions'|@translate}</legend>
-  <label><input type="checkbox" name="confirm_deletion" value="1"> {'confirm'|@translate}</label>
-  <input class="submit" type="submit" value="{'Delete selected users'|@translate}" name="delete">
-</fieldset>
-
-<fieldset>
-  <legend>{'Status'|@translate}</legend>
-
-  <table>
-    <tr>
-      <td>{'Status'|@translate}</td>
-      <td>
-        <label><input type="radio" name="status_action" value="leave" checked="checked"> {'leave'|@translate}</label>
-        <label><input type="radio" name="status_action" value="set" id="status_action_set"> {'set to'|@translate}</label>
-        <select onchange="document.getElementById('status_action_set').checked = true;" name="status" size="1">
-          {html_options options=$pref_status_options selected=$pref_status_selected}
-        </select>
-      </td>
-    </tr>
-  </table>
-</fieldset>
-
-{* form to set properties for many users at once *}
-<fieldset>
-  <legend>{'Groups'|@translate}</legend>
-
-<table>
-
-  <tr>
-    <td>{'associate to group'|@translate}</td>
-    <td>
+<div style="clear:right"></div>
+
+<p class="checkActions">
+  {'Select:'|@translate}
+  <a href="#" id="selectAll">{'All'|@translate}</a>,
+  <a href="#" id="selectNone">{'None'|@translate}</a>,
+  <a href="#" id="selectInvert">{'Invert'|@translate}</a>
+
+  <span id="selectedMessage"></span>
+</p>
+
+<fieldset id="action">
+  <legend>{'Action'|@translate}</legend>
+
+  <div id="forbidAction"{if count($selection) != 0} style="display:none"{/if}>{'No user selected, no action possible.'|@translate}</div>
+  <div id="permitAction"{if count($selection) == 0} style="display:none"{/if}>
+
+    <select name="selectAction">
+      <option value="-1">{'Choose an action'|@translate}</option>
+      <option disabled="disabled">------------------</option>
+      <option value="delete" class="icon-trash">{'Delete selected users'|@translate}</option>
+      <option value="status">{'Status'|@translate}</option>
+      <option value="group_associate">{'associate to group'|translate}</option>
+      <option value="group_dissociate">{'dissociate from group'|@translate}</option>
+      <option value="enabled_high">{'High definition enabled'|@translate}</option>
+      <option value="level">{'Privacy level'|@translate}</option>
+      <option value="nb_image_page">{'Number of photos per page'|@translate}</option>
+      <option value="theme">{'Interface theme'|@translate}</option>
+      <option value="language">{'Language'|@translate}</option>
+      <option value="recent_period">{'Recent period'|@translate}</option>
+      <option value="expand">{'Expand all albums'|@translate}</option>
+{if $ACTIVATE_COMMENTS}
+      <option value="show_nb_comments">{'Show number of comments'|@translate}</option>
+{/if}
+      <option value="show_nb_hits">{'Show number of hits'|@translate}</option>
+    </select>
+
+    {* delete *}
+    <div id="action_delete" class="bulkAction">
+      <p><label><input type="checkbox" name="confirm_deletion" value="1"> {'Are you sure?'|@translate}</label></p>
+    </div>
+
+    {* status *}
+    <div id="action_status" class="bulkAction">
+      <select name="status">
+        {html_options options=$pref_status_options selected=$pref_status_selected}
+      </select>
+    </div>
+
+    {* group_associate *}
+    <div id="action_group_associate" class="bulkAction">
       {html_options name=associate options=$association_options selected=$associate_selected}
-    </td>
-  </tr>
-
-  <tr>
-    <td>{'dissociate from group'|@translate}</td>
-    <td>
+    </div>
+
+    {* group_dissociate *}
+    <div id="action_group_dissociate" class="bulkAction">
       {html_options name=dissociate options=$association_options selected=$dissociate_selected}
-    </td>
-  </tr>
-
-</table>
-
-</fieldset>
-
-{* Properties *}
-<fieldset>
-  <legend>{'Properties'|@translate}</legend>
-
-  <table>
-
-    <tr>
-      <td>{'High definition enabled'|@translate}</td>
-      <td>
-        <label><input type="radio" name="enabled_high" value="leave" checked="checked"> {'leave'|@translate}</label>
-        / {'set to'|@translate}
-        <label><input type="radio" name="enabled_high" value="true">{'Yes'|@translate}</label>
-        <label><input type="radio" name="enabled_high" value="false">{'No'|@translate}</label>
-      </td>
-    </tr>
-
-    <tr>
-      <td>{'Privacy level'|@translate}</td>
-      <td>
-	<label><input type="radio" name="level_action" value="leave" checked="checked">{'leave'|@translate}</label>
-	<label><input type="radio" name="level_action" value="set" id="level_action_set">{'set to'|@translate}</label>
-	<select onchange="document.getElementById('level_action_set').checked = true;" name="level" size="1">
-	  {html_options options=$level_options selected=$level_selected}
-	</select>
-      </td>
-    </tr>
-  </table>
-
-</fieldset>
-
-{* preference *}
-<fieldset>
-  <legend>{'Preferences'|@translate}</legend>
-
-<table>
-  <tr>
-    <td>{'Number of photos per page'|@translate}</td>
-    <td>
-      <label><input type="radio" name="nb_image_page_action" value="leave" checked="checked"> {'leave'|@translate}</label>
-      <label><input type="radio" name="nb_image_page_action" value="set" id="nb_image_page_action_set"> {'set to'|@translate}</label>
-      <input onmousedown="document.getElementById('nb_image_page_action_set').checked = true;"
-             size="4" maxlength="3" type="text" name="nb_image_page" value="{$NB_IMAGE_PAGE}">
-    </td>
-  </tr>
-
-  <tr>
-    <td>{'Interface theme'|@translate}</td>
-    <td>
-      <label><input type="radio" name="theme_action" value="leave" checked="checked"> {'leave'|@translate}</label>
-      <label><input type="radio" name="theme_action" value="set" id="theme_action_set"> {'set to'|@translate}</label>
-      <select onchange="document.getElementById('theme_action_set').checked = true;" name="theme" size="1">
+    </div>
+
+    {* enabled_high *}
+    <div id="action_enabled_high" class="bulkAction">
+      <label><input type="radio" name="enabled_high" value="true">{'Yes'|@translate}</label>
+      <label><input type="radio" name="enabled_high" value="false">{'No'|@translate}</label>
+    </div>
+
+    {* level *}
+    <div id="action_level" class="bulkAction">
+      <select name="level" size="1">
+        {html_options options=$level_options selected=$level_selected}
+      </select>
+    </div>
+
+    {* nb_image_page *}
+    <div id="action_nb_image_page" class="bulkAction">
+      <input size="4" maxlength="3" type="text" name="nb_image_page" value="{$NB_IMAGE_PAGE}">
+    </div>
+
+    {* theme *}
+    <div id="action_theme" class="bulkAction">
+      <select name="theme" size="1">
         {html_options options=$theme_options selected=$theme_selected}
       </select>
-    </td>
-  </tr>
-
-  <tr>
-    <td>{'Language'|@translate}</td>
-    <td>
-      <label><input type="radio" name="language_action" value="leave" checked="checked"> {'leave'|@translate}</label>
-      <label><input type="radio" name="language_action" value="set" id="language_action_set"> {'set to'|@translate}</label>
-      <select onchange="document.getElementById('language_action_set').checked = true;" name="language" size="1">
+    </div>
+
+    {* language *}
+    <div id="action_language" class="bulkAction">
+      <select name="language" size="1">
         {html_options options=$language_options selected=$language_selected}
       </select>
-    </td>
-  </tr>
-
-  <tr>
-    <td>{'Recent period'|@translate}</td>
-    <td>
-      <label><input type="radio" name="recent_period_action" value="leave" checked="checked"> {'leave'|@translate}</label>
-      <label><input type="radio" name="recent_period_action" value="set" id="recent_period_action_set"> {'set to'|@translate}</label>
-      <input onmousedown="document.getElementById('recent_period_action_set').checked = true;"
-             type="text" size="3" maxlength="2" name="recent_period" value="{$RECENT_PERIOD}">
-    </td>
-  </tr>
-
-  <tr>
-    <td>{'Expand all albums'|@translate}</td>
-    <td>
-      <label><input type="radio" name="expand" value="leave" checked="checked"> {'leave'|@translate}</label>
-      / {'set to'|@translate}
+    </div>
+
+    {* recent_period *}
+    <div id="action_recent_period" class="bulkAction">
+      <input type="text" size="3" maxlength="2" name="recent_period" value="{$RECENT_PERIOD}">
+    </div>
+
+    {* expand *}
+    <div id="action_expand" class="bulkAction">
       <label><input type="radio" name="expand" value="true">{'Yes'|@translate}</label>
       <label><input type="radio" name="expand" value="false">{'No'|@translate}</label>
-    </td>
-  </tr>
-
-{if $ACTIVATE_COMMENTS}
-  <tr>
-    <td>{'Show number of comments'|@translate}</td>
-    <td>
-      <label><input type="radio" name="show_nb_comments" value="leave" checked="checked"> {'leave'|@translate}</label>
-      / {'set to'|@translate}
+    </div>
+
+    {* show_nb_comments *}
+    <div id="action_show_nb_comments" class="bulkAction">
       <label><input type="radio" name="show_nb_comments" value="true">{'Yes'|@translate}</label>
       <label><input type="radio" name="show_nb_comments" value="false">{'No'|@translate}</label>
-    </td>
-  </tr>
-{/if}
-
-  <tr>
-    <td>{'Show number of hits'|@translate}</td>
-    <td>
-      <label><input type="radio" name="show_nb_hits" value="leave" checked="checked"> {'leave'|@translate}</label>
-      / {'set to'|@translate}
+    </div>
+
+    {* show_nb_hits *}
+    <div id="action_show_nb_hits" class="bulkAction">
       <label><input type="radio" name="show_nb_hits" value="true">{'Yes'|@translate}</label>
       <label><input type="radio" name="show_nb_hits" value="false">{'No'|@translate}</label>
-    </td>
-  </tr>
-
-</table>
-
+    </div>
+
+    <p id="applyActionBlock" style="display:none" class="actionButtons">
+      <input id="applyAction" class="submit" type="submit" value="{'Apply action'|@translate}" name="submit"> <span id="applyOnDetails"></span>
+      <span id="applyActionLoading" style="display:none"><img src="themes/default/images/ajax-loader-small.gif"></span>
+      <span class="infos" style="display:none">&#x2714; Users modified</span>
+    </p>
+
+  </div> {* #permitAction *}
 </fieldset>
 
-<p>
-  {'target'|@translate}
-  <label><input type="radio" name="target" value="all"> {'all'|@translate}</label>
-  <label><input type="radio" name="target" value="selection" checked="checked"> {'selection'|@translate}</label>
-</p>
-
-<p>
-  <input class="submit" type="submit" value="{'Submit'|@translate}" name="pref_submit">
-  <input class="submit" type="reset" value="{'Reset'|@translate}" name="pref_reset">
-</p>
-
-</form>
-
-<script type="text/javascript">// <![CDATA[{literal}
-jQuery("form:last").submit( function() {
-	if ( jQuery("input[name=target][value=selection]:checked", this).length > 0 )
-	if ( jQuery("input[name='selection[]']:checked", this).length == 0)
-	{
-		alert( {/literal}"{'Select at least one user'|@translate|escape:javascript}"{literal} );
-		return false;
-	}
-	return true;
-}
-);{/literal}
-// ]]>
-</script>
- 
+</form> 
Index: /trunk/admin/user_list.php
===================================================================
--- /trunk/admin/user_list.php	(revision 25193)
+++ /trunk/admin/user_list.php	(revision 25194)
@@ -27,490 +27,8 @@
 
 // +-----------------------------------------------------------------------+
-// |                              functions                                |
-// +-----------------------------------------------------------------------+
-
-/**
- * returns a list of users depending on page filters (in $_GET)
- *
- * Each user comes with his related informations : id, username, mail
- * address, list of groups.
- *
- * @return array
- */
-function get_filtered_user_list()
-{
-  global $conf, $page;
-
-  $users = array();
-
-  // filter
-  $filter = array();
-
-  if (isset($_GET['username']) and !empty($_GET['username']))
-  {
-    $username = str_replace('*', '%', $_GET['username']);
-    $filter['username'] = pwg_db_real_escape_string($username);
-  }
-
-  if (isset($_GET['group'])
-      and -1 != $_GET['group']
-      and is_numeric($_GET['group']))
-  {
-    $filter['group'] = $_GET['group'];
-  }
-
-  if (isset($_GET['status'])
-      and in_array($_GET['status'], get_enums(USER_INFOS_TABLE, 'status')))
-  {
-    $filter['status'] = $_GET['status'];
-  }
-
-  // how to order the list?
-  $order_by = 'id';
-  if (isset($_GET['order_by'])
-      and in_array($_GET['order_by'], array_keys($page['order_by_items'])))
-  {
-    $order_by = $_GET['order_by'];
-  }
-
-  $direction = 'ASC';
-  if (isset($_GET['direction'])
-      and in_array($_GET['direction'], array_keys($page['direction_items'])))
-  {
-    $direction = strtoupper($_GET['direction']);
-  }
-
-  // search users depending on filters and order
-  $query = '
-SELECT DISTINCT u.'.$conf['user_fields']['id'].' AS id,
-                u.'.$conf['user_fields']['username'].' AS username,
-                u.'.$conf['user_fields']['email'].' AS email,
-                ui.status,
-                ui.enabled_high,
-                ui.level
-  FROM '.USERS_TABLE.' AS u
-    INNER JOIN '.USER_INFOS_TABLE.' AS ui
-      ON u.'.$conf['user_fields']['id'].' = ui.user_id
-    LEFT JOIN '.USER_GROUP_TABLE.' AS ug
-      ON u.'.$conf['user_fields']['id'].' = ug.user_id
-  WHERE u.'.$conf['user_fields']['id'].' > 0';
-  if (isset($filter['username']))
-  {
-    $query.= '
-  AND u.'.$conf['user_fields']['username'].' LIKE \''.$filter['username'].'\'';
-  }
-  if (isset($filter['group']))
-  {
-    $query.= '
-    AND ug.group_id = '.$filter['group'];
-  }
-  if (isset($filter['status']))
-  {
-    $query.= '
-    AND ui.status = \''.$filter['status']."'";
-  }
-  $query.= '
-  ORDER BY '.$order_by.' '.$direction.'
-;';
-
-  $result = pwg_query($query);
-  while ($row = pwg_db_fetch_assoc($result))
-  {
-    $user = $row;
-    $user['groups'] = array();
-
-    $users[] = $user;
-  }
-
-  // add group lists
-  $user_ids = array();
-  foreach ($users as $i => $user)
-  {
-    $user_ids[$i] = $user['id'];
-  }
-  $user_nums = array_flip($user_ids);
-
-  if (count($user_ids) > 0)
-  {
-    $query = '
-SELECT user_id, group_id
-  FROM '.USER_GROUP_TABLE.'
-  WHERE user_id IN ('.implode(',', $user_ids).')
-;';
-    $result = pwg_query($query);
-    while ($row = pwg_db_fetch_assoc($result))
-    {
-      $users[ $user_nums[ $row['user_id'] ] ]['groups'][] = $row['group_id'];
-    }
-  }
-
-  return $users;
-}
-
-// +-----------------------------------------------------------------------+
-// |                           initialization                              |
-// +-----------------------------------------------------------------------+
-
-if (!defined('PHPWG_ROOT_PATH'))
-{
-  die('Hacking attempt!');
-}
-
-include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
-
-// +-----------------------------------------------------------------------+
-// | Check Access and exit when user status is not ok                      |
-// +-----------------------------------------------------------------------+
-check_status(ACCESS_ADMINISTRATOR);
-
-$page['order_by_items'] = array(
-  'id' => l10n('registration date'),
-  'username' => l10n('Username'),
-  'level' => l10n('Privacy level'),
-  'Language' => l10n('Language'),
-  'email' => l10n('Email address'),
-  );
-
-$page['direction_items'] = array(
-  'asc' => l10n('ascending'),
-  'desc' => l10n('descending')
-  );
-
-// +-----------------------------------------------------------------------+
-// |                              add a user                               |
-// +-----------------------------------------------------------------------+
-
-// Check for config_default var - If True : Using double password type else single password type
-// This feature is discussed on Piwigo's english forum
-if ($conf['double_password_type_in_admin'] == true)
-{
-  if (isset($_POST['submit_add']))
-  {
-    if(empty($_POST['password']))
-    {
-      $page['errors'][] = l10n('Password is missing. Please enter the password.');
-    }
-    else if(empty($_POST['password_conf']))
-    {
-      $page['errors'][] = l10n('Password confirmation is missing. Please confirm the chosen password.');
-    }
-    else if ($_POST['password'] != $_POST['password_conf'])
-    {
-      $page['errors'][] = l10n('The passwords do not match');
-    }
-    else
-    {
-      register_user($_POST['login'],
-                    $_POST['password'],
-                    $_POST['email'],
-                    false,
-                    $page['errors']);
-
-      if (count($page['errors']) == 0)
-      {
-        $page['infos'][] = l10n('user "%s" added', $_POST['login']);
-      }
-    }
-  }
-}
-else if ($conf['double_password_type_in_admin'] == false)
-{
-  if (isset($_POST['submit_add']))
-  {
-    register_user($_POST['login'],
-                  $_POST['password'],
-                  $_POST['email'],
-                  false,
-                  $page['errors']);
-
-    if (count($page['errors']) == 0)
-    {
-      $page['infos'][] = l10n('user "%s" added', stripslashes($_POST['login']));
-    }
-  }
-}
-
-// email notification
-if ( 
-  isset($_POST['submit_add']) 
-  and count($page['errors']) == 0  
-  and !empty($_POST['email']) 
-  and isset($_POST['send_password_by_mail']) 
-  )
-{
-  include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
-        
-  $keyargs_content = array(
-    get_l10n_args('Hello %s,', $_POST['login']),
-    get_l10n_args('Thank you for registering at %s!', $conf['gallery_title']),
-    get_l10n_args('', ''),
-    get_l10n_args('Here are your connection settings', ''),
-    get_l10n_args('Username: %s', $_POST['login']),
-    get_l10n_args('Password: %s', $_POST['password']),
-    get_l10n_args('Email: %s', $_POST['email']),
-    get_l10n_args('', ''),
-    get_l10n_args('If you think you\'ve received this email in error, please contact us at %s', get_webmaster_mail_address()),
-    );
-    
-  pwg_mail(
-    $_POST['email'],
-    array(
-      'subject' => '['.$conf['gallery_title'].'] '.l10n('Registration'),
-      'content' => l10n_args($keyargs_content),
-      'content_format' => 'text/plain',
-      )
-    );
-}
-
-// +-----------------------------------------------------------------------+
-// |                               user list                               |
-// +-----------------------------------------------------------------------+
-
-$page['filtered_users'] = get_filtered_user_list();
-
-// +-----------------------------------------------------------------------+
-// |                            selected users                             |
-// +-----------------------------------------------------------------------+
-
-if (isset($_POST['delete']) or isset($_POST['pref_submit']))
-{
-  $collection = array();
-
-  switch ($_POST['target'])
-  {
-    case 'all' :
-    {
-      foreach($page['filtered_users'] as $local_user)
-      {
-        $collection[] = $local_user['id'];
-      }
-      break;
-    }
-    case 'selection' :
-    {
-      if (isset($_POST['selection']))
-      {
-        $collection = $_POST['selection'];
-      }
-      break;
-    }
-  }
-
-  if (count($collection) == 0)
-  {
-    $page['errors'][] = l10n('Select at least one user');
-  }
-}
-
-// +-----------------------------------------------------------------------+
-// |                             delete users                              |
-// +-----------------------------------------------------------------------+
-if (isset($_POST['delete']) and count($collection) > 0)
-{
-  if (in_array($conf['guest_id'], $collection))
-  {
-    $page['errors'][] = l10n('Guest cannot be deleted');
-  }
-  if (($conf['guest_id'] != $conf['default_user_id']) and
-      in_array($conf['default_user_id'], $collection))
-  {
-    $page['errors'][] = l10n('Default user cannot be deleted');
-  }
-  if (in_array($conf['webmaster_id'], $collection))
-  {
-    $page['errors'][] = l10n('Webmaster cannot be deleted');
-  }
-  if (in_array($user['id'], $collection))
-  {
-    $page['errors'][] = l10n('You cannot delete your account');
-  }
-
-  if (count($page['errors']) == 0)
-  {
-    if (isset($_POST['confirm_deletion']) and 1 == $_POST['confirm_deletion'])
-    {
-      foreach ($collection as $user_id)
-      {
-        delete_user($user_id);
-      }
-      
-      $page['infos'][] = l10n_dec(
-        '%d user deleted', '%d users deleted',
-        count($collection)
-        );
-      
-      foreach ($page['filtered_users'] as $filter_key => $filter_user)
-      {
-        if (in_array($filter_user['id'], $collection))
-        {
-          unset($page['filtered_users'][$filter_key]);
-        }
-      }
-    }
-    else
-    {
-      $page['errors'][] = l10n('You need to confirm deletion');
-    }
-  }
-}
-
-// +-----------------------------------------------------------------------+
-// |                       preferences form submission                     |
-// +-----------------------------------------------------------------------+
-
-if (isset($_POST['pref_submit']) and count($collection) > 0)
-{
-  if (-1 != $_POST['associate'])
-  {
-    $datas = array();
-
-    $query = '
-SELECT user_id
-  FROM '.USER_GROUP_TABLE.'
-  WHERE group_id = '.$_POST['associate'].'
-;';
-    $associated = array_from_query($query, 'user_id');
-
-    $associable = array_diff($collection, $associated);
-
-    if (count($associable) > 0)
-    {
-      foreach ($associable as $item)
-      {
-        $datas[] = array(
-          'group_id' => $_POST['associate'],
-          'user_id' => $item
-          );
-      }
-
-      mass_inserts(USER_GROUP_TABLE,
-                   array('group_id', 'user_id'),
-                   $datas);
-    }
-  }
-
-  if (-1 != $_POST['dissociate'])
-  {
-    $query = '
-DELETE FROM '.USER_GROUP_TABLE.'
-  WHERE group_id = '.$_POST['dissociate'].'
-  AND user_id IN ('.implode(',', $collection).')
-';
-    pwg_query($query);
-  }
-
-  // properties to set for the collection (a user list)
-  $datas = array();
-  $dbfields = array('primary' => array('user_id'), 'update' => array());
-
-  $formfields = array(
-          'nb_image_page', 'theme', 'language',
-          'recent_period', 'expand', 'show_nb_hits', 
-          'status', 'enabled_high', 'level'
-          );
-  
-  $true_false_fields = array('expand', 'show_nb_hits', 'enabled_high');
-  
-  if ($conf['activate_comments'])
-  {
-    $formfields[] = 'show_nb_comments';
-    $true_false_fields[] = 'show_nb_comments';
-  }
-
-  foreach ($formfields as $formfield)
-  {
-    // special for true/false fields
-    if (in_array($formfield, $true_false_fields))
-    {
-      $test = $formfield;
-    }
-    else
-    {
-      $test = $formfield.'_action';
-    }
-
-    if ($_POST[$test] != 'leave')
-    {
-      $dbfields['update'][] = $formfield;
-    }
-  }
-
-  // updating elements is useful only if needed...
-  if (count($dbfields['update']) > 0)
-  {
-    $datas = array();
-
-    foreach ($collection as $user_id)
-    {
-      $data = array();
-      $data['user_id'] = $user_id;
-
-      // TODO : verify if submited values are semanticaly correct
-      foreach ($dbfields['update'] as $dbfield)
-      {
-        // if the action is 'unset', the key won't be in row and
-        // mass_updates function will set this field to NULL
-        if (in_array($dbfield, $true_false_fields)
-            or 'set' == $_POST[$dbfield.'_action'])
-        {
-          $data[$dbfield] = $_POST[$dbfield];
-        }
-      }
-
-      // if the status is getting greater or equal to "admin", then level
-      // automatically switches to "admin" (8), unless the level is also
-      // defined in the same batch action.
-      if (isset($data['status']) and in_array($data['status'], array('webmaster', 'admin')))
-      {
-        if (!isset($data['level']))
-        {
-          $data['level'] = 8;
-          if (!in_array('level', $dbfields['update']))
-          {
-            $dbfields['update'][] = 'level';
-          }
-        }
-      }
-
-      // special users checks
-      if
-        (
-          ($conf['webmaster_id'] == $user_id) or
-          ($conf['guest_id'] == $user_id) or
-          ($conf['default_user_id'] == $user_id)
-        )
-      {
-        // status must not be changed
-        if (isset($data['status']))
-        {
-          if ($conf['webmaster_id'] == $user_id)
-          {
-            $data['status'] = 'webmaster';
-          }
-          else
-          {
-            $data['status'] = 'guest';
-          }
-        }
-      }
-
-      $datas[] = $data;
-    }
-
-    mass_updates(USER_INFOS_TABLE, $dbfields, $datas);
-  }
-
-  redirect(
-    get_root_url().
-    'admin.php'.
-    get_query_string_diff(array(), false)
-    );
-}
-
-// +-----------------------------------------------------------------------+
 // |                              groups list                              |
 // +-----------------------------------------------------------------------+
 
-$groups[-1] = '------------';
+$groups = array();
 
 $query = '
@@ -527,87 +45,51 @@
 
 // +-----------------------------------------------------------------------+
-// |                             template init                             |
+// | template                                                              |
 // +-----------------------------------------------------------------------+
 
 $template->set_filenames(array('user_list'=>'user_list.tpl'));
 
-$base_url = PHPWG_ROOT_PATH.'admin.php?page=user_list';
+$query = '
+SELECT
+    DISTINCT u.'.$conf['user_fields']['id'].' AS id,
+    u.'.$conf['user_fields']['username'].' AS username,
+    u.'.$conf['user_fields']['email'].' AS email,
+    ui.status,
+    ui.enabled_high,
+    ui.level
+  FROM '.USERS_TABLE.' AS u
+    INNER JOIN '.USER_INFOS_TABLE.' AS ui ON u.'.$conf['user_fields']['id'].' = ui.user_id
+  WHERE u.'.$conf['user_fields']['id'].' > 0
+;';
 
-if (isset($_GET['start']) and is_numeric($_GET['start']))
+$result = pwg_query($query);
+while ($row = pwg_db_fetch_assoc($result))
 {
-  $start = $_GET['start'];
-}
-else
-{
-  $start = 0;
+  $users[] = $row;
+  $user_ids[] = $row['id'];
 }
 
 $template->assign(
   array(
-    'U_HELP' => get_root_url().'admin/popuphelp.php?page=user_list',
+    'users' => $users,
+    'all_users' => join(',', $user_ids),
+    )
+  );
 
-    'F_ADD_ACTION' => $base_url,
-    'F_USERNAME' => @htmlentities($_GET['username'], ENT_COMPAT, 'UTF-8'),
-    'F_FILTER_ACTION' => get_root_url().'admin.php',
-    
-    'ACTIVATE_COMMENTS' => $conf['activate_comments'],
-    ));
+// echo '<pre>'; print_r($users); echo '</pre>';
 
-// Display or Hide double password type
-$template->assign('Double_Password', $conf['double_password_type_in_admin'] );
+$default_user = get_default_user_info(true);
 
-// Filter status options
-$status_options[-1] = '------------';
-foreach (get_enums(USER_INFOS_TABLE, 'status') as $status)
-{
-  $status_options[$status] = l10n('user_status_'.$status);
-}
-$template->assign('status_options', $status_options);
-$template->assign('status_selected',
-    isset($_GET['status']) ? $_GET['status'] : '');
-
-// Filter group options
-$template->assign('group_options', $groups);
-$template->assign('group_selected',
-    isset($_GET['group']) ? $_GET['group'] : '');
-
-// Filter order options
-$template->assign('order_options', $page['order_by_items']);
-$template->assign('order_selected',
-    isset($_GET['order_by']) ? $_GET['order_by'] : '');
-
-// Filter direction options
-$template->assign('direction_options', $page['direction_items']);
-$template->assign('direction_selected',
-    isset($_GET['direction']) ? $_GET['direction'] : '');
-
-
-if (isset($_POST['pref_submit']))
-{
-  $template->assign(
-    array(
-      'NB_IMAGE_PAGE' => $_POST['nb_image_page'],
-      'RECENT_PERIOD' => $_POST['recent_period'],
-      ));
-}
-else
-{
-  $default_user = get_default_user_info(true);
-  $template->assign(
-    array(
-      'NB_IMAGE_PAGE' => $default_user['nb_image_page'],
-      'RECENT_PERIOD' => $default_user['recent_period'],
-      ));
-}
-
-// Template Options
-$template->assign('theme_options', get_pwg_themes());
-$template->assign('theme_selected',
-    isset($_POST['pref_submit']) ? $_POST['theme'] : get_default_theme());
-
-// Language options
-$template->assign('language_options', get_languages());
-$template->assign('language_selected',
-    isset($_POST['pref_submit']) ? $_POST['language'] : get_default_language());
+$template->assign(
+  array(
+    'NB_IMAGE_PAGE' => $default_user['nb_image_page'],
+    'RECENT_PERIOD' => $default_user['recent_period'],
+    'theme_options' => get_pwg_themes(),
+    'theme_selected' => get_default_theme(),
+    'language_options' => get_languages(),
+    'language_selected' => get_default_language(),
+    'association_options' => $groups,
+    )
+  );
 
 // Status options
@@ -621,14 +103,5 @@
 }
 $template->assign('pref_status_options', $pref_status_options);
-$template->assign('pref_status_selected',
-    isset($_POST['pref_submit']) ? $_POST['status'] : 'normal');
-
-// associate and dissociate options
-$template->assign('association_options', $groups);
-$template->assign('associate_selected',
-    isset($_POST['pref_submit']) ? $_POST['associate'] : '');
-$template->assign('dissociate_selected',
-    isset($_POST['pref_submit']) ? $_POST['dissociate'] : '');
-
+$template->assign('pref_status_selected', 'normal');
 
 // user level options
@@ -638,104 +111,9 @@
 }
 $template->assign('level_options', $level_options);
-$template->assign('level_selected',
-    isset($_POST['pref_submit']) ? $_POST['level'] : $default_user['level']);
+$template->assign('level_selected', $default_user['level']);
+
 
 // +-----------------------------------------------------------------------+
-// |                            navigation bar                             |
-// +-----------------------------------------------------------------------+
-
-$url = PHPWG_ROOT_PATH.'admin.php'.get_query_string_diff(array('start'));
-
-$navbar = create_navigation_bar(
-  $url,
-  count($page['filtered_users']),
-  $start,
-  $conf['users_page']
-  );
-
-$template->assign('navbar', $navbar);
-
-// +-----------------------------------------------------------------------+
-// |                               user list                               |
-// +-----------------------------------------------------------------------+
-
-$profile_url = get_root_url().'admin.php?page=profile&amp;user_id=';
-$perm_url = get_root_url().'admin.php?page=user_perm&amp;user_id=';
-
-$visible_user_list = array();
-foreach ($page['filtered_users'] as $num => $local_user)
-{
-  // simulate LIMIT $start, $conf['users_page']
-  if ($num < $start)
-  {
-    continue;
-  }
-  if ($num >= $start + $conf['users_page'])
-  {
-    break;
-  }
-
-  $visible_user_list[] = $local_user;
-}
-
-// allow plugins to fill template var plugin_user_list_column_titles and
-// plugin_columns/plugin_actions for each user in the list
-$visible_user_list = trigger_event('loc_visible_user_list', $visible_user_list);
-
-foreach ($visible_user_list as $local_user)
-{
-  $groups_string = preg_replace(
-    '/(\d+)/e',
-    "\$groups['$1']",
-    implode(
-      ', ',
-      $local_user['groups']
-      )
-    );
-
-  if (isset($_POST['pref_submit'])
-      and isset($_POST['selection'])
-      and in_array($local_user['id'], $_POST['selection']))
-  {
-    $checked = 'checked="checked"';
-  }
-  else
-  {
-    $checked = '';
-  }
-
-  $properties = array();
-  if ( $local_user['level'] != 0 )
-  {
-    $properties[] = l10n( sprintf('Level %d', $local_user['level']) );
-  }
-  $properties[] =
-    (isset($local_user['enabled_high']) and ($local_user['enabled_high'] == 'true'))
-        ? l10n('High definition') : l10n('');
-
-  $template->append(
-    'users',
-    array(
-      'ID' => $local_user['id'],
-      'CHECKED' => $checked,
-      'U_PROFILE' => $profile_url.$local_user['id'],
-      'U_PERM' => $perm_url.$local_user['id'],
-      'USERNAME' => stripslashes($local_user['username'])
-        .($local_user['id'] == $conf['guest_id']
-          ? '<br>['.l10n('guest').']' : '')
-        .($local_user['id'] == $conf['default_user_id']
-          ? '<br>['.l10n('default values').']' : ''),
-      'STATUS' => l10n('user_status_'.$local_user['status']),
-      'EMAIL' => get_email_address_as_display_text($local_user['email']),
-      'GROUPS' => $groups_string,
-      'PROPERTIES' => implode( ', ', $properties),
-      'plugin_columns' => isset($local_user['plugin_columns']) ? $local_user['plugin_columns'] : array(),
-      'plugin_actions' => isset($local_user['plugin_actions']) ? $local_user['plugin_actions'] : array(),
-      )
-    );
-}
-
-// +-----------------------------------------------------------------------+
-// |                           html code display                           |
+// | html code display                                                     |
 // +-----------------------------------------------------------------------+
 
Index: /trunk/admin/user_list_backend.php
===================================================================
--- /trunk/admin/user_list_backend.php	(revision 25194)
+++ /trunk/admin/user_list_backend.php	(revision 25194)
@@ -0,0 +1,158 @@
+<?php
+define('PHPWG_ROOT_PATH','../');
+define('IN_ADMIN', true);
+
+include_once(PHPWG_ROOT_PATH.'include/common.inc.php');
+	
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Easy set variables
+ */
+	
+/* Array of database columns which should be read and sent back to DataTables. Use a space where
+ * you want to insert a non-database field (for example a counter or static image)
+ */
+$aColumns = array('id', 'username', 'status', 'mail_address');
+	
+/* Indexed column (used for fast and accurate table cardinality) */
+$sIndexColumn = "id";
+	
+/* DB table to use */
+$sTable = USERS_TABLE.' INNER JOIN '.USER_INFOS_TABLE.' AS ui ON id = ui.user_id';
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * If you just want to use the basic configuration for DataTables with PHP server-side, there is
+ * no need to edit below this line
+ */
+
+/* 
+ * Paging
+ */
+$sLimit = "";
+if ( isset( $_GET['iDisplayStart'] ) && $_GET['iDisplayLength'] != '-1' )
+{
+  $sLimit = "LIMIT ".pwg_db_real_escape_string( $_GET['iDisplayStart'] ).", ".
+    pwg_db_real_escape_string( $_GET['iDisplayLength'] );
+}
+	
+	
+/*
+ * Ordering
+ */
+if ( isset( $_GET['iSortCol_0'] ) )
+{
+  $sOrder = "ORDER BY  ";
+  for ( $i=0 ; $i<intval( $_GET['iSortingCols'] ) ; $i++ )
+  {
+    if ( $_GET[ 'bSortable_'.intval($_GET['iSortCol_'.$i]) ] == "true" )
+    {
+      $sOrder .= $aColumns[ intval( $_GET['iSortCol_'.$i] ) ]."
+				 	".pwg_db_real_escape_string( $_GET['sSortDir_'.$i] ) .", ";
+    }
+  }
+		
+  $sOrder = substr_replace( $sOrder, "", -2 );
+  if ( $sOrder == "ORDER BY" )
+  {
+    $sOrder = "";
+  }
+}
+	
+	
+/* 
+ * Filtering
+ * NOTE this does not match the built-in DataTables filtering which does it
+ * word by word on any field. It's possible to do here, but concerned about efficiency
+ * on very large tables, and MySQL's regex functionality is very limited
+ */
+$sWhere = "";
+if ( $_GET['sSearch'] != "" )
+{
+  $sWhere = "WHERE (";
+  for ( $i=0 ; $i<count($aColumns) ; $i++ )
+  {
+    $sWhere .= $aColumns[$i]." LIKE '%".pwg_db_real_escape_string( $_GET['sSearch'] )."%' OR ";
+  }
+  $sWhere = substr_replace( $sWhere, "", -3 );
+  $sWhere .= ')';
+}
+	
+/* Individual column filtering */
+for ( $i=0 ; $i<count($aColumns) ; $i++ )
+{
+  if ( $_GET['bSearchable_'.$i] == "true" && $_GET['sSearch_'.$i] != '' )
+  {
+    if ( $sWhere == "" )
+    {
+      $sWhere = "WHERE ";
+    }
+    else
+    {
+      $sWhere .= " AND ";
+    }
+    $sWhere .= $aColumns[$i]." LIKE '%".pwg_db_real_escape_string($_GET['sSearch_'.$i])."%' ";
+  }
+}
+	
+	
+/*
+ * SQL queries
+ * Get data to display
+ */
+$sQuery = "
+		SELECT SQL_CALC_FOUND_ROWS ".str_replace(" , ", " ", implode(", ", $aColumns))."
+		FROM   $sTable
+		$sWhere
+		$sOrder
+		$sLimit
+	";
+$rResult = pwg_query($sQuery);
+	
+/* Data set length after filtering */
+$sQuery = "
+		SELECT FOUND_ROWS()
+	";
+$rResultFilterTotal = pwg_query($sQuery);
+$aResultFilterTotal = pwg_db_fetch_array($rResultFilterTotal);
+$iFilteredTotal = $aResultFilterTotal[0];
+	
+/* Total data set length */
+$sQuery = "
+		SELECT COUNT(".$sIndexColumn.")
+		FROM   $sTable
+	";
+$rResultTotal = pwg_query($sQuery);
+$aResultTotal = pwg_db_fetch_array($rResultTotal);
+$iTotal = $aResultTotal[0];
+	
+	
+/*
+ * Output
+ */
+$output = array(
+  "sEcho" => intval($_GET['sEcho']),
+  "iTotalRecords" => $iTotal,
+  "iTotalDisplayRecords" => $iFilteredTotal,
+  "aaData" => array()
+	);
+	
+while ( $aRow = pwg_db_fetch_array( $rResult ) )
+{
+  $row = array();
+  for ( $i=0 ; $i<count($aColumns) ; $i++ )
+  {
+    if ( $aColumns[$i] == "version" )
+    {
+      /* Special output formatting for 'version' column */
+      $row[] = ($aRow[ $aColumns[$i] ]=="0") ? '-' : $aRow[ $aColumns[$i] ];
+    }
+    else if ( $aColumns[$i] != ' ' )
+    {
+      /* General output */
+      $row[] = $aRow[ $aColumns[$i] ];
+    }
+  }
+  $output['aaData'][] = $row;
+}
+	
+echo json_encode( $output );
+?>
