Index: l10n_community/l10n_community.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/l10n_server/l10n_community/Attic/l10n_community.css,v
retrieving revision 1.1.2.10.2.4
diff -u -p -u -p -r1.1.2.10.2.4 l10n_community.css
--- l10n_community/l10n_community.css	16 Aug 2009 15:00:51 -0000	1.1.2.10.2.4
+++ l10n_community/l10n_community.css	7 Sep 2009 14:34:09 -0000
@@ -47,18 +47,11 @@ div.admin form {
 }
 
 /* String search filtering form */
-#l10n-community-filter-form {
+#l10n-community-filter-form,
+#l10n-community-moderate-filter-form {
   white-space:nowrap;
 }
 
-#l10n-community-filter-form a.permalink {
-  font-size:90%;
-}
-
-#l10n-community-filter-form .form-text {
-  width: 10em !important;
-}
-
 /* Emphasized replaceable parts of translatables */
 em.l10n-community-marker {
   font-weight: bold;
Index: l10n_community/l10n_community.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/l10n_server/l10n_community/Attic/l10n_community.module,v
retrieving revision 1.1.2.23.2.45
diff -u -p -u -p -r1.1.2.23.2.45 l10n_community.module
--- l10n_community/l10n_community.module	5 Sep 2009 12:18:39 -0000	1.1.2.23.2.45
+++ l10n_community/l10n_community.module	7 Sep 2009 14:34:10 -0000
@@ -235,6 +235,16 @@ function l10n_community_menu() {
     'type' => MENU_LOCAL_TASK,
     'weight' => -8,
   );
+  $items['translate/languages/%l10n_community_language/moderate'] = array(
+    'title' => 'Moderate',
+    'page callback' => 'l10n_community_moderate_page',
+    'page arguments' => array(2),
+    'file' => 'moderate.inc',
+    'access callback' => 'l10n_community_moderation_access',
+    'access arguments' => array(2),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => -6,
+  );
   $items['translate/languages/%l10n_community_language/import'] = array(
     'title' => 'Import',
     'page callback' => 'l10n_community_import_page',
@@ -343,6 +353,13 @@ function l10n_community_export_access() 
 }
 
 /**
+ * Access to the moderation screen.
+ */
+function l10n_community_moderation_access($langcode) {
+  return user_access('access localization community') && (l10n_community_get_permission($langcode) == L10N_PERM_ALL);
+}
+
+/**
  * Title callback for project pages.
  */
 function l10n_community_page_title_project($uri) {
@@ -1248,6 +1265,10 @@ function l10n_community_theme($existing,
     'l10n_community_admin_releases_form' => array(
       'arguments' => array('form' => NULL),
     ),
+    // moderate.inc
+    'l10n_community_moderation_form' => array(
+      'arguments' => array('form' => NULL),
+    ),
   );
 }
 
@@ -1323,6 +1344,22 @@ function theme_l10n_community_copy_butto
 }
 
 /**
+ * Format string for display. Takes plurals into account.
+ */
+function l10n_community_format_string($value, $rich_markup = TRUE) {
+  if (strpos($value, "\0") !== FALSE) {
+    $items = explode(chr(0), $value);
+    foreach ($items as &$item) {
+      $item = l10n_community_format_text($item, NULL, NULL, $rich_markup);
+    }
+    return theme('item_list', $items);
+  }
+  else {
+    return l10n_community_format_text($value, NULL, NULL, $rich_markup);
+  }
+}
+
+/**
  * Format translatable strings with custom icons.
  *
  * We emphasize some parts of strings, so those are easy to recognize.
@@ -1330,8 +1367,14 @@ function theme_l10n_community_copy_butto
  *
  * @param $string
  *   Source string to translate.
+ * @param $sid
+ *   Source string ID.
+ * @param $delta
+ *   Sequence ID of plural version if $string is a plural variant.
+ * @param $rich_markup
+ *   Whether to output rich markup (used for the translaton UI).
  */
-function l10n_community_format_text($string, $sid = NULL, $delta = NULL) {
+function l10n_community_format_text($string, $sid = NULL, $delta = NULL, $rich_markup = TRUE) {
   static $path = NULL, $title = NULL;
 
   if (!isset($path)) {
@@ -1339,30 +1382,32 @@ function l10n_community_format_text($str
     $title = t('line break');
   }
 
-  $original = check_plain($string);
-
   // Replace all newline chars in the string with an indicator image.
-  $string = str_replace(
+  $formatted = str_replace(
     array("\n", "\\\\n"),
     '<img src="'. $path .'/images/newline.png" alt="'. $title .'" title="'. $title .'" /><br />',
     check_plain($string)
   );
   // Make all %, ! and @ marked pladeholders emphasized.
-  $string = preg_replace(
+  $formatted = preg_replace(
     '~((%|!|@)[0-9a-zA-Z_-]+)~',
     '<em class="l10n-community-marker">\\1</em>',
-    $string
+    $formatted
   );
 
-  $class = '';
-  if (isset($sid) && isset($delta)) {
-    $class = ' class="string-'. $sid .'-'. $delta .'"';
+  if ($rich_markup) {
+    $class = '';
+    if (isset($sid) && isset($delta)) {
+      $class = ' class="string-'. $sid .'-'. $delta .'"';
+    }
+    else if ($sid) {
+      $class = ' class="string-'. $sid .'"';
+    }
+    return '<div'. $class .'><span class="string">'. $formatted .'</span><span class="original hidden">'. check_plain($string) .'</span></div>';
   }
-  else if ($sid) {
-    $class = ' class="string-'. $sid .'"';
+  else {
+    return '<span class="string">'. $formatted .'</span>';
   }
-
-  return '<div'. $class .'><span class="string">'. $string .'</span><span class="original hidden">'. $original .'</span></div>';
 }
 
 /**
Index: l10n_community/moderate.inc
===================================================================
RCS file: l10n_community/moderate.inc
diff -N l10n_community/moderate.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ l10n_community/moderate.inc	7 Sep 2009 14:34:10 -0000
@@ -0,0 +1,297 @@
+<?php
+// $Id: pages.inc,v 1.1.2.20.2.18 2009/09/01 15:08:59 goba Exp $
+
+/**
+ * @file
+ *   Moderation functionality or localization community.
+ */
+
+// = Admin screens =============================================================
+
+/**
+ * Moderation page menu callback.
+ *
+ * Displays a string approval page useful for bulk string operations.
+ * Permissions are handled by the menu system.
+ *
+ * @param $langcode
+ *   Language code, for example 'hu', 'pt-br', 'de', 'it'.
+ */
+function l10n_community_moderate_page($langcode) {
+  module_load_include('inc', 'l10n_community', 'translate');
+  $languages = l10n_community_get_languages();
+  $filters = l10n_community_build_filter_values($_GET, TRUE);
+
+  $output = drupal_get_form('l10n_community_filter_form', $filters, TRUE);
+  
+  $strings = l10n_community_get_suggestions($langcode, $filters);
+  if (!count($strings)) {
+    drupal_set_message(t('No strings found with this filter. Try adjusting the filter options.'));
+  }
+  else{
+    // For users with some permission, display the form.
+    drupal_set_title(t('Suggestions for @language', array('@language' => $languages[$langcode]->name)));
+    $output .= drupal_get_form('l10n_community_moderation_form', $strings, $languages[$langcode], $filters);
+  }
+  return $output;  
+}
+
+// == Moderation form ==========================================================
+
+/** 
+ * Translation web interface.
+ *
+ * @param $strings
+ *   Array of string objects to display.
+ * @param $language
+ *   Language object.
+ * @param $filters
+ *   Filters used to present this moderation view.
+ */
+function l10n_community_moderation_form(&$form_state, $strings = array(), $language = NULL, $filters) {
+  $form['pager'] = array(
+    '#value' => theme('pager', NULL, $filters['limit'], 0)
+  );
+  
+  // Keep language code in form for further reference.
+  $form['langcode'] = array(
+    '#type' => 'value',
+    '#value' => $language->language
+  );
+
+  // Operations.
+  $form['options'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Update options'),
+    '#prefix' => '<div class="container-inline">',
+    '#suffix' => '</div>',
+  );
+  $form['options']['approve'] = array(
+    '#type' => 'submit',
+    '#value' => t('Approve all selected'),
+  );
+  $form['options']['decline'] = array(
+    '#type' => 'submit',
+    '#value' => t('Decline all selected'),
+  );
+  
+  // All strings and suggestions
+  $form['strings'] = array(
+    '#tree' => TRUE,
+  );
+  foreach ($strings as $string) {
+    $form['strings']['tid'][$string->tid] = array(
+      '#type' => 'checkbox',
+    );
+    $form['strings']['sid'][$string->tid] = array(
+      '#type' => 'value', '#value' => $string->sid,
+    );
+    $form['strings']['source'][$string->tid] = array(
+      //'#type' => 'item',
+      '#value' => l10n_community_format_string($string->source, FALSE),
+    );
+    $form['strings']['translation'][$string->tid] = array(
+      //'#type' => 'item',
+      '#value' => l10n_community_format_string($string->translation, FALSE),
+    );    
+    $form['strings']['suggestion'][$string->tid] = array(
+      //'#type' => 'item',
+      '#value' => l10n_community_format_string($string->suggestion, FALSE),
+    );
+  }
+  return $form;
+}
+
+/**
+ * Moderation form submission callback.
+ */
+function l10n_community_moderation_form_submit($form, &$form_state) {
+  // Filter out unchecked translations.
+  $translations = array_filter($form_state['values']['strings']['tid']);
+  if (!empty($translations)) {
+    // Use these tids to get the list of sids.
+    $strings = array_intersect_key($form_state['values']['strings']['sid'], $translations);
+
+    // The tids are the keys of the array.
+    $tids = array_keys($translations);
+    // The strings array has tid => sid.
+    $sids = array_unique($strings);
+    if ($form_state['values']['op'] == t('Approve all selected')) {
+      l10n_community_moderate_approve_selected($form_state['values']['langcode'], $tids, $sids);
+    }
+    elseif ($form_state['values']['op'] == t('Decline all selected')) {
+      l10n_community_moderate_decline_selected($form_state['values']['langcode'], $tids, $sids);
+    }
+  }
+  else {
+    drupal_set_message(t('No suggestions selected for operation. Please select one or more suggestions to run the operation on.'), 'error');
+  }
+}
+
+
+// == API functions ============================================================
+
+/**
+ * Get suggestions based on some conditions.
+ *
+ * @param $langcode
+ *   Language code, for example 'hu', 'pt-br', 'de', 'it'.
+ * @param $filters
+ *   See l10n_community_get_strings().
+ * @return
+ *   An array of suggestion records from database.
+ */
+function l10n_community_get_suggestions($langcode, $filters) {
+  $join = $join_args = $where = $where_args = array();
+  $sql = $sql_count = '';
+
+  $select = "SELECT DISTINCT ts.tid, ts.sid, tt.translation, ts.translation AS suggestion, s.value AS source FROM {l10n_community_translation} ts";
+  $select_count = "SELECT COUNT(DISTINCT(ts.tid)) FROM {l10n_community_translation} ts";
+  $join[] = "INNER JOIN {l10n_community_string} s ON ts.sid = s.sid";
+  $where[] = "ts.is_suggestion = 1 AND ts.is_active = 1 AND ts.language = '%s'";
+  $where_args[] = $langcode;
+    
+  // Add submitted by condition
+  if (!empty($filters['author'])) {
+    $where[] = "ts.uid_entered = %d";
+    $where_args[] = $filters['author']->uid;
+  }
+  
+  // Restrict based on project or release.
+  $release = empty($filters['release']) || $filters['release'] === 'all' ? NULL : $filters['release'];
+  $project = $filters['project'];
+  if ($release || $project) {
+    $join[] = "INNER JOIN {l10n_community_line} l ON ts.sid = l.sid";
+    $join[] = "INNER JOIN {l10n_community_file} f ON f.fid = l.fid";    
+    // If we have a release we ignore the project
+    if ($release) {
+      // Release restriction.
+      $where_args[] = $release;
+      $where[] = 'f.rid = %d';
+    }  
+    elseif ($project) {
+      $join[] = "INNER JOIN {l10n_community_release} r ON f.rid = r.rid";
+      $where_args[] = "r.pid = %d";
+      $args[] = $project->pid;
+    }
+  }
+
+  // Search in the source or target (suggestion) strings.
+  if (!empty($filters['search'])) {
+    $where_args[] = $filters['search'];
+    $where_args[] = $filters['search'];
+    $where[] = "(s.value LIKE '%%%s%%' OR ts.translation LIKE '%%%s%%')";
+  }
+
+  // Restriction based on string status.
+  if ($filters['status'] & L10N_STATUS_UNTRANSLATED) {
+    $join[] = "LEFT JOIN {l10n_community_translation} tt ON tt.sid = ts.sid AND tt.is_suggestion = 0 AND tt.is_active = 1 AND tt.language = '%s'";
+    $join_args[] = $langcode;
+    $where[] = "(tt.tid IS NULL OR tt.translation = '')";
+  }
+  elseif ($filters['status'] & L10N_STATUS_TRANSLATED) {
+    $join[] = "INNER JOIN {l10n_community_translation} tt ON tt.sid = ts.sid AND tt.is_suggestion = 0 AND tt.is_active = 1 AND tt.language = '%s'";
+    $join_args[] = $langcode;
+  }
+  else {
+    $join[] = "LEFT JOIN {l10n_community_translation} tt ON tt.sid = ts.sid AND tt.is_suggestion = 0 AND tt.is_active = 1 AND tt.language = '%s'";
+    $join_args[] = $langcode;
+  }
+
+  // Build the queries
+  $sql_args = array_merge($join_args, $where_args);
+  $sql_where = implode(' ', $join) . ' WHERE ' . implode(' AND ', $where);
+  $sql = $select . ' ' . $sql_where;
+  $sql_count = $select_count . ' ' . $sql_where;
+
+  $strings = pager_query($sql, $filters['limit'], 0, $sql_count, $sql_args);
+  $result = array();
+  while ($string = db_fetch_object($strings)) {
+    $result[] = $string;
+  }
+  return $result;
+}
+
+/**
+ * Mass approve callback.
+ */
+function l10n_community_moderate_approve_selected($langcode, $tids, $strings) {
+  global $user;
+  
+  // In case we have more than one sugggestion for an sid, error.
+  $sids = array_unique($strings);
+  if (count($sids) < count($strings)) {
+    drupal_set_message(t('You cannot approve more than one suggestion per string.'), 'error');
+    return;
+  }
+  $tid_placeholders = db_placeholders($tids);
+  $sid_placeholders = db_placeholders($sids);
+  
+  // Mark existing translations and suggestions as inactive in this language.
+  $args = array_merge($sids, array($langcode));
+  db_query("UPDATE {l10n_community_translation} SET is_active = 0 WHERE sid IN ($sid_placeholders) AND language = '%s'", $args);
+  
+  // Remove placeholder translation record (which was there if
+  // first came suggestions, before an actual translation).
+  db_query("DELETE FROM {l10n_community_translation} WHERE sid IN ($sid_placeholders) AND translation = '' AND language = '%s'", $args);
+  
+  // Mark this exact suggestions as active, and set approval time.
+  $args = array_merge(array(time(), $user->uid), $tids);
+  db_query("UPDATE {l10n_community_translation} SET time_approved = %d, uid_approved = %d, has_suggestion = 0, is_suggestion = 0, is_active = 1 WHERE tid IN ($tid_placeholders)", $args);
+
+  drupal_set_message(format_plural(count($tids), 'A suggestion has been approved.', '@count suggestions have been approved.'));
+}
+
+/**
+ * Mass decline callback.
+ */
+function l10n_community_moderate_decline_selected($langcode, $tids, $sids) {
+  // We are not interested in duplicated sids.
+  $sids = array_unique($sids);
+  $tid_placeholders = db_placeholders($tids);
+  $sid_placeholders = db_placeholders($sids);
+
+  // Deactive the selected suggestions.
+  db_query("UPDATE {l10n_community_translation} SET is_active = 0 WHERE tid IN ($tid_placeholders)", $tids);
+  drupal_set_message(format_plural(db_affected_rows(), 'A suggestion has been declined.', '@count suggestions have been declined.'));
+  
+  // Update 'has suggestion' option for remaining string translations
+  $args = array_merge(array($langcode), $sids);
+  $result = db_query(
+    "SELECT tt.tid 
+     FROM {l10n_community_translation} tt 
+     LEFT JOIN {l10n_community_translation} ts ON tt.sid = ts.sid AND tt.language = ts.language AND ts.is_active = 1 AND ts.is_suggestion = 1 
+     WHERE tt.is_active = 1 AND tt.is_suggestion = 0 AND tt.has_suggestion = 1 AND tt.language = '%s' AND tt.sid IN ($sid_placeholders) 
+     GROUP BY tt.tid 
+     HAVING COUNT(ts.tid) = 0",
+    $args
+  );
+  while ($string = db_fetch_object($result)) {
+    db_query("UPDATE {l10n_community_translation} SET has_suggestion = 0 WHERE tid = %d", $string->tid);
+  }
+}
+
+// == Theme functions ==========================================================
+
+/**
+ * Theme the approval form
+ */
+function theme_l10n_community_moderation_form($form) {
+  $output = '';
+  $pager = isset($form['pager']) ? drupal_render($form['pager']) : '';
+  $output .= $pager;
+  $output .= drupal_render($form['options']);  
+  $header = array(theme('table_select_header_cell'), t('Suggestion'), t('Translation'), t('Source'));
+  foreach (element_children($form['strings']['tid']) as $tid) {
+    $rows[] = array(
+      drupal_render($form['strings']['tid'][$tid]),
+      drupal_render($form['strings']['suggestion'][$tid]),
+      drupal_render($form['strings']['translation'][$tid]),
+      array('class' => 'source', 'data' => drupal_render($form['strings']['source'][$tid])),
+    );
+  }
+  $output .= theme('table', $header, $rows);
+  $output .= $pager;
+  $output .= drupal_render($form);
+  return $output;
+}
Index: l10n_community/translate.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/l10n_server/l10n_community/Attic/translate.inc,v
retrieving revision 1.1.2.7.2.13
diff -u -p -u -p -r1.1.2.7.2.13 translate.inc
--- l10n_community/translate.inc	1 Sep 2009 15:48:28 -0000	1.1.2.7.2.13
+++ l10n_community/translate.inc	7 Sep 2009 14:34:10 -0000
@@ -30,35 +30,23 @@ function l10n_community_translate_page($
   $languages = l10n_community_get_languages();
   $perm = l10n_community_get_permission($langcode);
 
-  // Build filter values for the form generation.
-  $project = $release = NULL;
-  if (isset($_GET['project']) && ($project = l10n_community_get_projects(array('uri' => $_GET['project'])))) {
-    if (isset($_GET['release']) && ($releases = l10n_community_get_releases($project->uri)) && isset($releases[$_GET['release']])) {
-      // Allow to select this release, if belongs to current project.
-      $release = $_GET['release'];
-    }
-  }
-  // Thankfully L10N_STATUS_ALL is 0, so non-number bad data goes to no filter.
-  $status  = isset($_GET['status']) ? (int) $_GET['status'] : 0;
-  $search  = isset($_GET['search']) ? (string) $_GET['search'] : '';
-  $context = isset($_GET['context']) ? (string) $_GET['context'] : 'all';
+  $filters = l10n_community_build_filter_values($_GET);
+  $output = drupal_get_form('l10n_community_filter_form', $filters);
 
-  $output = drupal_get_form('l10n_community_filter_form', $project, $status, $release, $search, $context);
-
-  $strings = l10n_community_get_strings($languages[$langcode]->language, $project, $status, $release, $search, $context, 10);
+  $strings = l10n_community_get_strings($languages[$langcode]->language, $filters, $filters['limit']);
   if (!count($strings)) {
     drupal_set_message(t('No strings found with this filter. Try adjusting the filter options.'));
   }
   elseif ($perm == L10N_PERM_NONE || $mode == 'view') {
     // For users without permission to translate or suggest, display the view.
     drupal_set_title(t('@language translations', array('@language' => $languages[$langcode]->name)));
-    $output .= l10n_community_translate_view($strings, $languages[$langcode], $project);
+    $output .= l10n_community_translate_view($strings, $languages[$langcode], $filters);
   }
   else {
     // For users with some permission, display the form.
     drupal_add_js(drupal_get_path('module', 'l10n_community') .'/l10n_community.js');
     drupal_set_title(t('Translate to @language', array('@language' => $languages[$langcode]->name)));
-    $output .= drupal_get_form('l10n_community_translate_form', $strings, $languages[$langcode], $project, $perm);
+    $output .= drupal_get_form('l10n_community_translate_form', $strings, $languages[$langcode], $filters, $perm);
   }
   return $output;
 }
@@ -67,8 +55,13 @@ function l10n_community_translate_page($
 
 /**
  * Translate form filter.
+ *
+ * @param $filters
+ *   Array of filter options.
+ * @param $limited
+ *   Return limited set of filters (no suggestion filters).
  */
-function l10n_community_filter_form(&$form_state, $project = NULL, $status = L10N_STATUS_ALL, $release = 'all', $search  = '', $context = 'all') {
+function l10n_community_filter_form(&$form_state, $filters, $limited = FALSE) {
   $projects = l10n_community_get_projects();
 
   $translation_options = array(
@@ -84,15 +77,15 @@ function l10n_community_filter_form(&$fo
   
   $form['project'] = array(
     '#title' => t('Project'),
-    '#default_value' => isset($project) ? $project->title : '',
+    '#default_value' => isset($filters['project']) ? $filters['project']->title : '',
   );
   if (($count = count($projects)) <= 30) {
     // Select widget for 1-30 projects.
     $form['project']['#type'] = 'select';
     $form['project']['#options'] = array('' => t('All'));
-    foreach ($projects as $this_project) {
+    foreach ($projects as $project) {
       // Title used to conform to the autocomplete behavior.
-      $form['project']['#options'][$this_project->title] = $this_project->title;
+      $form['project']['#options'][$project->title] = $project->title;
     }
   }
   else {
@@ -100,20 +93,21 @@ function l10n_community_filter_form(&$fo
     $form['project'] += array(
       '#type' => 'textfield',
       '#autocomplete_path' => 'translate/projects/autocomplete',
+      '#size' => 10,
     );
   }
 
-  if (isset($project)) {
-    $releases = l10n_community_get_releases($project->uri);
+  if (isset($filters['project'])) {
+    $releases = l10n_community_get_releases($filters['project']->uri);
     $release_options = array('all' => t('All'));
-    foreach ($releases as $rid => $this_release) {
-      $release_options[$rid] = $this_release->title;
+    foreach ($releases as $rid => $release) {
+      $release_options[$rid] = $release->title;
     }
     $form['release'] = array(
       '#title' => t('Release'),
       '#type' => 'select',
       '#options' => $release_options,
-      '#default_value' => isset($release) ? $release : 'all',
+      '#default_value' => isset($filters['release']) ? $filters['release'] : 'all',
     );
   }
 
@@ -122,7 +116,7 @@ function l10n_community_filter_form(&$fo
      '#title' => t('Context'),
       '#type' => 'select',
       '#options' => array('all' => t('All')) + $contexts,
-      '#default_value' => $context,
+      '#default_value' => $filters['context'],
     );
   }
 
@@ -133,19 +127,39 @@ function l10n_community_filter_form(&$fo
   $form['status']['translation'] = array(
     '#type' => 'select',
     '#options' => $translation_options,
-    '#default_value' => $status & (L10N_STATUS_TRANSLATED | L10N_STATUS_UNTRANSLATED),
-  );
-  $form['status']['suggestion'] = array(
-    '#type' => 'select',
-    '#options' => $suggestion_options,
-    '#default_value' => $status & (L10N_STATUS_HAS_SUGGESTION | L10N_STATUS_NO_SUGGESTION),
+    '#default_value' => $filters['status'] & (L10N_STATUS_TRANSLATED | L10N_STATUS_UNTRANSLATED),
   );
+  if (!$limited) {
+    $form['status']['suggestion'] = array(
+      '#type' => 'select',
+      '#options' => $suggestion_options,
+      '#default_value' => $filters['status'] & (L10N_STATUS_HAS_SUGGESTION | L10N_STATUS_NO_SUGGESTION),
+    );
+  }
+
+  $form['author']= array(
+    '#type' => 'textfield',
+    '#title' => t('Submitted by'),
+    '#maxlength' => 60,
+    '#autocomplete_path' => 'user/autocomplete',
+    '#default_value' => isset($filters['author']) ? $filters['author']->name : '',
+    '#size' => 15,
+  );
 
   $form['search'] = array(
     '#title' => t('Contains'),
     '#type' => 'textfield',
-    '#default_value' => $search,
+    '#default_value' => $filters['search'],
+    '#size' => 20,
   );
+
+  $form['limit'] = array(
+    '#type' => 'select',
+    '#title' => t('Limit'),
+    '#default_value' => isset($filters['limit']) ? $filters['limit'] : 10,
+    '#options' => drupal_map_assoc(array(5, 10, 20, 30)),
+  );
+
   $form['submit'] = array(
     '#value' => t('Filter'),
     '#type' => 'submit',
@@ -169,32 +183,16 @@ function l10n_community_filter_form_subm
   }
 
   if ($form_state['values']['op'] == t('Filter')) {
-    // Validate incoming data. The project needs to be resolved by title,
-    // so that we redirect with the proper uri value. Other validations
-    // are just to make sure that the URL keeps being short, and the user
-    // is not tricked into thinking a certain filter is applied, while
-    // being invalid. These checks are done on input as well.
-    if (!empty($form_state['values']['project'])) {
-      if ($uri = l10n_community_project_uri_by_title($form_state['values']['project'])) {
-        // Remember proper internal name based on title.
-        $form_state['values']['project'] = $uri;
-        if (!empty($form_state['values']['release']) && !(($releases = l10n_community_get_releases($uri)) && isset($releases[$form_state['values']['release']]))) {
-          // Such a release was not found for this project. Unset.
-          unset($form_state['values']['release']);
-        }
-      }
-      else {
-        // If project not found, release data is cleared.
-        unset($form_state['values']['release']);
-      }
-    }
-    $form_state['values']['status'] = ((int) $form_state['values']['status']['translation']) | ((int) $form_state['values']['status']['suggestion']);
+    $filters = l10n_community_build_filter_values($form_state['values']);
+    // Replace some values by their string representation.
+    foreach (array('project' => 'uri', 'author' => 'name') as $name => $key) {
+      if (!empty($filters[$name])) {
+        $filters[$name] = $filters[$name]->$key;
+      }
+    }
 
     // Redirect keeping the relevant filters intact in the URL.
-    $form_state['redirect'] = array(
-      $_GET['q'],
-      l10n_community_build_filter_array($form_state['values'])
-    );
+    $form_state['redirect'] = array($_GET['q'], $filters);
   }
 }
 
@@ -207,22 +205,16 @@ function l10n_community_filter_form_subm
  *   Array of string objects to display on the page.
  * @param $language
  *   Language object corresponding to the page displayed.
- * @param $project
- *   Project object.
+ * @param $filters
+ *   Filters used to present this listing view.
  */
-function l10n_community_translate_view($strings = array(), $language = NULL, $project = NULL) {
+function l10n_community_translate_view($strings = array(), $language = NULL, $filters = array()) {
   $output = '';
   $rows = array();
   foreach ($strings as $string) {
     $row = array();
     // Source display
-    // Multiple source strings if we deal with plurals.
-    if (strpos($string->value, "\0") !== FALSE) {
-      $source = theme('item_list', array_map('l10n_community_format_text', explode(chr(0), $string->value)), '');
-    }
-    else {
-      $source = l10n_community_format_text($string->value);
-    }
+    $source = l10n_community_format_string($string->value);
     $source .= theme('l10n_community_in_context', $string);
     $row[] = array('data' => $source, 'class' => 'source');
 
@@ -246,9 +238,9 @@ function l10n_community_translate_view($
     }
     $rows[] = $row;
   }
-  $output .= theme('pager', NULL, 10, 0);
+  $output .= ($pager = theme('pager', NULL, $filters['limit'], 0));
   $output .= theme('table', array(t('Source Text'), t('Translations')), $rows, array('class' => 'l10n-server-translate'));
-  $output .= theme('pager', NULL, 10, 0);
+  $output .= $pager;
   $output = "<div id='l10n-community-translate-view'>". $output ."</div>";
   return $output;
 }
@@ -262,29 +254,30 @@ function l10n_community_translate_view($
  *   Array of string objects to display.
  * @param $language
  *   Language object.
- * @param $project
- *   Project object.
+ * @param $filters
+ *   Filters used to present this editing view.
  * @param $perm
  *   Community permission level of user watching the page.
  */
-function l10n_community_translate_form(&$form_state, $strings = array(), $language = NULL, $project = NULL, $perm = L10N_PERM_SUGGEST) {
+function l10n_community_translate_form(&$form_state, $strings = array(), $language = NULL, $filters = array(), $perm = L10N_PERM_SUGGEST) {
+
+  if (isset($_GET['page'])) {
+    // Ensure that we keep all filter values, even the page number, so
+    // after submission, the same page can be shown.
+    $filters['page'] = (int) $_GET['page'];
+  }
 
   $form = array(
     '#tree' => TRUE,
-    '#redirect' => array(
-      $_GET['q'],
-      // Ensure that we keep all filter values, even the page number, so
-      // after submission, the same page can be shown.
-      l10n_community_build_filter_array($_GET, TRUE)
-    )
+    '#redirect' => array($_GET['q'], $filters)
   );
   $form['pager_top'] = array(
     '#weight' => -10,
-    '#value' => theme('pager', NULL, 10, 0)
+    '#value' => ($pager = theme('pager', NULL, $filters['limit'], 0)),
   );
   $form['pager_bottom'] = array(
     '#weight' => 10,
-    '#value' => theme('pager', NULL, 10, 0)
+    '#value' => $pager,
   );
   // Keep language code and URI in form for further reference.
   $form['langcode'] = array(
@@ -520,13 +513,12 @@ function l10n_community_translate_form_s
 
 /**
  * Theme function for l10n_community_filter_form.
- * Arranges elements in a condensed fashion in a table.
  */
 function theme_l10n_community_filter_form($form) {
-  $row = array(); // table content
-  $labels = array(); // table header labels
+  $row = array();
+  $labels = array();
   // Only display these elements in distinct table cells
-  $elements = array('project', 'release', 'context', 'status', 'search');
+  $elements = array('project', 'release', 'context', 'status', 'author', 'search', 'limit');
   foreach ($form as $id => &$element) {
     if (in_array($id, $elements)) {
       $labels[] = $element['#title'];
@@ -538,7 +530,7 @@ function theme_l10n_community_filter_for
   $labels[] = '';
   // Display the rest of the form in the last cell
   $row[] = drupal_render($form);
-  return theme('table', $labels, array($row), array('class' => 'l10n-server-translate'));
+  return theme('table', $labels, array($row), array('class' => 'l10n-server-filter'));
 }
 
 /**
@@ -602,32 +594,33 @@ function theme_l10n_community_in_context
  *
  * @param $langcode
  *   Language code to use for the lookup.
- * @param $project
- *   Project object to look up strings for.
- * @param $status
- *   Filter strings by status. See L10N_STATUS_ALL,
- *   L10N_STATUS_UNTRANSLATED, L10N_STATUS_HAS_SUGGESTION and
- *   L10N_STATUS_TRANSLATED.
- * @param $release
- *   Release id of the particular project release to filter with.
- *   Use NULL to not filter on releases.
- * @param $search
- *   Substring to search for in all source and translation strings.
- * @param $context
- *   From Drupal 7, separate contexts are supported. POTX_CONTEXT_NONE is
- *   the default, if the code does not specify a context otherwise.
+ * @param $filters
+ *   - 'project'
+ *     Project object to look up strings for.
+ *   - 'status'
+ *     Filter strings by status. See L10N_STATUS_ALL,
+ *     L10N_STATUS_UNTRANSLATED, L10N_STATUS_HAS_SUGGESTION and
+ *     L10N_STATUS_TRANSLATED.
+ *   - 'release'
+ *     Release id of the particular project release to filter with.
+ *     Use NULL to not filter on releases.
+ *   - 'search'
+ *     Substring to search for in all source and translation strings.
+ *   - 'context'
+ *     From Drupal 7, separate contexts are supported. POTX_CONTEXT_NONE is
+ *     the default, if the code does not specify a context otherwise.
  * @param $pager
  *   Number of strings to be returned in a pager. Should be NULL if
  *   no pager should be used.
  * @return
  *   An array of string records from database.
  */
-function l10n_community_get_strings($langcode, $project = NULL, $status = L10N_STATUS_ALL, $release = NULL, $search = NULL, $context = NULL, $pager = NULL) {
+function l10n_community_get_strings($langcode, $filters, $pager = NULL) {
 
   $sql = $sql_count = '';
   $sql_args = array();
 
-  if (!isset($project)) {
+  if (!isset($filters['project'])) {
     // No project based filtering.
     $sql = "SELECT DISTINCT s.sid, s.value, s.context, t.tid, t.language, t.translation, t.uid_entered, t.uid_approved, t.time_entered, t.time_approved, t.has_suggestion, t.is_suggestion, t.is_active FROM {l10n_community_string} s LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid AND t.language = '%s' AND t.is_active = 1 AND t.is_suggestion = 0 WHERE";
     $sql_count = "SELECT COUNT(DISTINCT(s.sid)) FROM {l10n_community_string} s LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid AND t.language = '%s' AND t.is_active = 1 AND t.is_suggestion = 0 WHERE";
@@ -637,22 +630,30 @@ function l10n_community_get_strings($lan
     // Project based filtering and language based filtering built in.
     $sql = "SELECT DISTINCT s.sid, s.value, s.context, t.tid, t.language, t.translation, t.uid_entered, t.uid_approved, t.time_entered, t.time_approved, t.has_suggestion, t.is_suggestion, t.is_active FROM {l10n_community_release} r INNER JOIN {l10n_community_file} f ON r.rid = f.rid INNER JOIN {l10n_community_line} l ON f.fid = l.fid INNER JOIN {l10n_community_string} s ON l.sid = s.sid LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid AND t.language = '%s' AND t.is_active = 1 AND t.is_suggestion = 0 WHERE r.pid = %d";
     $sql_count = "SELECT COUNT(DISTINCT(s.sid)) FROM {l10n_community_release} r INNER JOIN {l10n_community_file} f ON r.rid = f.rid INNER JOIN {l10n_community_line} l ON f.fid = l.fid INNER JOIN {l10n_community_string} s ON l.sid = s.sid LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid AND t.language = '%s' AND t.is_active = 1 AND t.is_suggestion = 0 WHERE r.pid = %d";
-    $sql_args = array($langcode, $project->pid);
+    $sql_args = array($langcode, $filters['project']->pid);
   }
 
-  if (!empty($search)) {
+  // Add submitted by condition
+  if (!empty($filters['author'])) {
+    $search_sql = " AND t.uid_entered = %d";
+    $sql .= $search_sql;
+    $sql_count .= $search_sql;
+    $sql_args[] = $filters['author']->uid;
+  }
+
+  if (!empty($filters['search'])) {
     // Search in the source or target strings.
-    $sql_args[] = $search;
-    $sql_args[] = $search;
+    $sql_args[] = $filters['search'];
+    $sql_args[] = $filters['search'];
     $search_sql = " AND (s.value LIKE '%%%s%%' OR t.translation LIKE '%%%s%%')";
     $sql .= $search_sql;
     $sql_count .= $search_sql;
   }
 
-  if (isset($release) && $release != 'all') {
+  if (isset($filters['release']) && $filters['release'] != 'all') {
     // Release restriction.
-    $sql_args[] = $release;
-    $sql_args[] = $release;
+    $sql_args[] = $filters['release'];
+    $sql_args[] = $filters['release'];
     $release_sql = ' AND r.rid = %d';
     $sql .= $release_sql;
     $sql_count .= $release_sql;
@@ -660,7 +661,7 @@ function l10n_community_get_strings($lan
 
   // Restriction based on string status by translation / suggestions.
   $status_sql = '';
-  if ($status & L10N_STATUS_UNTRANSLATED) {
+  if ($filters['status'] & L10N_STATUS_UNTRANSLATED) {
     // We are doing a LEFT JOIN especially to look into the case, when we have nothing
     // to match in the translation table, but we still have the string. (We get our
     // records in the result set in this case). The translation field is empty or
@@ -668,26 +669,26 @@ function l10n_community_get_strings($lan
     // translation if there are suggestions but no translation yet.
     $status_sql .= " AND (t.translation is NULL OR t.translation = '')";
   } 
-  elseif ($status & L10N_STATUS_TRANSLATED) {
+  elseif ($filters['status'] & L10N_STATUS_TRANSLATED) {
     $status_sql .= " AND t.translation != ''";
   }
-  if ($status & L10N_STATUS_HAS_SUGGESTION) {
+  if ($filters['status'] & L10N_STATUS_HAS_SUGGESTION) {
     // Note that we are not searching in the suggestions themselfs, only
     // the source and active translation values. The user interface underlines
     // that we are  looking for strings which have suggestions, not the
     // suggestions themselfs.
     $status_sql .= " AND t.has_suggestion = 1";
   }
-  elseif ($status & L10N_STATUS_NO_SUGGESTION) {
+  elseif ($filters['status'] & L10N_STATUS_NO_SUGGESTION) {
     $status_sql .= " AND t.has_suggestion = 0";
   }
   $sql .= $status_sql;
   $sql_count .= $status_sql;
 
   // Context based filtering.
-  if (isset($context) && $context != 'all') {
+  if (isset($filters['context']) && $filters['context'] != 'all') {
     // We use 'none' for no context, so '' can be the defaut (for all contexts).
-    $sql_args[] = $context == 'none' ? '' : $context;
+    $sql_args[] = $filters['context'] == 'none' ? '' : $filters['context'];
     $context_sql = " AND s.context = '%s'";
     $sql .= $context_sql;
     $sql_count .= $context_sql;
@@ -713,28 +714,46 @@ function l10n_community_get_strings($lan
   return $result;
 }
 
-/**
- * Builds a simple array with only the keys allowed for filtering.
+/**
+ * Check and sanitize arguments and build filter array.
  *
- * @param $input
- *   An array from $_GET or $form_state['values']
- * @param $allow_pager
- *   Allow page number to be kept in the array.
- * @return
- *  Filtered array with only the allowed keys and their values.
- *  The values are not checked for accuracy.
- */
-function l10n_community_build_filter_array($input, $allow_pager = FALSE) {
-  // Redirect keeping the relevant filters intact in the URL.
-  $filter = array();
-  $allowed_keys = array('search', 'project', 'release', 'context', 'status');
-  if ($allow_pager) {
-    $allowed_keys[] = 'page';
-  }
-  foreach ($input as $key => $value) {
-    if (in_array($key, $allowed_keys)) {
-      $filter[$key] = $value;
+ * @param $params
+ *   Associative array with unsanitized values.
+ * @param $suggestions
+ *   Whether we build the filters for the suggestions (TRUE) or not (FALSE).
+ */
+function l10n_community_build_filter_values($params, $suggestions = FALSE) {
+  $project = $release = NULL;
+
+  // Convert array representation of flags to one integer.
+  if (isset($params['status']) && is_array($params['status'])) {
+    if (isset($params['status']['suggestion'])) {
+      $params['status'] = ((int) $params['status']['translation']) | ((int) $params['status']['suggestion']);
+    }
+    else {
+      $params['status'] = (int) $params['status']['translation'];
     }
   }
-  return $filter;
-}
+
+  $filter = array(
+    'project' => NULL,
+    'status' => isset($params['status']) ? (int) $params['status'] : L10N_STATUS_ALL,
+    'release' => 'all',
+    'search' => !empty($params['search']) ? (string) $params['search'] : '',
+    'author' => !empty($params['author']) && ($account = user_load(array('name' => $params['author']))) ? $account : NULL,
+    // Dropdown, validated by form API.
+    'context' => isset($params['context']) ? (string) $params['context'] : 'all',
+    'limit' => isset($params['limit']) ? (int) $params['limit'] : 10,
+  );
+  
+  // The project can be a dropdown or text field depending on number of 
+  // projects. So we need to sanitize its value. 
+  if (isset($params['project']) && ($project = l10n_community_get_projects(array('uri' => $params['project'])))) {
+    $filter['project'] = $project;
+    if (isset($params['release']) && ($releases = l10n_community_get_releases($project->uri)) && isset($releases[$params['release']])) {
+      // Allow to select this release, if belongs to current project only.
+      $filter['release'] = $params['release'];
+    }
+  }
+  return $filter;
+}
