commit a2baadd1378c84df8e1f9eabc172ef8bd0ef5da1
Author: Assos Assos <assos+forge@centrale-marseille.fr>
Date:   Wed Apr 2 22:39:01 2014 +0200

    Weekly update of contrib modules

diff --git a/drupal7/sites/all/modules/cas/cas.admin.inc b/drupal7/sites/all/modules/cas/cas.admin.inc
index 4c209a22..08967fd3 100644
--- a/drupal7/sites/all/modules/cas/cas.admin.inc
+++ b/drupal7/sites/all/modules/cas/cas.admin.inc
@@ -310,11 +310,13 @@ function cas_admin_settings() {
       ),
     ),
   );
+
   $form['advanced']['cas_proxy_settings']['cas_pgtformat'] = array(
     '#type' => 'radios',
     '#title' => t('CAS PGT storage file format'),
     '#default_value' => variable_get('cas_pgtformat', 'plain'),
     '#options' => array('plain' => t('Plain Text'), 'xml' => t('XML')),
+    '#after_build' => array('cas_pgtformat_version_check'),
   );
 
   $form['advanced']['cas_proxy_settings']['cas_pgtpath'] = array(
@@ -325,6 +327,14 @@ function cas_admin_settings() {
     '#description' => t("Only needed if 'Use CAS proxy initializer' is configured. Leave empty for default."),
   );
 
+  $form['advanced']['cas_proxy_list'] = array(
+    '#type' => 'textarea',
+    '#title' => t('CAS proxy list'),
+    '#description' => t("If CAS client could be proxied, indicate each proxy server absolute url per line. If not provided, phpCAS will exclude by default all tickets provided by proxy. Each proxy server url could be a plain url or a regular expression. IMPORTANT : regular expression delimiter must be a slash. For example : https://proxy.example.com/ AND/OR regular expression : /^https:\/\/app[0-9]\.example\.com\/rest\//."),
+    '#default_value' => variable_get('cas_proxy_list', ''),
+    '#after_build' => array('cas_proxy_list_version_check'),
+  );
+
   $form['advanced']['cas_debugfile'] = array(
     '#type' => 'textfield',
     '#title' => t('CAS debugging output filename'),
@@ -359,3 +369,24 @@ function cas_library_version_check($element, &$form_state) {
   }
   return $element;
 }
+
+/**
+ * Proxy chain object only exists with phpCAS version >= 1.3. As phpCAS CAS.php
+ * is include only after building element 'cas_library_dir', we must check it after it.
+ */
+function cas_proxy_list_version_check($element, &$form_state) {
+  if (!defined('PHPCAS_VERSION') || version_compare(PHPCAS_VERSION, '1.3', '<')) {
+    $element['#access'] = FALSE;
+  }
+  return $element;
+}
+
+/**
+ * Since 1.3, pgt format isn't supported and default to plain.
+ */
+function cas_pgtformat_version_check($element, &$form_state) {
+  if (!defined('PHPCAS_VERSION') || version_compare(PHPCAS_VERSION, '1.3', '>')) {
+    $element['#access'] = FALSE;
+  }
+  return $element;
+}
diff --git a/drupal7/sites/all/modules/cas/cas.drush.inc b/drupal7/sites/all/modules/cas/cas.drush.inc
index 6ad97d37..a975a09f 100644
--- a/drupal7/sites/all/modules/cas/cas.drush.inc
+++ b/drupal7/sites/all/modules/cas/cas.drush.inc
@@ -10,6 +10,19 @@
  */
 function cas_drush_command() {
   $items = array();
+  $items['cas-user-add-role'] = array(
+    'callback' => 'cas_drush_user_add_role',
+    'description' => 'Add a role to the specified CAS usernames.',
+    'arguments' => array(
+      'role' => 'The name of the role to add',
+      'users' => '(optional) A comma delimited list of CAS user names.',
+    ),
+    'required-arguments' => 1,
+    'examples' => array(
+      'drush cas-user-add-role "power user" casuser1,casuser2' =>
+        'Add the "power user" role to the accounts with CAS user names casuser1 and casuser2.',
+    ),
+  );
   $items['cas-user-create'] = array(
     'callback' => 'cas_drush_user_create',
     'description' => dt('Create a CAS user account with the specified CAS username.'),
@@ -25,12 +38,14 @@ function cas_drush_command() {
 }
 
 /**
- * Implementats hook_drush_help().
+ * Implements hook_drush_help().
  */
 function cas_drush_help($section) {
   switch ($section) {
     case 'drush:cas-user-create':
       return dt('Create a CAS user account with the specified CAS username.');
+    case 'drush:cas-user-add-role':
+      return dt('Add a role to the specified CAS usernames.');
   }
 }
 
@@ -57,3 +72,54 @@ function cas_drush_user_create($cas_name) {
     drush_set_error(dt('There is already a user account with CAS username @cas_name.', array('@cas_name' => $cas_name)));
   }
 }
+
+/**
+ * Add a role to the specified CAS user accounts.
+ */
+function cas_drush_user_add_role($role, $users = '') {
+  $uids = cas_drush_user_get_users_from_arguments($users);
+  if (drush_drupal_major_version() >= 7) {
+    $rid_query = db_query("SELECT rid FROM {role} WHERE name = :role", array(':role' => $role));
+  }
+  else {
+    $rid_query = db_query("SELECT rid FROM {role} WHERE name = '%s'", $role);
+  }
+  if (!empty($uids)) {
+    if ($rid = drush_db_result($rid_query)) {
+      drush_op('user_multiple_role_edit', $uids, 'add_role', $rid);
+      foreach ($uids as $uid) {
+        drush_log(dt("Added the !role role to uid !uid", array('!role' => $role, '!uid' => $uid)), 'success');
+      }
+    }
+    else {
+      return drush_set_error(dt("There is no role named: !role", array('!role' => $role)));
+    }
+  }
+  else {
+    return drush_set_error("Could not find any valid uids!");
+  }
+}
+
+/**
+ * Look up user ids from a comma-separated list of CAS usernames.
+ *
+ * @param $users string
+ *   Comma separated list of CAS usernames.
+ *
+ * @return array
+ *   An array of user ids corresponding to the given CAS usernames. Unknown
+ *   CAS usernames are ignored.
+ */
+function cas_drush_user_get_users_from_arguments($users) {
+  $uids = array();
+  if ($users !== '') {
+    $users = explode(',', $users);
+    foreach ($users as $user) {
+      $account = cas_user_load_by_name($user);
+      if ($account) {
+        $uids[] = $account->uid;
+      }
+    }
+  }
+  return $uids;
+}
diff --git a/drupal7/sites/all/modules/cas/cas.info b/drupal7/sites/all/modules/cas/cas.info
index 384e9909..a600e3bd 100644
--- a/drupal7/sites/all/modules/cas/cas.info
+++ b/drupal7/sites/all/modules/cas/cas.info
@@ -7,9 +7,9 @@ configure = admin/config/people/cas
 files[] = cas.test
 files[] = includes/views/handlers/cas_handler_field_cas_name.inc
 
-; Information added by drupal.org packaging script on 2012-05-09
-version = "7.x-1.2"
+; Information added by Drupal.org packaging script on 2014-04-02
+version = "7.x-1.3"
 core = "7.x"
 project = "cas"
-datestamp = "1336595150"
+datestamp = "1396458247"
 
diff --git a/drupal7/sites/all/modules/cas/cas.install b/drupal7/sites/all/modules/cas/cas.install
index 8c481bee..d9d11986 100644
--- a/drupal7/sites/all/modules/cas/cas.install
+++ b/drupal7/sites/all/modules/cas/cas.install
@@ -99,6 +99,7 @@ function cas_uninstall() {
   variable_del('cas_pgtpath');
   variable_del('cas_port');
   variable_del('cas_proxy');
+  variable_del('cas_proxy_list');
   variable_del('cas_registerURL');
   variable_del('cas_server');
   variable_del('cas_uri');
diff --git a/drupal7/sites/all/modules/cas/cas.module b/drupal7/sites/all/modules/cas/cas.module
index 7b8c247b..75ff6fb6 100644
--- a/drupal7/sites/all/modules/cas/cas.module
+++ b/drupal7/sites/all/modules/cas/cas.module
@@ -99,7 +99,7 @@ function cas_login_check($force_authentication = TRUE) {
   if (empty($cas_user['login']) || empty($cas_user['name'])) {
     // Only set a warning if we forced login.
     if ($force_authentication) {
-      drupal_set_message(t('The user account %name is not available on this site.', array('%name' => $cas_name)), 'error');
+      drupal_set_message(t('The user account %name is not available on this site.', array('%name' => $cas_user['name'])), 'error');
     }
     return;
   }
@@ -159,7 +159,7 @@ function cas_login_check($force_authentication = TRUE) {
     $edit['cas_user'] = $cas_user;
     $edit['roles'] = $account->roles + cas_roles();
     if (module_exists('persistent_login') && !empty($_SESSION['cas_remember'])) {
-      $edit['persistent_login'] = 1;
+      $edit['values']['persistent_login'] = 1;
     }
     // Allow other modules to make their own custom changes.
     cas_user_module_invoke('presave', $edit, $account);
@@ -179,7 +179,7 @@ function cas_login_check($force_authentication = TRUE) {
     $user = drupal_anonymous_user();
     // Only display error messages only if the user intended to log in.
     if ($force_authentication) {
-      drupal_set_message(t('No account found for %cas_name.', array('%cas_name' => $cas_name)));
+      drupal_set_message(t('No account found for %cas_name.', array('%cas_name' => $cas_name)), 'error');
     }
   }
 }
@@ -243,21 +243,35 @@ function cas_phpcas_init() {
   $server_port       = (int)variable_get('cas_port', '443');
   $server_uri        = (string)variable_get('cas_uri', '');
   $cas_cert          = (string)variable_get('cas_cert', '');
-  if ( ($debugFile = variable_get("cas_debugfile", "")) != "" ) {
-    phpCAS::setDebug($debugFile);
+  $debug_file        = (string)variable_get('cas_debugfile', '');
+  if ($debug_file != '') {
+    phpCAS::setDebug($debug_file);
   }
   $start_session = (boolean)FALSE;
-  if ( variable_get("cas_proxy", 0) ) {
+  if (variable_get('cas_proxy', 0)) {
     phpCAS::proxy($server_version, $server_cas_server, $server_port, $server_uri, $start_session);
-    $casPGTStoragePath = variable_get("cas_pgtpath", "");
-    if ( $casPGTStoragePath != "" ) {
-      $casPGTFormat = variable_get("cas_pgtformat", "plain");
-      phpCAS::setPGTStorageFile($casPGTFormat, $casPGTStoragePath);
+    $cas_pgt_storage_path = variable_get('cas_pgtpath', '');
+    if ($cas_pgt_storage_path != '') {
+      if (version_compare(PHPCAS_VERSION, '1.3', '>=')) {
+        phpCAS::setPGTStorageFile($cas_pgt_storage_path);
+      }
+      else {
+        $cas_pgt_format = variable_get('cas_pgtformat', 'plain');
+        phpCAS::setPGTStorageFile($cas_pgt_format, $cas_pgt_storage_path);
+      }
     }
   }
   else {
     phpCAS::client($server_version, $server_cas_server, $server_port, $server_uri, $start_session);
   }
+
+  //Add CAS proxy lists allowed
+  $proxy_list = variable_get('cas_proxy_list', '');
+  if ($proxy_list) {
+    $proxy_list = explode("\n", $proxy_list);
+    phpCAS::allowProxyChain(new CAS_ProxyChain($proxy_list));
+  }
+
   // force CAS authentication
   if ($cas_cert = variable_get('cas_cert', '')) {
     phpCAS::setCasServerCACert($cas_cert);
@@ -380,8 +394,8 @@ function cas_menu_site_status_alter(&$menu_site_status, $path) {
  * Implements hook_menu_link_alter().
  *
  * Flag this link as needing alter at display time.
- * @see cas_translated_menu_link_alter().
- **/
+ * @see cas_translated_menu_link_alter()
+ */
 function cas_menu_link_alter(&$item) {
   if ($item['link_path'] == 'cas' || $item['link_path'] == 'caslogout') {
     $item['options']['alter'] = TRUE;
@@ -392,7 +406,7 @@ function cas_menu_link_alter(&$item) {
  * Implements hook_translated_menu_item_alter().
  *
  * Append dynamic query 'destination' to several menu items.
- **/
+ */
 function cas_translated_menu_link_alter(&$item) {
   if ($item['href'] == 'cas') {
     $item['localized_options']['query'] = drupal_get_destination();
@@ -437,7 +451,7 @@ function cas_user_operations($form = array(), $form_state = array()) {
  *   An array of user ids. For each account, a CAS username is created with
  *   the same name as the Drupal username.
  *
- * @see cas_user_operations().
+ * @see cas_user_operations()
  */
 function cas_user_operations_create_username($uids) {
   $accounts = user_load_multiple($uids);
@@ -463,7 +477,7 @@ function cas_user_operations_create_username($uids) {
  * @param $uids
  *   An array of user ids. For each account, all CAS usernames are removed.
  *
- * @see cas_user_operations().
+ * @see cas_user_operations()
  */
 function cas_user_operations_remove_usernames($uids) {
   db_delete('cas_user')
@@ -590,12 +604,15 @@ function cas_user_delete($account) {
  * @param $alter
  *   If TRUE, run the CAS username through hook_cas_user_alter() before
  *   loading the account.
+ * @param $reset
+ *   TRUE to reset the internal cache and load from the database; FALSE
+ *   (default) to load from the internal cache, if set.
  *
  * @return
  *   A fully-loaded $user object upon successful user load or FALSE if user
  *   cannot be loaded.
  */
-function cas_user_load_by_name($cas_name, $alter = FALSE) {
+function cas_user_load_by_name($cas_name, $alter = FALSE, $reset = FALSE) {
   if ($alter) {
     $cas_user = array(
       'name' => $cas_name,
@@ -606,9 +623,9 @@ function cas_user_load_by_name($cas_name, $alter = FALSE) {
     $cas_name = $cas_user['name'];
   }
 
-  $uid = db_query("SELECT uid FROM {cas_user} WHERE cas_name = :cas_name", array(':cas_name' => $cas_name))->fetchField();
+  $uid = db_select('cas_user')->fields('cas_user', array('uid'))->condition('cas_name', db_like($cas_name), 'LIKE')->range(0, 1)->execute()->fetchField();
   if ($uid) {
-    return user_load($uid);
+    return user_load($uid, $reset);
   }
   return FALSE;
 }
@@ -623,14 +640,17 @@ function cas_user_load_by_name($cas_name, $alter = FALSE) {
 function cas_login_page($cas_first_login = FALSE) {
   global $user;
   $destination = '';
+  $query = array();
   // If it is the user's first CAS login and initial login redirection is enabled, go to the set page
   if ($cas_first_login && variable_get('cas_first_login_destination', '')) {
     $destination = variable_get('cas_first_login_destination', '');
+    if (isset($_GET['destination']))
+      $query['destination'] = $_GET['destination'];
     unset($_GET['destination']);
   }
 
   // Respect the query string, if transmitted.
-  drupal_goto($destination);
+  drupal_goto($destination, array('query' => $query));
 }
 
 /**
@@ -654,8 +674,6 @@ function cas_logout($invoke_hook = TRUE) {
 
   // Build the logout URL.
   cas_phpcas_init();
-  $logout_url = phpCAS::getServerLogoutURL();
-  $options = array();
 
   if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) {
     // Add destination override so that a destination can be specified on the
@@ -671,23 +689,23 @@ function cas_logout($invoke_hook = TRUE) {
   //Make it an absolute url.  This will also convert <front> to the front page.
   if ($destination) {
     $destination_url = url($destination, array('absolute' => TRUE));
-    $options['query'] = array(
-      'destination' => $destination_url,
+    $options = array(
       'service' => $destination_url,
       'url' => $destination_url,
     );
   }
+  else {
+    $options = array();
+  }
 
   // Mimic user_logout().
   if ($invoke_hook) {
     watchdog('user', 'Session closed for %name.', array('%name' => $user->name));
     module_invoke_all('user_logout', $user);
   }
-  session_destroy();
 
-  // Force redirection in drupal_goto().
-  unset($_GET['destination']);
-  drupal_goto($logout_url, $options);
+  // phpCAS automatically calls session_destroy().
+  phpCAS::logout($options);
 }
 
 /**
@@ -745,7 +763,7 @@ function cas_login_block($form) {
  * Determine if we should automatically check if the user is authenticated.
  *
  * This implements part of the CAS gateway feature.
- * @see phpCAS::checkAuthentication().
+ * @see phpCAS::checkAuthentication()
  *
  * @return
  *   TRUE if we should query the CAS server to see if the user is already
@@ -763,33 +781,35 @@ function _cas_allow_check_for_login() {
   }
 
   // Check to see if we've got a search bot.
-  $crawlers = array(
-    'Google',
-    'msnbot',
-    'Rambler',
-    'Yahoo',
-    'AbachoBOT',
-    'accoona',
-    'AcoiRobot',
-    'ASPSeek',
-    'CrocCrawler',
-    'Dumbot',
-    'FAST-WebCrawler',
-    'GeonaBot',
-    'Gigabot',
-    'Lycos',
-    'MSRBOT',
-    'Scooter',
-    'AltaVista',
-    'IDBot',
-    'eStyle',
-    'Scrubby',
-    'gsa-crawler',
-    );
-  // Return on the first find.
-  foreach ($crawlers as $c) {
-    if (stripos($_SERVER['HTTP_USER_AGENT'], $c) !== FALSE) {
-      return FALSE;
+  if (isset($_SERVER['HTTP_USER_AGENT'])) {
+    $crawlers = array(
+      'Google',
+      'msnbot',
+      'Rambler',
+      'Yahoo',
+      'AbachoBOT',
+      'accoona',
+      'AcoiRobot',
+      'ASPSeek',
+      'CrocCrawler',
+      'Dumbot',
+      'FAST-WebCrawler',
+      'GeonaBot',
+      'Gigabot',
+      'Lycos',
+      'MSRBOT',
+      'Scooter',
+      'AltaVista',
+      'IDBot',
+      'eStyle',
+      'Scrubby',
+      'gsa-crawler',
+      );
+    // Return on the first find.
+    foreach ($crawlers as $c) {
+      if (stripos($_SERVER['HTTP_USER_AGENT'], $c) !== FALSE) {
+        return FALSE;
+      }
     }
   }
 
@@ -869,7 +889,6 @@ function _cas_force_login() {
  */
 function cas_form_alter(&$form, &$form_state, $form_id) {
 
-  //drupal_set_message($form_id.'<pre>'.print_r($form,1).'</pre>');
   switch ($form_id) {
     case 'user_login':
     case 'user_login_block':
@@ -953,14 +972,16 @@ function cas_form_alter(&$form, &$form_state, $form_id) {
         }
         if (variable_get('cas_hide_password', 0)) {
           $form['account']['pass']['#access'] = FALSE;
-
-          // Also remove requirement to validate your current password before
-          // changing your e-mail address.
-          $form['account']['current_pass']['#access'] = FALSE;
-          $form['account']['current_pass_required_values']['#access'] = FALSE;
-          $form['#validate'] = array_diff($form['#validate'], array('user_validate_current_pass'));
         }
       }
+      if (cas_is_external_user($account) && variable_get('cas_hide_password', 0)) {
+        // Also remove requirement to validate your current password before
+        // changing your e-mail address.
+        $form['account']['current_pass']['#access'] = FALSE;
+        $form['account']['current_pass_required_values']['#access'] = FALSE;
+        $form['account']['current_pass_required_values']['#value'] = array();
+        $form['#validate'] = array_diff($form['#validate'], array('user_validate_current_pass'));
+      }
       break;
 
     case 'user_pass':
@@ -1046,7 +1067,7 @@ function cas_login_submit(&$form, &$form_state) {
 
 function _cas_single_sign_out_check() {
   if (isset($_POST["logoutRequest"])) {
-    $cas_logout_request_xml_string = utf8_encode($_POST["logoutRequest"]);     // it's important!
+    $cas_logout_request_xml_string = utf8_encode(urldecode($_POST["logoutRequest"]));
     $cas_logout_request_xml = new SimpleXMLElement($cas_logout_request_xml_string);
     if (is_object($cas_logout_request_xml)) {
       $namespaces = $cas_logout_request_xml->getNameSpaces();
@@ -1061,20 +1082,19 @@ function _cas_single_sign_out_check() {
         // Log them out now.
         // first lets find out who we want to log off
 
-        $result = db_query_range("SELECT cld.uid FROM {cas_login_data} cld WHERE cld.cas_session_id = :ticket", 0 , 1, array(':ticket' => $cas_session_index));
-        foreach ($result as $record) {
-            $uid = $record->uid;
-            $acct = user_load($uid);
-            watchdog('user', 'Session closed for %name.', array('%name' => $acct->name));
-            // remove all entry for user id in cas_login_data
-            db_delete('cas_login_data')
-              ->condition('uid', $uid)
-              ->execute();
-
-            // remove their session
-            db_delete('sessions')
-              ->condition('uid', $uid)
-              ->execute();
+
+        $record = db_query_range("SELECT cld.uid, u.name FROM {users} u JOIN {cas_login_data} cld ON u.uid = cld.uid WHERE cld.cas_session_id = :ticket", 0, 1, array(':ticket' => $cas_session_index))->fetchObject();
+        if ($record) {
+          watchdog('user', 'Session closed for %name by CAS logout request.', array('%name' => $record->name));
+          //remove all entry for user id in cas_login_data
+          db_delete('cas_login_data')
+            ->condition('uid', $record->uid)
+            ->execute();
+
+          // remove their session
+          db_delete('sessions')
+            ->condition('uid', $record->uid)
+            ->execute();
         }
       }
     }
@@ -1238,7 +1258,7 @@ function cas_user_register($cas_name, $options = array()) {
  * Get the CAS attributes of the current CAS user.
  *
  * Ensures that phpCAS is properly initialized before getting the attributes.
- * @see phpCAS::getAttributes().
+ * @see phpCAS::getAttributes()
  *
  * @param $cas_name
  *   If provided, ensure that the currently logged in CAS user matches this
diff --git a/drupal7/sites/all/modules/cas/cas.pages.inc b/drupal7/sites/all/modules/cas/cas.pages.inc
index 8e8f25f4..5105ea53 100644
--- a/drupal7/sites/all/modules/cas/cas.pages.inc
+++ b/drupal7/sites/all/modules/cas/cas.pages.inc
@@ -9,7 +9,7 @@
  * Menu callback; Manage CAS identities for the specified user.
  */
 function cas_user_identities($account) {
-  drupal_set_title(format_username($account));
+  drupal_set_title(check_plain(format_username($account)));
 
   $header = array(t('CAS username'), t('Operations'));
   $rows = array();
diff --git a/drupal7/sites/all/modules/cas/cas.test b/drupal7/sites/all/modules/cas/cas.test
index a0970fd9..154efa47 100644
--- a/drupal7/sites/all/modules/cas/cas.test
+++ b/drupal7/sites/all/modules/cas/cas.test
@@ -35,7 +35,7 @@ class CasTestHelper extends DrupalWebTestCase {
     $this->admin_user = $this->drupalCreateUser(array('administer users', 'administer cas'));
 
     // Download and extract in PHPCAS.
-    $this->downloadExtractPhpCas('1.2.2');
+    $this->downloadExtractPhpCas('1.3.1');
   }
 
   /**
@@ -77,16 +77,6 @@ class CasTestHelper extends DrupalWebTestCase {
       // Extract the files.
       $archiver = archiver_get_archiver($local_archive);
       $archiver->extract($simpletest_cache);
-      // Work around issue https://issues.jasig.org/browse/PHPCAS-105.
-      // @see http://drupal.org/node/1120034
-      switch ($version) {
-        case '1.1.3':
-          system_retrieve_file('http://drupal.org/files/issues/CAS-1.1.3-CAS-client.txt', $cas_library_dir . '/CAS/client.php', FALSE, FILE_EXISTS_REPLACE);
-          break;
-        case '1.2.1':
-          system_retrieve_file('http://drupal.org/files/issues/CAS-1.2.1-CAS-client_0.txt', $cas_library_dir . '/CAS/client.php', FALSE, FILE_EXISTS_REPLACE);
-          break;
-      }
     }
     if (function_exists('sem_get')) {
       sem_release($semaphore);
@@ -145,17 +135,41 @@ class CasTestHelper extends DrupalWebTestCase {
     }
 
     // Log in the user.
-    $cas_name = is_object($account) ? $account->cas_name : $account;
-    $cas_user = array('name' => $cas_name, 'attributes' => $attributes);
-    variable_set('cas_test_cas_user', $cas_user);
+    $cas_name = $this->setCasUser($account, $attributes);
     $this->drupalGet('cas');
 
     $pass = $this->assertLink(t('Log out'), 0, t('CAS user %cas_name successfully logged in.', array('%cas_name' => $cas_name)), t('User login'));
     if ($pass) {
-      $this->loggedInUser = cas_user_load_by_name($cas_name, TRUE);
+      $this->loggedInUser = cas_user_load_by_name($cas_name, TRUE, TRUE);
     }
   }
 
+  /**
+   * Set the CAS username and attributes for the next CAS login request.
+   *
+   * @param $account
+   *   A user object with a valid CAS username field, or the CAS username as a
+   *   string.
+   * @param $attributes
+   *   Additional attributes for the CAS user.
+   *
+   * @return
+   *   The CAS username.
+   */
+  function setCasUser($account, $attributes = array()) {
+    $cas_name = is_object($account) ? $account->cas_name : $account;
+    $cas_user = array('name' => $cas_name, 'attributes' => $attributes);
+    variable_set('cas_test_cas_user', $cas_user);
+    return $cas_name;
+  }
+
+  /**
+   * Clear the CAS username and attributes for the next CAS login request.
+   */
+  function clearCasUser() {
+    variable_del('cas_test_cas_user');
+  }
+
   /**
    * Assert that the user has logged in.
    *
@@ -353,7 +367,7 @@ class CasUserTestCase extends CasTestHelper {
    */
   function testCasAutoRegister() {
     $cas_name = $this->randomName();
-    variable_set('cas_test_cas_user', array('name' => $cas_name));
+    $this->setCasUser($cas_name);
 
     // Test that the user is not automatically registered.
     variable_set('cas_user_register', FALSE);
@@ -370,7 +384,7 @@ class CasUserTestCase extends CasTestHelper {
     // Create a new user.
     $user2 = $this->drupalCreateUser();
     $cas_name = $user2->name;
-    variable_set('cas_test_cas_user', array('name' => $cas_name));
+    $this->setCasUser($cas_name);
 
     // Test that CAS does not hijack an existing username.
     $this->drupalGet('cas');
@@ -423,6 +437,13 @@ class CasUserTestCase extends CasTestHelper {
 
     $this->assertToken('[cas:name]', $account->cas_name);
   }
+
+  function testCaseInsensitiveLogin() {
+    $account = $this->casCreateUser();
+    $this->casLogin(strtoupper($account->cas_name));
+    $this->assertLoggedIn($account);
+  }
+
 }
 
 /**
@@ -510,7 +531,7 @@ class CasLoginRedirectionTestCase extends CasTestHelper {
     // Create a CAS user.
     $account = $this->casCreateUser();
     $cas_name = $account->cas_name;
-    variable_set('cas_test_cas_user', array('name' => $cas_name));
+    $this->setCasUser($cas_name);
 
     // Test going to 'cas'
     $this->drupalGet('cas');
@@ -572,7 +593,7 @@ class CasLoginRedirectionTestCase extends CasTestHelper {
     variable_set('cas_first_login_destination', "node/$node->nid");
     $cas_name = $this->randomName();
     $account = $this->casLogin($cas_name);
-    $this->assertUrl("node/$node->nid");
+    $this->assertUrl("node/$node->nid", array('query' => array('destination' => '')));
     $this->drupalLogout();
 
     // The second login should not be redirected.
@@ -584,7 +605,7 @@ class CasLoginRedirectionTestCase extends CasTestHelper {
     // destination.
     $account = $this->casCreateUser();
     $this->casLogin($account);
-    $this->assertUrl("node/$node->nid");
+    $this->assertUrl("node/$node->nid", array('query' => array('destination' => '')));
     $this->drupalLogout();
 
     // The second login should not be redirected.
@@ -619,6 +640,19 @@ class CasSingleSignOutTestCase extends CasTestHelper {
     // @todo: Add additional tests for other methods of logging in (especially
     //   methods coming from cas_pages).
   }
+
+  function testSingleSignOutDoubleEncode() {
+    // Create a user, and log in.
+    $cas_name = $this->randomName();
+    $account = $this->casCreateUser($cas_name);
+    $this->casLogin($account);
+
+    cas_test_single_sign_out($cas_name, TRUE);
+    $this->assertLoggedOut();
+
+    // @todo: Add additional tests for other methods of logging in (especially
+    //   methods coming from cas_pages).
+  }
 }
 
 /**
@@ -658,7 +692,7 @@ class CasGatewayTestCase extends CasTestHelper {
     // Create a user.
     $cas_name = $this->randomName();
     $account = $this->casCreateUser($cas_name);
-    variable_set('cas_test_cas_user', array('name' => $cas_name));
+    $this->setCasUser($cas_name);
 
     $this->drupalGet('node');
     $this->assertLoggedIn($account);
@@ -685,7 +719,7 @@ class CasRequiredLoginTestCase extends CasTestHelper {
     $node1 = $this->drupalCreateNode();
     $node2 = $this->drupalCreateNode();
     $account = $this->casCreateUser();
-    variable_set('cas_test_cas_user', array('name' => $account->cas_name));
+    $this->setCasUser($account);
 
     $this->drupalGet("node/$node2->nid");
 
@@ -707,7 +741,7 @@ class CasRequiredLoginTestCase extends CasTestHelper {
     $this->assertField('pass', t('Password field found.'), t('Logout'));
 
     // Verify that accessing any other page redirects to the login page.
-    variable_del('cas_test_cas_user');
+    $this->clearCasUser();
     $this->drupalGet('node');
     $this->assertText('No CAS name provided.');
   }
@@ -718,7 +752,7 @@ class CasRequiredLoginTestCase extends CasTestHelper {
   function testCasExclude() {
     $node = $this->drupalCreateNode();
     $account = $this->casCreateUser();
-    variable_set('cas_test_cas_user', array('name' => $account->cas_name));
+    $this->setCasUser($account);
 
     variable_set('cas_check_first', TRUE);
     variable_set('cas_exclude', "node/$node->nid");
@@ -786,7 +820,7 @@ class CasLoginBlockTestCase extends CasTestHelper {
    */
   function testCasLoginBlock() {
     $account = $this->casCreateUser();
-    variable_set('cas_test_cas_user', array('name' => $account->cas_name));
+    $this->setCasUser($account);
 
     // Verify that the block is shown on some pages, but not on others.
     $this->drupalGet('');
diff --git a/drupal7/sites/all/modules/cas/cas.user.inc b/drupal7/sites/all/modules/cas/cas.user.inc
index 628c657c..ed998543 100644
--- a/drupal7/sites/all/modules/cas/cas.user.inc
+++ b/drupal7/sites/all/modules/cas/cas.user.inc
@@ -16,7 +16,7 @@ function cas_add_user_form() {
     '#title' => t('CAS username'),
     '#required' => TRUE,
     '#default_value' => '',
-    '#description' => t('Registration will proceded as if the user with the specified CAS username just logged in.'),
+    '#description' => t('Registration will proceed as if the user with the specified CAS username just logged in.'),
     '#element_validate' => array('_cas_name_element_validate'),
     '#weight' => -10,
   );
diff --git a/drupal7/sites/all/modules/cas/cas_server.api.php b/drupal7/sites/all/modules/cas/cas_server.api.php
index 168247e2..ce6ec17c 100644
--- a/drupal7/sites/all/modules/cas/cas_server.api.php
+++ b/drupal7/sites/all/modules/cas/cas_server.api.php
@@ -33,7 +33,7 @@ function hook_cas_server_user_attributes($account, $service, $ticket) {
   $attributes['friends'] = array('dries', 'webchick');
 
   // Or change the response based upon the server.
-  if (preg_match($service, "@^http://apple.com/@")) {
+  if (preg_match("@^http://apple.com/@", $service)) {
     // This data should not be confidential as the service URL is directly
     // supplied by the user and is in no way validated.
     $attributes['friends'] += 'sjobs';
diff --git a/drupal7/sites/all/modules/cas/cas_server.info b/drupal7/sites/all/modules/cas/cas_server.info
index 5ca10914..1dc495b5 100644
--- a/drupal7/sites/all/modules/cas/cas_server.info
+++ b/drupal7/sites/all/modules/cas/cas_server.info
@@ -3,9 +3,9 @@ package = Central Authentication Service
 core = 7.x
 description = "Provides protocol compliant CAS Server"
 
-; Information added by drupal.org packaging script on 2012-05-09
-version = "7.x-1.2"
+; Information added by Drupal.org packaging script on 2014-04-02
+version = "7.x-1.3"
 core = "7.x"
 project = "cas"
-datestamp = "1336595150"
+datestamp = "1396458247"
 
diff --git a/drupal7/sites/all/modules/cas/cas_server.install b/drupal7/sites/all/modules/cas/cas_server.install
index 4e882a33..1e10b842 100644
--- a/drupal7/sites/all/modules/cas/cas_server.install
+++ b/drupal7/sites/all/modules/cas/cas_server.install
@@ -35,6 +35,11 @@ function cas_server_schema() {
         'type' => 'int',
         'not null' => TRUE,
       ),
+      'valid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1,
+      ),
     ),
     'primary key' => array('ticket'),
   );
@@ -46,7 +51,7 @@ function cas_server_schema() {
 /**
  * Creates CAS server tickets table.
  */
-function cas_server_update_1() {
+function cas_server_update_7000() {
   $schema = array();
 
   $schema['cas_server_tickets'] = array(
@@ -78,6 +83,13 @@ function cas_server_update_1() {
   );
 
   $ret = array();
-  db_create_table($ret, 'cas_server_tickets', $schema['cas_server_tickets']);
+  db_create_table('cas_server_tickets', $schema['cas_server_tickets']);
   return $ret;
 }
+
+/**
+ * Adds valid field to indicate when ticket is valid for reuse.
+ */
+function cas_server_update_7101() {
+  db_add_field('cas_server_tickets', 'valid', array('type' => 'int', 'not null' => TRUE, 'default' => 1, ));
+}
diff --git a/drupal7/sites/all/modules/cas/cas_server.module b/drupal7/sites/all/modules/cas/cas_server.module
index e20a0274..8d7af9ac 100644
--- a/drupal7/sites/all/modules/cas/cas_server.module
+++ b/drupal7/sites/all/modules/cas/cas_server.module
@@ -64,6 +64,10 @@ function cas_server_theme() {
       'variables' => array('ticket' => NULL, 'error_code' => NULL),
       'file' => 'cas_server.response.inc',
     ),
+    'cas_service_logout_request' => array(
+      'variables' => array('ticket' => NULL, 'date' => NULL, 'id' => NULL),
+      'file' => 'cas_server.response.inc',
+    ),
   );
 }
 
@@ -131,6 +135,12 @@ function cas_server_login() {
  * This provides the simple non-xml based
  */
 function cas_server_validate() {
+  // Prevent this page from being cached.
+  drupal_page_is_cacheable(FALSE);
+
+  // Set content type.
+  drupal_add_http_header('Content-Type', 'text/plain; charset=utf-8');
+
   //Obtain the ticket from the url and validate it.
   $ticket = isset($_REQUEST['ticket']) ? $_REQUEST['ticket'] : '';
   $service = isset($_REQUEST['service']) ? $_REQUEST['service'] : '';
@@ -149,13 +159,18 @@ function cas_server_validate() {
  * Returns data in xml
  */
 function cas_server_service_validate() {
+  // Prevent this page from being cached.
+  drupal_page_is_cacheable(FALSE);
+
+  // Set content type.
+  drupal_add_http_header('Content-Type', 'text/xml; charset=utf-8');
+
   $ticket = isset($_REQUEST['ticket']) ? $_REQUEST['ticket'] : '';
   $service = isset($_REQUEST['service']) ? $_REQUEST['service'] : '';
   $user_name = _cas_server_validate($service, $ticket);
   if (!$user_name) $cas_error='INVALID_TICKET';
   if (!$ticket || !$service) $cas_error='INVALID_REQUEST';
 
-  header('Content-type:', 'text/xml');
   if ($user_name) {
     //@TODO Generate proxy granting ticket
     $account = user_load_by_name($user_name);
@@ -189,13 +204,14 @@ function _cas_server_validate($service, $ticket) {
   // Look up the ticket
   $user_name='';
   $ticket_info=array(':service' => $service, ':ticket' => $ticket);
-  $result = db_query_range("SELECT u.name FROM {cas_server_tickets} t JOIN {users} u ON t.uid=u.uid  WHERE t.service = :service and t.ticket = :ticket", 0, 1, $ticket_info);
+  $result = db_query_range("SELECT u.name FROM {cas_server_tickets} t JOIN {users} u ON t.uid=u.uid  WHERE t.service = :service and t.ticket = :ticket AND valid=1", 0, 1, $ticket_info);
   if ($result !== FALSE) {
     foreach ($result as $ticket_data) {
       $user_name = $ticket_data->name;
     }
   }
-  db_delete('cas_server_tickets')
+  db_update('cas_server_tickets')
+    ->fields(array('valid' => 0))
     ->condition('ticket', $ticket)
     ->execute();
 
@@ -211,7 +227,7 @@ function _cas_server_save_ticket($uid, $service) {
   // Generate the ticket
   $time = REQUEST_TIME;
   $ticket = 'ST-' . user_password();
-  $ticket_data = array('uid' => $uid, 'service' => $service, 'ticket' => $ticket, 'timestamp' => $time);
+  $ticket_data = array('uid' => $uid, 'service' => $service, 'ticket' => $ticket, 'timestamp' => $time, 'valid' => 1);
   // Save the ticket to the db
   if ($uid && $service) {
     db_insert('cas_server_tickets')->fields($ticket_data)->execute();
@@ -221,8 +237,6 @@ function _cas_server_save_ticket($uid, $service) {
 
 /**
  * Menu callback; triggers a CAS logout.
- *
- * @TODO: Implement single sign out support
  */
 function cas_server_logout() {
   global $user;
@@ -240,3 +254,49 @@ function cas_server_logout() {
   }
   return $output;
 }
+
+/**
+ * Send CAS a logout requests for each of the user's CAS tickets.
+ *
+ * @param $account
+ *   The user for whom to send CAS logout requests.
+ */
+function cas_server_logout_clients($account) {
+   $result = db_query("SELECT service, ticket, valid FROM {cas_server_tickets} WHERE uid= :uid", array(':uid' => $account->uid));
+    if ($result !== FALSE) {
+      $expired_tickets = array();
+      foreach ($result as $client) {
+        $expired_tickets[] = $client->ticket;
+        if (!$client->valid)  {
+          $id = 'LR-' . user_password();
+          $date = date('c');
+          $logout_request = theme('cas_service_logout_request', array('id' => $id, 'date' => $date, 'ticket' => $client->ticket));
+          // Send POST request
+          $response = drupal_http_request(
+            $client->service,
+            array(
+              'headers' => array('Content-Type' => 'application/x-www-form-urlencoded'),
+              'method' =>  'POST',
+              'data' => 'logoutRequest=' . urlencode($logout_request),
+            )
+          );
+          if (@$response->error) {
+            watchdog('error', 'Error in CAS logout Request - %code : %message', array('%code' => $response->code, '%error' => $response->error));
+          }
+        }
+        // Remove ticket
+      }
+      if ($expired_tickets) {
+        db_delete('cas_server_tickets')
+          ->condition('ticket', $expired_tickets, 'IN')
+          ->execute();
+      }
+    }
+}
+
+/**
+ * Implements hook_user_logout().
+ */
+function cas_server_user_logout($account) {
+  cas_server_logout_clients($account);
+}
diff --git a/drupal7/sites/all/modules/cas/cas_server.response.inc b/drupal7/sites/all/modules/cas/cas_server.response.inc
index 11ae532d..d280dd34 100644
--- a/drupal7/sites/all/modules/cas/cas_server.response.inc
+++ b/drupal7/sites/all/modules/cas/cas_server.response.inc
@@ -15,7 +15,7 @@
  */
 function theme_cas_service_validate_success($variables) {
   $cas_name = $variables['name'];
-  $output .= "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\n";
+  $output = "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\n";
   $output .= "<cas:authenticationSuccess>\n";
   $output .= "<cas:user>$cas_name</cas:user>\n";
   $output .= theme('cas_service_validate_attributes', $variables);
@@ -40,7 +40,7 @@ function theme_cas_service_validate_attributes($variables) {
   $style = $variables['style'];
 
   $output = '';
-  switch($style) {
+  switch ($style) {
     case 'jasig':
     default:
       $output .= "<cas:attributes>\n";
@@ -91,3 +91,21 @@ function theme_cas_service_validate_failure($variables) {
 
   return $output;
 }
+
+
+/**
+ * Generate the Single Sign Out request.
+ *
+ * @param unknown_type $variables
+ *   An associative array containing the key, date and logout id request
+ */
+function theme_cas_service_logout_request($variables) {
+  $id = $variables['id'];
+  $date = $variables['date'];
+  $ticket = $variables['ticket'];
+  $output = "<samlp:LogoutRequest xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol' ID='$id' Version='2.0' IssueInstant='$date'>\n";
+  $output .= "<saml:NameID xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>@NOT_USED@</saml:NameID>\n";
+  $output .= "<samlp:SessionIndex>$ticket</samlp:SessionIndex>\n";
+  $output .= "</samlp:LogoutRequest>\n";
+  return $output;
+}
diff --git a/drupal7/sites/all/modules/cas/includes/views/handlers/cas_handler_field_cas_name.inc b/drupal7/sites/all/modules/cas/includes/views/handlers/cas_handler_field_cas_name.inc
index 55822de9..f01cd571 100644
--- a/drupal7/sites/all/modules/cas/includes/views/handlers/cas_handler_field_cas_name.inc
+++ b/drupal7/sites/all/modules/cas/includes/views/handlers/cas_handler_field_cas_name.inc
@@ -1,4 +1,10 @@
 <?php
+
+/**
+ * @file
+ * Field handler to provide a list of CAS user names.
+ */
+
 /**
  * Field handler to provide a list of CAS user names.
  */
diff --git a/drupal7/sites/all/modules/cas/tests/cas_test.info b/drupal7/sites/all/modules/cas/tests/cas_test.info
index 8fd4fea8..ad6a00ce 100644
--- a/drupal7/sites/all/modules/cas/tests/cas_test.info
+++ b/drupal7/sites/all/modules/cas/tests/cas_test.info
@@ -8,9 +8,9 @@ hidden = TRUE
 ; code duplication we use several themes defined by that module.
 dependencies[] = cas_server
 
-; Information added by drupal.org packaging script on 2012-05-09
-version = "7.x-1.2"
+; Information added by Drupal.org packaging script on 2014-04-02
+version = "7.x-1.3"
 core = "7.x"
 project = "cas"
-datestamp = "1336595150"
+datestamp = "1396458247"
 
diff --git a/drupal7/sites/all/modules/cas/tests/cas_test.module b/drupal7/sites/all/modules/cas/tests/cas_test.module
index 78b7b9cd..28b83823 100644
--- a/drupal7/sites/all/modules/cas/tests/cas_test.module
+++ b/drupal7/sites/all/modules/cas/tests/cas_test.module
@@ -103,7 +103,7 @@ function cas_test_login() {
 
   if ($cas_user) {
     if (!is_array($cas_user)) {
-      $cas_user = array('name' => $cas_user);
+      $cas_user = array('name' => $cas_user, 'attributes' => array());
     }
     // Generate a ticket and redirect to the service URL with the login ticket.
     $ticket = _cas_test_ticket_generate($service, $cas_user);
@@ -128,6 +128,12 @@ function cas_test_login() {
  * Validate a ticket using the CAS 1.x protocol.
  */
 function cas_test_validate() {
+  // Prevent this page from being cached.
+  drupal_page_is_cacheable(FALSE);
+
+  // Set content type.
+  drupal_add_http_header('Content-Type', 'text/plain; charset=utf-8');
+
   //Obtain the ticket from the url and validate it.
   $ticket = $_GET['ticket'];
   $service = $_GET['service'];
@@ -148,10 +154,15 @@ function cas_test_validate() {
  * Validate a ticket using the CAS 2.0 protocol.
  */
 function cas_test_service_validate() {
+  // Prevent this page from being cached.
+  drupal_page_is_cacheable(FALSE);
+
+  // Set content type.
+  drupal_add_http_header('Content-Type', 'text/xml; charset=utf-8');
+
   $ticket = $_GET['ticket'];
   $service = $_GET['service'];
 
-  header('Content-type:', 'text/xml');
   if ($cas_user = _cas_test_ticket_validate($service, $ticket)) {
     print theme('cas_service_validate_success', $cas_user);
   }
@@ -234,7 +245,7 @@ function _cas_test_ticket_validate($service, $ticket) {
  *
  * @param $cas_user
  */
-function cas_test_single_sign_out($cas_name) {
+function cas_test_single_sign_out($cas_name, $double_encode=FALSE) {
   $sso = variable_get('cas_test_sso', array());
   foreach ($sso[$cas_name] as $service => $tickets) {
     foreach ($tickets as $ticket) {
@@ -243,6 +254,11 @@ function cas_test_single_sign_out($cas_name) {
         'logoutRequest' => t("<samlp:LogoutRequest ID=\"!id\" Version=\"2.0\" IssueInstant=\"!time\">\n<saml:NameID>@NOT_USED@</saml:NameID>\n<samlp:SessionIndex>!ticket</samlp:SessionIndex>\n</samlp:LogoutRequest>", array('!id' => user_password(10), '!time' => time(), '!ticket' => $ticket)),
       );
 
+      if ($double_encode) {
+        // Certain CAS servers urlencode the logoutRequest.
+        $data['logoutRequest'] = urlencode($data['logoutRequest']);
+      }
+
       // Sign out the user.
       $options = array(
         'method' => 'POST',
diff --git a/drupal7/sites/all/modules/print/icons/mail_icon.gif b/drupal7/sites/all/modules/print/icons/mail_icon.gif
index 515a047b..5a783b85 100644
Binary files a/drupal7/sites/all/modules/print/icons/mail_icon.gif and b/drupal7/sites/all/modules/print/icons/mail_icon.gif differ
diff --git a/drupal7/sites/all/modules/print/icons/pdf_icon.gif b/drupal7/sites/all/modules/print/icons/pdf_icon.gif
index 6ce5d1df..660595b9 100644
Binary files a/drupal7/sites/all/modules/print/icons/pdf_icon.gif and b/drupal7/sites/all/modules/print/icons/pdf_icon.gif differ
diff --git a/drupal7/sites/all/modules/print/icons/print_icon.gif b/drupal7/sites/all/modules/print/icons/print_icon.gif
index 12d2cf72..f49a90ab 100644
Binary files a/drupal7/sites/all/modules/print/icons/print_icon.gif and b/drupal7/sites/all/modules/print/icons/print_icon.gif differ
diff --git a/drupal7/sites/all/modules/print/print.info b/drupal7/sites/all/modules/print/print.info
index 6fa82c61..aba761ab 100644
--- a/drupal7/sites/all/modules/print/print.info
+++ b/drupal7/sites/all/modules/print/print.info
@@ -1,18 +1,14 @@
 name = "Printer-friendly pages"
-description = "Adds a printer-friendly version link to content and administrative pages."
-core=7.x
+description = "Generates a printer-friendly version of Drupal pages."
+core = 7.x
 package = "Printer, email and PDF versions"
-files[] = print.module
-files[] = print.admin.inc
-files[] = print.pages.inc
-files[] = print.install
-files[] = print.views.inc
 files[] = print_join_page_counter.inc
+dependencies[] = node
 configure = admin/config/user-interface/print
 
-; Information added by drupal.org packaging script on 2012-09-04
-version = "7.x-1.2"
+; Information added by Drupal.org packaging script on 2014-04-02
+version = "7.x-1.3"
 core = "7.x"
 project = "print"
-datestamp = "1346768900"
+datestamp = "1396426750"
 
diff --git a/drupal7/sites/all/modules/print/print.install b/drupal7/sites/all/modules/print/print.install
index 2a6f7f24..6406ceab 100644
--- a/drupal7/sites/all/modules/print/print.install
+++ b/drupal7/sites/all/modules/print/print.install
@@ -126,7 +126,7 @@ function print_schema() {
     'fields' => array(
       'path' => array(
         'type' => 'varchar',
-        'length' => 128,
+        'length' => 255,
         'not null' => TRUE,
         'description' => 'Page path',
       ),
@@ -175,3 +175,13 @@ function print_update_7101(&$sandbox) {
   $link_pos['help'] = 'help';
   variable_set('print_html_link_pos', $link_pos);
 }
+
+/**
+ * Increase size of the path field in the print_page_counter table
+ */
+function print_update_7103(&$sandbox) {
+  db_drop_primary_key('print_page_counter');
+  db_change_field('print_page_counter', 'path', 'path',
+    array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'description' => 'Page path'),
+    array('primary key' => array('path')));
+}
diff --git a/drupal7/sites/all/modules/print/print.module b/drupal7/sites/all/modules/print/print.module
index d799a826..43d3b609 100644
--- a/drupal7/sites/all/modules/print/print.module
+++ b/drupal7/sites/all/modules/print/print.module
@@ -263,6 +263,8 @@ function print_block_info() {
  * Implements hook_block_view().
  */
 function print_block_view($delta = '') {
+  $block = array();
+
   switch ($delta) {
     case 'print-links':
       $nid = preg_replace('!^node/!', '', $_GET['q']);
@@ -465,9 +467,15 @@ function print_node_insert($node) {
  */
 function print_node_update($node) {
   if (user_access('administer print') || user_access('node-specific print configuration')) {
-    if (!isset($node->print_display)) $node->print_display = variable_get('print_display_' . $node->type, PRINT_TYPE_SHOW_LINK_DEFAULT);
-    if (!isset($node->print_display_comment)) $node->print_display_comment = variable_get('print_display_comment_' . $node->type, PRINT_TYPE_COMMENT_LINK_DEFAULT);
-    if (!isset($node->print_display_urllist)) $node->print_display_urllist = variable_get('print_display_urllist_' . $node->type, PRINT_TYPE_URLLIST_DEFAULT);
+    if (!isset($node->print_display) || $node->print_display === NULL) {
+      $node->print_display = variable_get('print_display_' . $node->type, PRINT_TYPE_SHOW_LINK_DEFAULT);
+    }
+    if (!isset($node->print_display_comment) || $node->print_display_comment === NULL) {
+      $node->print_display_comment = variable_get('print_display_comment_' . $node->type, PRINT_TYPE_COMMENT_LINK_DEFAULT);
+    }
+    if (!isset($node->print_display_urllist) || $node->print_display_urllist === NULL) {
+      $node->print_display_urllist = variable_get('print_display_urllist_' . $node->type, PRINT_TYPE_URLLIST_DEFAULT);
+    }
 
     _print_node_conf_modify($node->nid, $node->print_display, $node->print_display_comment, $node->print_display_urllist);
   }
diff --git a/drupal7/sites/all/modules/print/print.pages.inc b/drupal7/sites/all/modules/print/print.pages.inc
index a7bd7345..f6c7a546 100644
--- a/drupal7/sites/all/modules/print/print.pages.inc
+++ b/drupal7/sites/all/modules/print/print.pages.inc
@@ -193,7 +193,7 @@ function _print_var_generator($node, $query = NULL, $message = NULL, $cid = NULL
   $print['favicon'] = theme_get_setting('toggle_favicon') ? "<link rel='shortcut icon' href='" . theme_get_setting('favicon') . "' type='image/x-icon' />\n" : '';
 
   if (!empty($print_css)) {
-    drupal_add_css(strtr($print_css, array('%t' => path_to_theme())));
+    drupal_add_css(strtr($print_css, array('%t' => drupal_get_path('theme', variable_get('theme_default')))));
   }
   else {
     drupal_add_css(drupal_get_path('module', 'print') . '/css/print.css');
@@ -254,6 +254,9 @@ function _print_var_generator($node, $query = NULL, $message = NULL, $cid = NULL
       $footer = $print_footer_user;
       break;
   }
+  // Delete the contextual links
+  $footer = preg_replace('!\s*<div class="contextual-links-wrapper">.*?</div>!sim', '', $footer);
+
   $print['footer_message'] = filter_xss_admin($footer);
 
   $published_site = variable_get('site_name', 0);
@@ -387,7 +390,7 @@ function _print_rewrite_urls($matches) {
           }
           // Because base href is the original page, change the link to
           // still be usable inside the print page
-          $matches[1] = str_replace($url, base_path() . $_GET['q'] . $url, $matches[1]);
+          $matches[1] = str_replace($url, check_plain(base_path() . $_GET['q'] . $url), $matches[1]);
         }
         else {
           // URL is relative, convert it into absolute URL
@@ -416,7 +419,7 @@ function _print_rewrite_urls($matches) {
     }
   }
 
-  return $ret;
+  return filter_xss_admin($ret);
 }
 
 /**
@@ -671,6 +674,9 @@ function _print_generate_path($path, $query = NULL, $format = PRINT_HTML_FORMAT,
   // Delete any links area
   $node->body = preg_replace('!\s*<div class="links">.*?</div>!sim', '', $node->body);
 
+  // Delete the contextual links also
+  $node->content = preg_replace('!\s*<div class="contextual-links-wrapper">.*?</div>!sim', '', $node->content);
+
   // Check URL list settings
   $_print_urls = _print_url_list_enabled($node, $format);
 
diff --git a/drupal7/sites/all/modules/print/print_join_page_counter.inc b/drupal7/sites/all/modules/print/print_join_page_counter.inc
index e988d6e1..fe73f002 100644
--- a/drupal7/sites/all/modules/print/print_join_page_counter.inc
+++ b/drupal7/sites/all/modules/print/print_join_page_counter.inc
@@ -11,13 +11,15 @@ class print_join_page_counter extends views_join {
   // PHP 4 doesn't call constructors of the base class automatically from a
   // constructor of a derived class. It is your responsibility to propagate
   // the call to constructors upstream where appropriate.
-  function construct($table, $left_table, $left_field, $field, $extra = array(), $type = 'LEFT') {
+  function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
     parent::construct($table, $left_table, $left_field, $field, $extra, $type);
   }
 
   function build_join($select_query, $table, $view_query) {
-    $this->left_field = "CONCAT('node/', " . $this->left_table . '.' . $this->left_field . ')';
-    $this->left_table = NULL;
+    if ($this->left_table) {
+      $this->left_field = "CONCAT('node/', $this->left_table.$this->left_field)";
+      $this->left_table = NULL;
+    }
     parent::build_join($select_query, $table, $view_query);
   }
 }
diff --git a/drupal7/sites/all/modules/print/print_mail/print_mail.inc b/drupal7/sites/all/modules/print/print_mail/print_mail.inc
index 43785a9c..dbfcf657 100644
--- a/drupal7/sites/all/modules/print/print_mail/print_mail.inc
+++ b/drupal7/sites/all/modules/print/print_mail/print_mail.inc
@@ -116,8 +116,8 @@ function print_mail_form($form, &$form_state) {
     '#required' => TRUE,
   );
   if (!empty($title)) {
-    // To prevent useless translation strings, try to translate only node titles
-    if (drupal_substr($path, 0, 5) == 'node/') {
+    // To prevent useless translation strings, try to translate only non-node titles
+    if (drupal_substr($path, 0, 5) != 'node/') {
       $title = t($title);
     }
 
@@ -150,10 +150,6 @@ function print_mail_form($form, &$form_state) {
     '#type' => 'submit',
     '#value' => t('Send email'),
   );
-  $form['btn_clear'] = array(
-    '#type' => 'markup',
-    '#markup' => '<input type="reset" name="clear" id="edit-btn-clear" value="' . t('Clear form') . '" class="form-submit" />',
-  );
   $form['btn_cancel'] = array(
     '#name' => 'cancel',
     '#type' => 'submit',
diff --git a/drupal7/sites/all/modules/print/print_mail/print_mail.info b/drupal7/sites/all/modules/print/print_mail/print_mail.info
index 4d62ebf3..e821e6aa 100644
--- a/drupal7/sites/all/modules/print/print_mail/print_mail.info
+++ b/drupal7/sites/all/modules/print/print_mail/print_mail.info
@@ -1,18 +1,13 @@
 name = "Send by email"
 description = "Provides the capability to send the web page by email"
-core=7.x
+core = 7.x
 package = "Printer, email and PDF versions"
 dependencies[] = print
-files[] = print_mail.module
-files[] = print_mail.inc
-files[] = print_mail.admin.inc
-files[] = print_mail.install
-files[] = print_mail.views.inc
 configure = admin/config/user-interface/print/email
 
-; Information added by drupal.org packaging script on 2012-09-04
-version = "7.x-1.2"
+; Information added by Drupal.org packaging script on 2014-04-02
+version = "7.x-1.3"
 core = "7.x"
 project = "print"
-datestamp = "1346768900"
+datestamp = "1396426750"
 
diff --git a/drupal7/sites/all/modules/print/print_mail/print_mail.install b/drupal7/sites/all/modules/print/print_mail/print_mail.install
index 1b91e057..69cc4646 100644
--- a/drupal7/sites/all/modules/print/print_mail/print_mail.install
+++ b/drupal7/sites/all/modules/print/print_mail/print_mail.install
@@ -115,7 +115,7 @@ function print_mail_schema() {
     'fields' => array(
       'path' => array(
         'type' => 'varchar',
-        'length' => 128,
+        'length' => 255,
         'not null' => TRUE,
         'description' => 'Page path',
       ),
@@ -201,3 +201,13 @@ function print_mail_update_7102(&$sandbox) {
   $link_pos['help'] = 'help';
   variable_set('print_mail_link_pos', $link_pos);
 }
+
+/**
+ * Increase size of the path field in the print_mail_page_counter table
+ */
+function print_mail_update_7103(&$sandbox) {
+  db_drop_primary_key('print_mail_page_counter');
+  db_change_field('print_mail_page_counter', 'path', 'path',
+    array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'description' => 'Page path'),
+    array('primary key' => array('path')));
+}
diff --git a/drupal7/sites/all/modules/print/print_mail/print_mail.module b/drupal7/sites/all/modules/print/print_mail/print_mail.module
index 517a9165..29bb2101 100644
--- a/drupal7/sites/all/modules/print/print_mail/print_mail.module
+++ b/drupal7/sites/all/modules/print/print_mail/print_mail.module
@@ -145,6 +145,8 @@ function print_mail_block_info() {
  * Implements hook_block_view().
  */
 function print_mail_block_view($delta = 0) {
+  $block = array();
+
       switch ($delta) {
       case 'print_mail-top':
         $block['subject'] = t('Most emailed');
@@ -278,9 +280,15 @@ function print_mail_node_insert($node) {
  */
 function print_mail_node_update($node) {
   if (user_access('administer print') || user_access('node-specific print configuration')) {
-    if (!isset($node->print_mail_display)) $node->print_mail_display = variable_get('print_mail_display_' . $node->type, PRINT_TYPE_SHOW_LINK_DEFAULT);
-    if (!isset($node->print_mail_display_comment)) $node->print_mail_display_comment = variable_get('print_mail_display_comment_' . $node->type, PRINT_TYPE_COMMENT_LINK_DEFAULT);
-    if (!isset($node->print_mail_display_urllist)) $node->print_mail_display_urllist = variable_get('print_mail_display_urllist_' . $node->type, PRINT_TYPE_URLLIST_DEFAULT);
+    if (!isset($node->print_mail_display) || $node->print_mail_display === NULL) {
+      $node->print_mail_display = variable_get('print_mail_display_' . $node->type, PRINT_TYPE_SHOW_LINK_DEFAULT);
+    }
+    if (!isset($node->print_mail_display_comment) || $node->print_mail_display_comment === NULL) {
+      $node->print_mail_display_comment = variable_get('print_mail_display_comment_' . $node->type, PRINT_TYPE_COMMENT_LINK_DEFAULT);
+    }
+    if (!isset($node->print_mail_display_urllist) || $node->print_mail_display_urllist === NULL) {
+      $node->print_mail_display_urllist = variable_get('print_mail_display_urllist_' . $node->type, PRINT_TYPE_URLLIST_DEFAULT);
+    }
 
     _print_mail_node_conf_modify($node->nid, $node->print_mail_display, $node->print_mail_display_comment, $node->print_mail_display_urllist);
   }
diff --git a/drupal7/sites/all/modules/print/print_pdf/print_pdf.admin.inc b/drupal7/sites/all/modules/print/print_pdf/print_pdf.admin.inc
index f10b5c74..581d6c0c 100644
--- a/drupal7/sites/all/modules/print/print_pdf/print_pdf.admin.inc
+++ b/drupal7/sites/all/modules/print/print_pdf/print_pdf.admin.inc
@@ -260,7 +260,7 @@ function print_pdf_settings() {
   else {
     $form['settings'] = array(
       '#type' => 'markup',
-      '#markup' => '<p>' . t("No PDF generation tool found! Please dowload a supported PHP PDF generation tool. Check this module's INSTALL.txt for more details.") . '</p>',
+      '#markup' => '<p>' . t("No PDF generation tool found! Please download a supported PHP PDF generation tool. Check this module's INSTALL.txt for more details.") . '</p>',
     );
   }
 
diff --git a/drupal7/sites/all/modules/print/print_pdf/print_pdf.class.inc b/drupal7/sites/all/modules/print/print_pdf/print_pdf.class.inc
index 9eaa8cf9..b6a4c530 100644
--- a/drupal7/sites/all/modules/print/print_pdf/print_pdf.class.inc
+++ b/drupal7/sites/all/modules/print/print_pdf/print_pdf.class.inc
@@ -12,8 +12,13 @@
 class PrintTCPDF extends TCPDF {
   public $footer;
 
+  // Display invisible link at the bottom of all pages.
+  public function setTcpdfLink($tcpdflink) {
+    $this->tcpdflink = $tcpdflink;
+  }
+
   // Page footer data
-  public function SetFooterData($arg = '') {
+  public function setFooterContent($arg = '') {
     $this->footer = $arg;
   }
 
diff --git a/drupal7/sites/all/modules/print/print_pdf/print_pdf.drush.inc b/drupal7/sites/all/modules/print/print_pdf/print_pdf.drush.inc
index 4ebe9968..dbaa13d3 100644
--- a/drupal7/sites/all/modules/print/print_pdf/print_pdf.drush.inc
+++ b/drupal7/sites/all/modules/print/print_pdf/print_pdf.drush.inc
@@ -13,12 +13,13 @@
 define('TCPDF_DOWNLOAD_URI', 'http://sourceforge.net/projects/tcpdf/files/latest');
 
 // URI to the the latest dompdf version.. Hardcoded version unfortunately
-define('DOMPDF_DOWNLOAD_URI', 'http://dompdf.googlecode.com/files/dompdf_0-6-0_beta3.tar.gz');
+define('DOMPDF_DOWNLOAD_URI', 'https://github.com/dompdf/dompdf/releases/download/v0.6.1/dompdf-0.6.1.zip');
 
 // wkhtmltopdf is a binary, requiring a different download for each platform
-define('WKHTMLTOPDF_AMD64_DOWNLOAD_URI', 'http://wkhtmltopdf.googlecode.com/files/wkhtmltopdf-0.11.0_rc1-static-amd64.tar.bz2');
-define('WKHTMLTOPDF_I386_DOWNLOAD_URI', 'http://wkhtmltopdf.googlecode.com/files/wkhtmltopdf-0.11.0_rc1-static-i386.tar.bz2');
-define('WKHTMLTOPDF_WIN_DOWNLOAD_URI', 'http://wkhtmltopdf.googlecode.com/files/wkhtmltox-0.11.0_rc1-installer.exe');
+define('WKHTMLTOPDF_AMD64_DOWNLOAD_URI', 'http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.0/wkhtmltox-linux-amd64_0.12.0-03c001d.tar.xz');
+define('WKHTMLTOPDF_I386_DOWNLOAD_URI', 'http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.0/wkhtmltox-linux-i386_0.12.0-03c001d.tar.xz');
+define('WKHTMLTOPDF_WIN64_DOWNLOAD_URI', 'http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.0/wkhtmltox-win64_0.12.0-03c001d.exe');
+define('WKHTMLTOPDF_WIN_DOWNLOAD_URI', 'http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.0/wkhtmltox-win32_0.12.0-03c001d.exe');
 define('WKHTMLTOPDF_OSX_DOWNLOAD_URI', 'http://wkhtmltopdf.googlecode.com/files/wkhtmltopdf-OSX-0.10.0_rc2-static.tar.bz2');
 
 /**
@@ -159,6 +160,10 @@ function _drush_print_pdf_download_extract($filename) {
         // Decompress the tar bz2 archive
         $arch_ret = drush_shell_exec('tar xjf ' . $filename);
         break;
+      case 'application/x-xz':
+        // Decompress the tar xz archive
+        $arch_ret = drush_shell_exec('tar xJf %s', $filename);
+        break;
     }
   }
   else {
diff --git a/drupal7/sites/all/modules/print/print_pdf/print_pdf.info b/drupal7/sites/all/modules/print/print_pdf/print_pdf.info
index d6725138..56f59e5d 100644
--- a/drupal7/sites/all/modules/print/print_pdf/print_pdf.info
+++ b/drupal7/sites/all/modules/print/print_pdf/print_pdf.info
@@ -1,18 +1,14 @@
 name = "PDF version"
 description = "Adds the capability to export pages as PDF."
-core=7.x
+core = 7.x
 package = "Printer, email and PDF versions"
 dependencies[] = print
-files[] = print_pdf.module
-files[] = print_pdf.admin.inc
-files[] = print_pdf.pages.inc
-files[] = print_pdf.install
-files[] = print_pdf.views.inc
+files[] = print_pdf.class.inc
 configure = admin/config/user-interface/print/pdf
 
-; Information added by drupal.org packaging script on 2012-09-04
-version = "7.x-1.2"
+; Information added by Drupal.org packaging script on 2014-04-02
+version = "7.x-1.3"
 core = "7.x"
 project = "print"
-datestamp = "1346768900"
+datestamp = "1396426750"
 
diff --git a/drupal7/sites/all/modules/print/print_pdf/print_pdf.install b/drupal7/sites/all/modules/print/print_pdf/print_pdf.install
index c36eac51..13032b5e 100644
--- a/drupal7/sites/all/modules/print/print_pdf/print_pdf.install
+++ b/drupal7/sites/all/modules/print/print_pdf/print_pdf.install
@@ -103,7 +103,7 @@ function print_pdf_schema() {
     'fields' => array(
       'path' => array(
         'type' => 'varchar',
-        'length' => 128,
+        'length' => 255,
         'not null' => TRUE,
         'description' => 'Page path',
       ),
@@ -155,3 +155,13 @@ function print_pdf_update_7101(&$sandbox) {
   $link_pos['help'] = 'help';
   variable_set('print_pdf_link_pos', $link_pos);
 }
+
+/**
+ * Increase size of the path field in the print_pdf_page_counter table
+ */
+function print_pdf_update_7105(&$sandbox) {
+  db_drop_primary_key('print_pdf_page_counter');
+  db_change_field('print_pdf_page_counter', 'path', 'path',
+    array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'description' => 'Page path'),
+    array('primary key' => array('path')));
+}
diff --git a/drupal7/sites/all/modules/print/print_pdf/print_pdf.module b/drupal7/sites/all/modules/print/print_pdf/print_pdf.module
index 3ced06b4..de6500e2 100644
--- a/drupal7/sites/all/modules/print/print_pdf/print_pdf.module
+++ b/drupal7/sites/all/modules/print/print_pdf/print_pdf.module
@@ -170,6 +170,8 @@ function print_pdf_block_info() {
  * Implements hook_block_view().
  */
 function print_pdf_block_view($delta = 0) {
+  $block = array();
+
       switch ($delta) {
       case 'print_pdf-top':
         $block['subject'] = t('Most PDFd');
@@ -408,9 +410,15 @@ function print_pdf_node_insert($node) {
  */
 function print_pdf_node_update($node) {
   if (user_access('administer print') || user_access('node-specific print configuration')) {
-    if (!isset($node->print_pdf_display)) $node->print_pdf_display = variable_get('print_pdf_display_' . $node->type, PRINT_TYPE_SHOW_LINK_DEFAULT);
-    if (!isset($node->print_pdf_display_comment)) $node->print_pdf_display_comment = variable_get('print_pdf_display_comment_' . $node->type, PRINT_TYPE_COMMENT_LINK_DEFAULT);
-    if (!isset($node->print_pdf_display_urllist)) $node->print_pdf_display_urllist = variable_get('print_pdf_display_urllist_' . $node->type, PRINT_TYPE_URLLIST_DEFAULT);
+    if (!isset($node->print_pdf_display) || $node->print_pdf_display === NULL) {
+      $node->print_pdf_display = variable_get('print_pdf_display_' . $node->type, PRINT_TYPE_SHOW_LINK_DEFAULT);
+    }
+    if (!isset($node->print_pdf_display_comment) || $node->print_pdf_display_comment === NULL) {
+      $node->print_pdf_display_comment = variable_get('print_pdf_display_comment_' . $node->type, PRINT_TYPE_COMMENT_LINK_DEFAULT);
+    }
+    if (!isset($node->print_pdf_display_urllist) || $node->print_pdf_display_urllist === NULL) {
+      $node->print_pdf_display_urllist = variable_get('print_pdf_display_urllist_' . $node->type, PRINT_TYPE_URLLIST_DEFAULT);
+    }
 
     _print_pdf_node_conf_modify($node->nid, $node->print_pdf_display, $node->print_pdf_display_comment, $node->print_pdf_display_urllist);
   }
@@ -675,19 +683,22 @@ function _print_pdf_tcpdf_version() {
   require_once(DRUPAL_ROOT . '/' . $print_pdf_pdf_tool);
 
   // Hide warnings, as some TCPDF constants may still be undefined
-  @$pdf = new TCPDF();
+  if (class_exists('TCPDF')) {
+    @$pdf = new TCPDF();
 
-  if (method_exists($pdf, 'getTCPDFVersion')) {
-    return $pdf->getTCPDFVersion();
-  }
-  elseif (defined('PDF_PRODUCER')) {
-    sscanf(PDF_PRODUCER, "TCPDF %s", $version);
+    if (class_exists('TCPDF_STATIC')) {
+      return TCPDF_STATIC::getTCPDFVersion();
+    }
+    elseif (method_exists($pdf, 'getTCPDFVersion')) {
+      return $pdf->getTCPDFVersion();
+    }
+    elseif (defined('PDF_PRODUCER')) {
+      sscanf(PDF_PRODUCER, "TCPDF %s", $version);
 
-    return $version;
-  }
-  else {
-    return 'unknown';
+      return $version;
+    }
   }
+  return 'unknown';
 }
 
 /**
diff --git a/drupal7/sites/all/modules/print/print_pdf/print_pdf.pages.inc b/drupal7/sites/all/modules/print/print_pdf/print_pdf.pages.inc
index 50103ec7..e8e1a05a 100644
--- a/drupal7/sites/all/modules/print/print_pdf/print_pdf.pages.inc
+++ b/drupal7/sites/all/modules/print/print_pdf/print_pdf.pages.inc
@@ -44,12 +44,12 @@ function print_pdf_controller() {
 
     $pdf_filename = variable_get('print_pdf_filename', PRINT_PDF_FILENAME_DEFAULT);
     if (!empty($pdf_filename) && !empty($node)) {
-      $pdf_filename = token_replace($pdf_filename, array('node' => $node));
+      $pdf_filename = token_replace($pdf_filename, array('node' => $node), array('clear' => TRUE));
     }
     else {
-      $pdf_filename = token_replace($pdf_filename, array('site'));
-      if (empty($pdf_filename) || count(token_scan($pdf_filename))) {
-        // If there are still tokens, use a fallback solution
+      $pdf_filename = token_replace($pdf_filename, array('site'), array('clear' => TRUE));
+      if (empty($pdf_filename)) {
+        // If empty, use a fallback solution
         $pdf_filename = str_replace('/', '_', $path);
       }
     }
@@ -179,11 +179,11 @@ function _print_pdf_dompdf($print, $html, $filename = NULL) {
   $print_pdf_content_disposition = variable_get('print_pdf_content_disposition', PRINT_PDF_CONTENT_DISPOSITION_DEFAULT);
 
   if (variable_get('print_pdf_autoconfig', PRINT_PDF_AUTOCONFIG_DEFAULT)) {
-    define("DOMPDF_ENABLE_PHP", FALSE);
-    define("DOMPDF_ENABLE_REMOTE", TRUE);
-    define("DOMPDF_TEMP_DIR", file_directory_temp());
-    define("DOMPDF_UNICODE_ENABLED", variable_get('print_pdf_dompdf_unicode', PRINT_PDF_DOMPDF_UNICODE_DEFAULT));
-    define("DOMPDF_FONT_CACHE", drupal_realpath('public://' . PRINT_PDF_DOMPDF_CACHE_DIR_DEFAULT . '/fonts/'));
+    if (!defined('DOMPDF_ENABLE_PHP')) define("DOMPDF_ENABLE_PHP", FALSE);
+    if (!defined('DOMPDF_ENABLE_REMOTE')) define("DOMPDF_ENABLE_REMOTE", TRUE);
+    if (!defined('DOMPDF_TEMP_DIR')) define("DOMPDF_TEMP_DIR", file_directory_temp());
+    if (!defined('DOMPDF_UNICODE_ENABLED')) define("DOMPDF_UNICODE_ENABLED", variable_get('print_pdf_dompdf_unicode', PRINT_PDF_DOMPDF_UNICODE_DEFAULT));
+    if (!defined('DOMPDF_FONT_CACHE')) define("DOMPDF_FONT_CACHE", drupal_realpath('public://' . PRINT_PDF_DOMPDF_CACHE_DIR_DEFAULT . '/fonts/'));
   }
 
   require_once(DRUPAL_ROOT . '/' . $print_pdf_pdf_tool);
@@ -235,6 +235,10 @@ function _print_pdf_dompdf($print, $html, $filename = NULL) {
   }
   $html = htmlspecialchars_decode(htmlentities($html, ENT_NOQUOTES, 'ISO-8859-1'), ENT_NOQUOTES);
   }
+  else {
+    // Otherwise, ensure the content is properly formatted Unicode.
+    $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
+  }
 
   // Must get rid of tbody (dompdf goes into recursion)
   $html = preg_replace('!<tbody[^>]*?>|</tbody>!i', '', $html);
@@ -273,15 +277,15 @@ function _print_pdf_tcpdf($print, $html, $filename = NULL) {
   $pdf_tool_path = realpath(dirname($print_pdf_pdf_tool));
 
   if (variable_get('print_pdf_autoconfig', PRINT_PDF_AUTOCONFIG_DEFAULT)) {
-    define('K_TCPDF_EXTERNAL_CONFIG', TRUE);
-    define('K_PATH_MAIN', dirname($_SERVER['SCRIPT_FILENAME']));
-    define('K_PATH_URL', $base_url);
-    define('K_PATH_FONTS', $pdf_tool_path . '/fonts/');
-    define('K_PATH_CACHE', drupal_realpath('public://' . PRINT_PDF_TCPDF_CACHE_DIR_DEFAULT . '/cache/'));
-    define('K_PATH_IMAGES', '');
-    define('K_BLANK_IMAGE', $pdf_tool_path . '/images/_blank.png');
-    define('K_CELL_HEIGHT_RATIO', 1.25);
-    define('K_SMALL_RATIO', 2/3);
+    if (!defined('K_TCPDF_EXTERNAL_CONFIG')) define('K_TCPDF_EXTERNAL_CONFIG', TRUE);
+    if (!defined('K_PATH_MAIN')) define('K_PATH_MAIN', dirname($_SERVER['SCRIPT_FILENAME']));
+    if (!defined('K_PATH_URL')) define('K_PATH_URL', $base_url);
+    if (!defined('K_PATH_FONTS')) define('K_PATH_FONTS', $pdf_tool_path . '/fonts/');
+    if (!defined('K_PATH_CACHE')) define('K_PATH_CACHE', drupal_realpath('public://' . PRINT_PDF_TCPDF_CACHE_DIR_DEFAULT . '/cache') . '/');
+    if (!defined('K_PATH_IMAGES')) define('K_PATH_IMAGES', '');
+    if (!defined('K_BLANK_IMAGE')) define('K_BLANK_IMAGE', $pdf_tool_path . '/images/_blank.png');
+    if (!defined('K_CELL_HEIGHT_RATIO')) define('K_CELL_HEIGHT_RATIO', 1.25);
+    if (!defined('K_SMALL_RATIO')) define('K_SMALL_RATIO', 2/3);
   }
 
   // Try to use local file access for image files
@@ -291,7 +295,7 @@ function _print_pdf_tcpdf($print, $html, $filename = NULL) {
   $pattern = "!<img\s[^>]*?src\s*?=\s*?['\"]?{$base_url}[^>]*?>!is";
   $html = preg_replace_callback($pattern, create_function('$matches', 'return html_entity_decode($matches[0], ENT_QUOTES);'), $html);
   // Remove queries from the image URL
-  $pattern = "!(<img\s[^>]*?src\s*?=\s*?['\"]?{$base_url}[^>]*?)(?:%3F|\?)[\w=&]+([^>]*?>)!is";
+  $pattern = "!(<img\s[^>]*?src\s*?=\s*?['\"]?{$base_url}[^>]*?)(?:%3F|\?)[^\s'\"]+([^>]*?>)!is";
   $html = preg_replace($pattern, '$1$2', $html);
 
   require_once(DRUPAL_ROOT . '/' . $print_pdf_pdf_tool);
@@ -315,6 +319,7 @@ function _print_pdf_tcpdf($print, $html, $filename = NULL) {
   $pdf->SetTitle(html_entity_decode($print['title'], ENT_QUOTES, 'UTF-8'));
   $pdf->setPDFVersion('1.6');
   $pdf->setFontSubsetting(variable_get('print_pdf_font_subsetting', PRINT_PDF_FONT_SUBSETTING_DEFAULT));
+  $pdf->setTcpdfLink(false);
 
   if ($language->direction == LANGUAGE_RTL) {
     $pdf->setRTL(TRUE);
@@ -367,7 +372,7 @@ function _print_pdf_wkhtmltopdf($print, $html, $filename = NULL) {
   $dpi = 96;
 
   if (!empty($print_pdf_wkhtmltopdf_options)) {
-    $print_pdf_wkhtmltopdf_options = token_replace($print_pdf_wkhtmltopdf_options, array('node' => $print['node']));
+    $print_pdf_wkhtmltopdf_options = token_replace($print_pdf_wkhtmltopdf_options, array('node' => $print['node']), array('clear' => TRUE));
   }
 
   $version = _print_pdf_wkhtmltopdf_version();
@@ -405,7 +410,7 @@ function _print_pdf_wkhtmltopdf($print, $html, $filename = NULL) {
       if (empty($error)) {
         $error = 'No stderr output available.';
       }
-      watchdog('print_pdf', "wkhtmltopdf (returned $retval): $error");
+      watchdog('print_pdf', 'wkhtmltopdf [%cmd] (returned %ret): %error', array('%cmd' => $cmd, '%ret' => $retval, '%error' => $error));
     }
   }
 
@@ -431,7 +436,7 @@ function _print_pdf_wkhtmltopdf($print, $html, $filename = NULL) {
   }
   else {
     drupal_set_message(t('Unable to generate PDF file.'), 'error');
-    drupal_goto($meta['url']);
+    drupal_goto($print['url']);
     return NULL;
   }
 }
@@ -445,38 +450,43 @@ function _print_pdf_wkhtmltopdf($print, $html, $filename = NULL) {
  */
 function theme_print_pdf_dompdf_footer($vars) {
   preg_match('!<div class="print-footer">(.*?)</div>!si', $vars['html'], $tpl_footer);
-  $html = str_replace($tpl_footer[0], '', $vars['html']);
 
-  $text = '<script type="text/php">
-    if (isset($pdf)) {
-      $font = Font_Metrics::get_font("verdana");;
-      $size = 10;
-      $color = array(0,0,0);
-      $text_height = Font_Metrics::get_font_height($font, $size);
+  if (isset($tpl_footer[1])) {
+    $html = str_replace($tpl_footer[0], '', $vars['html']);
 
-      $w = $pdf->get_width();
-      $h = $pdf->get_height();
+    $text = '<script type="text/php">
+      if (isset($pdf)) {
+        $font = Font_Metrics::get_font("verdana");;
+        $size = 10;
+        $color = array(0,0,0);
+        $text_height = Font_Metrics::get_font_height($font, $size);
 
-      $footer = $pdf->open_object();
+        $w = $pdf->get_width();
+        $h = $pdf->get_height();
 
-      // Draw a line along the bottom
-      $y = $h - 25;
-      $pdf->line(15, $y, $w - 15, $y, $color, 1);
+        $footer = $pdf->open_object();
 
-      $y += $text_height / 2;
-      $pdf->page_text(15, $y, \'' . addslashes(strip_tags($tpl_footer[1])) . '\', $font, $size, $color);
+        // Draw a line along the bottom
+        $y = $h - 25;
+        $pdf->line(15, $y, $w - 15, $y, $color, 1);
 
-      $pdf->close_object();
-      $pdf->add_object($footer, "all");
+        $y += $text_height / 2;
+        $pdf->page_text(15, $y, \'' . addslashes(strip_tags($tpl_footer[1])) . '\', $font, $size, $color);
 
-      // Center the text
-      $width = Font_Metrics::get_text_width("Page 1 of 2", $font, $size);
-      $pagenumtxt = t("Page !n of !total", array("!n" => "{PAGE_NUM}", "!total" => "{PAGE_COUNT}"));
-      $pdf->page_text($w - 15 - $width, $y, $pagenumtxt, $font, $size, $color);
-    }
-  </script>';
+        $pdf->close_object();
+        $pdf->add_object($footer, "all");
 
-  return str_replace("<body>", "<body>" . $text, $html);
+        // Center the text
+        $width = Font_Metrics::get_text_width("Page 1 of 2", $font, $size);
+        $pagenumtxt = t("Page !n of !total", array("!n" => "{PAGE_NUM}", "!total" => "{PAGE_COUNT}"));
+        $pdf->page_text($w - 15 - $width, $y, $pagenumtxt, $font, $size, $color);
+      }
+    </script>';
+
+    return str_replace("<body>", "<body>" . $text, $html);
+  } else {
+    return $vars['html'];
+  }
 }
 
 /**
@@ -589,15 +599,17 @@ function theme_print_pdf_tcpdf_content($vars) {
 function theme_print_pdf_tcpdf_footer($vars) {
   $pdf = $vars['pdf'];
   preg_match('!<div class="print-footer">(.*?)</div>!si', $vars['html'], $tpl_footer);
-  $footer = trim(preg_replace('!</?div[^>]*?>!i', '', $tpl_footer[1]));
-
-  // set footer font
-  $vars['font'][2] *= 0.8;
-  $pdf->setFooterFont($vars['font']);
-  // set footer margin
-  $pdf->SetFooterMargin(10);
-  // set footer data
-  $pdf->SetFooterData($footer);
+  if (isset($tpl_footer[1])) {
+    $footer = trim(preg_replace('!</?div[^>]*?>!i', '', $tpl_footer[1]));
+
+    // set footer font
+    $vars['font'][2] *= 0.8;
+    $pdf->setFooterFont($vars['font']);
+    // set footer margin
+    $pdf->SetFooterMargin(10);
+    // set footer data
+    $pdf->setFooterContent($footer);
+  }
 
   return $pdf;
 }
diff --git a/drupal7/sites/all/modules/print/print_pdf/print_pdf.views.inc b/drupal7/sites/all/modules/print/print_pdf/print_pdf.views.inc
index f3dc6905..5068c1fd 100644
--- a/drupal7/sites/all/modules/print/print_pdf/print_pdf.views.inc
+++ b/drupal7/sites/all/modules/print/print_pdf/print_pdf.views.inc
@@ -33,7 +33,7 @@ function print_pdf_views_data() {
     'left_field' => 'nid',
     'field' => 'path',
 //    'type' => 'INNER',
-    'handler' => 'print_pdf_join_page_counter',
+    'handler' => 'print_join_page_counter',
   );
 
   // print_pdf_node_conf fields
diff --git a/drupal7/sites/all/modules/taxonomy_menu/taxonomy_menu.batch.inc b/drupal7/sites/all/modules/taxonomy_menu/taxonomy_menu.batch.inc
index 10891865..1efec70e 100644
--- a/drupal7/sites/all/modules/taxonomy_menu/taxonomy_menu.batch.inc
+++ b/drupal7/sites/all/modules/taxonomy_menu/taxonomy_menu.batch.inc
@@ -40,7 +40,7 @@
  *
  */
 function _taxonomy_menu_insert_link_items_batch($vid) {
-  $terms = taxonomy_get_tree($vid);
+  $terms = taxonomy_get_tree($vid, 0, NULL, TRUE);
   $menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $vid), FALSE);
 
   $batch = array(
diff --git a/drupal7/sites/all/modules/taxonomy_menu/taxonomy_menu.info b/drupal7/sites/all/modules/taxonomy_menu/taxonomy_menu.info
index 80b82a79..295734e2 100644
--- a/drupal7/sites/all/modules/taxonomy_menu/taxonomy_menu.info
+++ b/drupal7/sites/all/modules/taxonomy_menu/taxonomy_menu.info
@@ -10,9 +10,9 @@ files[] = taxonomy_menu.module
 files[] = taxonomy_menu.test
 files[] = taxonomy_menu.install
 
-; Information added by drupal.org packaging script on 2012-09-06
-version = "7.x-1.4"
+; Information added by Drupal.org packaging script on 2014-04-01
+version = "7.x-1.5"
 core = "7.x"
 project = "taxonomy_menu"
-datestamp = "1346896425"
+datestamp = "1396359247"
 
diff --git a/drupal7/sites/all/modules/taxonomy_menu/taxonomy_menu.module b/drupal7/sites/all/modules/taxonomy_menu/taxonomy_menu.module
index 3d610990..7bcea712 100644
--- a/drupal7/sites/all/modules/taxonomy_menu/taxonomy_menu.module
+++ b/drupal7/sites/all/modules/taxonomy_menu/taxonomy_menu.module
@@ -6,10 +6,10 @@
  */
 
 //include the database layer
-require_once(drupal_get_path('module', 'taxonomy_menu') . '/taxonomy_menu.database.inc');
+require_once(DRUPAL_ROOT . '/' . drupal_get_path('module', 'taxonomy_menu') . '/taxonomy_menu.database.inc');
 
 //include the batch functions
-require_once(drupal_get_path('module', 'taxonomy_menu') . '/taxonomy_menu.batch.inc');
+require_once(DRUPAL_ROOT . '/' . drupal_get_path('module', 'taxonomy_menu') . '/taxonomy_menu.batch.inc');
 
 /**
  * Implements hook_form_alter().
@@ -481,7 +481,12 @@ function _taxonomy_menu_taxonomy_termapi_helper($term, $operation) {
  */
 function _taxonomy_menu_nodeapi_helper($op, $terms = array(), $node) {
   foreach ($terms as $key => $tid) {
-    $term = taxonomy_term_load($tid);
+
+    // taxonomy_term_load($tid) return FALSE if the term was not found
+    // if taxonomy $term is false, then go to the next $term
+    if (!$term = taxonomy_term_load($tid)) {
+      continue;
+    }
 
     // update the menu for each term if necessary
     $menu_name = variable_get(_taxonomy_menu_build_variable('vocab_menu', $term->vid), FALSE);
@@ -754,7 +759,7 @@ function taxonomy_menu_path_default($vid, $tid) {
   }
   else {
     $path = 'taxonomy/term/' . $tid;
-    if (variable_get(_taxonomy_menu_build_variable('display_decendants', $vid), FALSE)) {
+    if (variable_get(_taxonomy_menu_build_variable('display_descendants', $vid), FALSE)) {
       // Use 'all' at the end of the path
       if (variable_get(_taxonomy_menu_build_variable('end_all', $vid), FALSE)) {
         $path .= '/all';
diff --git a/drupal7/sites/all/modules/webform_validation/webform_validation.admin.inc b/drupal7/sites/all/modules/webform_validation/webform_validation.admin.inc
index 2f1ed197..e1b0b542 100644
--- a/drupal7/sites/all/modules/webform_validation/webform_validation.admin.inc
+++ b/drupal7/sites/all/modules/webform_validation/webform_validation.admin.inc
@@ -10,9 +10,12 @@
  */
 function webform_validation_manage($node) {
   $rules = webform_validation_get_webform_rules($node);
-  $output = '';
-  $output .= theme('webform_validation_manage_overview', array('rules' => $rules, 'node' => $node));
-  $output .= theme('webform_validation_manage_add_rule', array('nid' => $node->nid));
+  $output = array();
+  $output['webform_validation_manage_overview_form'] = drupal_get_form('webform_validation_manage_overview_form', $rules, $node);
+  $output['webform_validation_manage_add_rule'] = array(
+    '#theme' => 'webform_validation_manage_add_rule',
+    '#nid' => $node->nid,
+  );
   return $output;
 }
 
@@ -27,48 +30,126 @@ function webform_validation_get_webform_rules($node) {
   return $rules;
 }
 
+
 /**
- * Themable function to list the rules assigned to a webform
+ * Themable function to list and re-order the rules assigned to a webform
  */
-function theme_webform_validation_manage_overview($variables) {
-  $rules = $variables['rules'];
-  $node = $variables['node'];
+function theme_webform_validation_manage_overview_form($variables) {
+  $form = $variables['form'];
+  $header = array(t('Rule name'), t('Validator'), t('Components'), t('Weight'), array(
+    'data' => t('Operations'),
+    'colspan' => 2,
+  ));
 
-  $header = array(t('Rule name'), t('Validator'), t('Components'), array(
-      'data' => t('Operations'),
-      'colspan' => 2,
-    ));
-  $validators = webform_validation_get_validators_selection();
-  if (!empty($rules)) {
-    foreach ($rules as $rule) {
-      $component_info = webform_validation_rule_components_basic($rule['components']);
-      $row = array();
+  $rows = array();
+  foreach (element_children($form) as $rule) {
+    $row = array();
+    foreach (element_children($form[$rule]) as $item) {
+      //unset the titles of the form elements, since we are displaying them in a table with a header.
+      unset($form[$rule][$item]['#title']);
+      //add a class to the weight field
+      $form[$rule]['weight']['#attributes']['class'] = array('ruleid-weight');
       $row[] = array(
-        'data' => check_plain($rule['rulename']),
+        'data' => drupal_render($form[$rule][$item]),
       );
-      $row[] = array(
-        'data' => $validators[$rule['validator']],
-      );
-      $row[] = array(
-        'data' => theme('item_list', array('items' => $component_info)),
-      );
-      $row[] = array(
-        'data' => l(t('Edit'), 'node/' . $node->nid . '/webform/validation/edit/' . $rule['validator'] . '/' . $rule['ruleid'], array("query" => drupal_get_destination())),
-      );
-      $row[] = array(
-        'data' => l(t('Delete'), 'node/' . $node->nid . '/webform/validation/delete/' . $rule['ruleid'], array("query" => drupal_get_destination())),
+    }
+    if (count($row) > 1) {
+      $rows[] = array(
+        'data' => $row,
+        'class' => array('draggable'),
       );
-      $rows[] = $row;
+    }
+    // Hide any fieldsets, since we are displaying the form in a table.
+    if (isset($form[$rule]['#type']) && $form[$rule]['#type'] === 'fieldset') {
+      hide($form[$rule]);
     }
   }
-  else {
+  $drag = TRUE;
+  if (!$rows) {
+    $drag = FALSE;
     $rows[][] = array(
       'data' => t('No validation rules available.'),
       'colspan' => 5,
     );
   }
+  $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'webform-validation-overview-form')));
+  $output .= drupal_render_children($form);
+  if ($drag) {
+    drupal_add_tabledrag('webform-validation-overview-form', 'order', 'sibling', 'ruleid-weight');
+  }
+  return $output;
+}
+
+/**
+ * Form to list and reorder the rules assigned to a webform
+ */
+function webform_validation_manage_overview_form($form, &$form_state, $rules, $node) {
+  $form = array();
+  $form['#tree'] = TRUE;
+  $validators = webform_validation_get_validators_selection();
 
-  return theme('table', array('header' => $header, 'rows' => $rows));
+  foreach ($rules as $rule) {
+    $component_info = webform_validation_rule_components_basic($rule['components']);
+    $form[$rule['ruleid']] = array(
+      '#type' => 'fieldset',
+    );
+    $form[$rule['ruleid']]['name'] = array(
+      '#type' => 'item',
+      '#title' => t('Name'),
+      '#markup' => check_plain($rule['rulename']),
+    );
+    $form[$rule['ruleid']]['validator'] = array(
+      '#type' => 'item',
+      '#title' => t('Validator'),
+      '#markup' => $validators[$rule['validator']],
+    );
+    $form[$rule['ruleid']]['components'] = array(
+      '#type' => 'item',
+      '#title' => t('Components'),
+      '#markup' => theme('item_list', array('items' => $component_info)),
+    );
+    $form[$rule['ruleid']]['weight'] = array(
+      '#type' => 'weight',
+      '#title' => t('Weight'),
+      '#default_value' => $rule['weight'],
+      '#description' => t('Optional. By changing the order of validation rules, you can use code that relies on earlier validation functions being completed.'),
+    );
+    $form[$rule['ruleid']]['actions'] = array('#type' => 'actions');
+    $form[$rule['ruleid']]['actions']['edit'] = array(
+      '#type' => 'item',
+      '#markup' => l(t('Edit'), 'node/' . $node->nid . '/webform/validation/edit/' . $rule['validator'] . '/' . $rule['ruleid'], array("query" => drupal_get_destination())),
+    );
+    $form[$rule['ruleid']]['actions']['delete'] = array(
+      '#type' => 'item',
+      '#markup' => l(t('Delete'), 'node/' . $node->nid . '/webform/validation/delete/' . $rule['ruleid'], array("query" => drupal_get_destination())),
+    );
+  }
+  if (count($rules) > 1) {
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save rule order'),
+    );
+  }
+
+  return $form;
+}
+
+/**
+ * Submit function for rule overview form.
+ */
+function webform_validation_manage_overview_form_submit($form, $form_state) {
+  //Save the rule weights.
+  foreach ($form_state['values'] as $ruleid => $value) {
+    if (is_numeric($ruleid)) {
+      $update = db_update('webform_validation_rule')
+        ->fields(array(
+          'weight' => $value['weight'],
+        ))
+        ->condition('ruleid', $ruleid)
+        ->execute();
+    }
+  }
+  drupal_set_message(t('The order of the validation rules has been saved.'));
 }
 
 /**
@@ -256,6 +337,6 @@ function webform_validation_delete_rule($form, &$form_state, $rule) {
  */
 function webform_validation_delete_rule_submit($form, &$form_state) {
   $ruleid = $form_state['values']['ruleid'];
-  webform_dynamic_delete_rule($ruleid);
   module_invoke_all('webform_validation', 'rule', 'delete', $ruleid);
+  webform_dynamic_delete_rule($ruleid);
 }
diff --git a/drupal7/sites/all/modules/webform_validation/webform_validation.info b/drupal7/sites/all/modules/webform_validation/webform_validation.info
index 7887ba01..3dba047f 100644
--- a/drupal7/sites/all/modules/webform_validation/webform_validation.info
+++ b/drupal7/sites/all/modules/webform_validation/webform_validation.info
@@ -9,9 +9,9 @@ files[] = webform_validation.module
 files[] = webform_validation.rules.inc
 files[] = webform_validation.validators.inc
 
-; Information added by Drupal.org packaging script on 2014-02-11
-version = "7.x-1.4"
+; Information added by Drupal.org packaging script on 2014-03-31
+version = "7.x-1.5"
 core = "7.x"
 project = "webform_validation"
-datestamp = "1392157410"
+datestamp = "1396296845"
 
diff --git a/drupal7/sites/all/modules/webform_validation/webform_validation.install b/drupal7/sites/all/modules/webform_validation/webform_validation.install
index 504e691a..26a91fc0 100644
--- a/drupal7/sites/all/modules/webform_validation/webform_validation.install
+++ b/drupal7/sites/all/modules/webform_validation/webform_validation.install
@@ -58,6 +58,13 @@ function webform_validation_schema() {
         'not null' => TRUE,
         'default' => 0,
       ),
+      'weight' => array(
+        'description' => 'Weight of the rule order.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => FALSE,
+        'default' => 0,
+      ),
     ),
     'primary key' => array('ruleid'),
     'indexes' => array(
@@ -104,15 +111,14 @@ function webform_validation_update_1() {
     $range = $row->data;
     $range = _webform_validation_update_range_syntax($range);
     db_update('webform_validation_rule')
-    ->fields(array(
-      'data' => $range,
-    ))
-    ->condition('ruleid', $row->ruleid, '=')
-    ->execute();
+      ->fields(array(
+        'data' => $range,
+      ))
+      ->condition('ruleid', $row->ruleid, '=')
+      ->execute();
   }
   if ($has_numeric_validator) {
-    $warning_message = t('The numeric validation rule range syntax has changed with this release. Existing numeric validation rules were found and updated. Please verify that they still work as expected!');
-    drupal_set_message($warning_message, 'warning');
+    drupal_set_message(t('The numeric validation rule range syntax has changed with this release. Existing numeric validation rules were found and updated. Please verify that they still work as expected!'), 'warning');
   }
   return $ret;
 }
@@ -177,3 +183,30 @@ function webform_validation_update_7101() {
     }
   }
 }
+
+/**
+ * Rename oneoftwo and oneofseveral to someofseveral and default data to 1.
+ */
+function webform_validation_update_7104() {
+  db_update('webform_validation_rule')
+    ->fields(array(
+      'validator' => 'someofseveral',
+      'data' => 1,
+    ))
+    ->condition('validator', array('oneoftwo', 'oneofseveral'), 'IN')
+    ->execute();
+}
+
+/**
+ * Add a weight field to webform_validation rules, so they can be sorted.
+ */
+function webform_validation_update_7105() {
+  $spec = array(
+    'description' => 'Weight of the rule order.',
+    'type' => 'int',
+    'not null' => TRUE,
+    'unsigned' => FALSE,
+    'default' => 0,
+  );
+  db_add_field('webform_validation_rule', 'weight', $spec);
+}
diff --git a/drupal7/sites/all/modules/webform_validation/webform_validation.module b/drupal7/sites/all/modules/webform_validation/webform_validation.module
index 15f3cebb..c6688b65 100644
--- a/drupal7/sites/all/modules/webform_validation/webform_validation.module
+++ b/drupal7/sites/all/modules/webform_validation/webform_validation.module
@@ -76,11 +76,8 @@ function webform_validation_theme() {
         'nid' => NULL,
       ),
     ),
-    'webform_validation_manage_overview' => array(
-      'variables' => array(
-        'rules' => NULL,
-        'node' => NULL,
-      ),
+    'webform_validation_manage_overview_form' => array(
+      'render element' => 'form',
     ),
   );
 }
@@ -94,6 +91,21 @@ function webform_validation_form_alter(&$form, &$form_state, $form_id) {
   }
 }
 
+/**
+ * Implements hook_i18n_string_info().
+ */
+function webform_validation_i18n_string_info() {
+  $groups = array();
+  $groups['webform_validation'] = array(
+    'title' => t('Webform Validation'),
+    'description' => t('Translatable strings for webform validation translation'),
+    'format' => FALSE, // This group doesn't have strings with format
+    'list' => FALSE, // This group cannot list all strings
+    'refresh callback' => 'webform_validation_i18n_string_refresh',
+  );
+  return $groups;
+}
+
 /**
  * Webform validation handler to validate against the given rules
  */
@@ -145,7 +157,7 @@ function webform_validation_validate($form, &$form_state) {
       // create a list of components that need validation against this rule (component id => user submitted value)
       $items = array();
       foreach ($rule['components'] as $cid => $component) {
-        if (isset($flat_values[$cid])) {
+        if (array_key_exists($cid, $flat_values)) {
           $items[$cid] = $flat_values[$cid];
         }
       }
@@ -161,7 +173,15 @@ function webform_validation_validate($form, &$form_state) {
         foreach ($errors as $item_key => $error) {
           // build the proper form element error key, taking into account hierarchy
           $error_key = 'submitted][' . webform_validation_parent_tree($item_key, $components) . $components[$item_key]['form_key'];
-          form_set_error($error_key, $error);
+          if (is_array($error)) {
+            foreach ($error as $sub_item_key => $sub_error) {
+              form_set_error($error_key . '][' . $sub_item_key, $sub_error);
+            }
+          }
+          else {
+            // @ignore security_form_set_error. filter_xss() is run in _webform_validation_i18n_error_message().
+            form_set_error($error_key, $error);
+          }
         }
       }
     }
@@ -248,18 +268,28 @@ function theme_webform_validation_manage_add_rule($variables) {
   $validators = webform_validation_get_validators();
 
   if ($validators) {
+    $results = db_query('SELECT DISTINCT type FROM {webform_component} WHERE nid = :nid', array('nid' => $nid));
+    $types = array();
+    while ($item = $results->fetch()) {
+      $types[] = $item->type;
+    }
+
     $output = '<h3>' . t('Add a validation rule') . '</h3>';
     $output .= '<dl>';
     foreach ($validators as $validator_key => $validator_info) {
-      $item = '';
       $url = 'node/' . $nid . '/webform/validation/add/' . $validator_key;
-      $components = ' (' . implode(', ', $validator_info['component_types']) . ')';
-      $item = '<dt>' . l($validator_info['name'], $url, array("query" => drupal_get_destination())) . '</dt>';
-      $item .= '<dd>';
-      if ($validator_info['description']) {
-        $item .= $validator_info['description'] . ' ';
+      if (array_intersect($types, $validator_info['component_types'])) {
+        $title = l($validator_info['name'], $url, array('query' => drupal_get_destination()));
+        $component_list_postfix = '';
+      }
+      else {
+        $title = $validator_info['name'];
+        $component_list_postfix = '; none present in this form';
       }
-      $item .= $components . '</dd>';
+      $item = '<dt>' . $title . '</dt>';
+      $item .= '<dd>';
+      $item .= $validator_info['description'];
+      $item .= ' Works with: ' . implode(', ', $validator_info['component_types']) . $component_list_postfix . '.</dd>';
       $output .= $item;
     }
     $output .= '</dl>';
@@ -272,8 +302,8 @@ function theme_webform_validation_manage_add_rule($variables) {
  */
 function webform_validation_webform_validation($type, $op, $data) {
   if ($type == 'rule' && in_array($op, array('add', 'edit'))) {
-    if (module_exists('i18nstrings') && isset($data['error_message'])) {
-      i18nstrings_update('webform_validation:error_message:' . $data['ruleid'] . ':message', $data['error_message']);
+    if (module_exists('i18n') && isset($data['error_message'])) {
+      i18n_string_update('webform_validation:error_message:' . $data['ruleid'] . ':message', $data['error_message']);
     }
   }
 }
@@ -322,46 +352,76 @@ function webform_validation_node_clone($node) {
 }
 
 /**
- * Save a validation rule. Data comes from the admin form
- * or nodeapi function in case of node clone
+ * Save a validation rule. Data comes from the admin form or nodeapi function in
+ * case of node clone.
+ *
+ * @param array $values
+ *   An associative array containing:
+ *   - action: "add" or "edit".
+ *   - ruleid: ID of the rule to edit. Do not set for "add".
+ *   - nid: Node ID of the Webform.
+ *   - validator: Machine name of the validator used by this validation rule.
+ *   - rulename: Human-readable name for this validation rule.
+ *   - rule_components: An array in which the keys and the values are the cid's
+ *     of the Webform components that this rule applies to.
+ *
+ * @return int
+ *   The $ruleid of the rule added or edited.
  */
 function webform_validation_rule_save($values) {
-  // save rules data
-  if ($values['action'] == 'add') {
-    drupal_write_record('webform_validation_rule', $values);
-    $ruleid = $values['ruleid'];
-    if ($ruleid && array_filter($values['rule_components'])) {
-      webform_validation_save_rule_components($ruleid, array_filter($values['rule_components']));
-      module_invoke_all('webform_validation', 'rule', 'add', $values);
-    }
+  if ($values['action'] === 'add') {
+    $primary_keys = array();
+  }
+  elseif ($values['action'] === 'edit') {
+    $primary_keys = array('ruleid');
   }
+  else {
+    return FALSE;
+  }
+
+  drupal_write_record('webform_validation_rule', $values, $primary_keys);
 
-  if ($values['action'] == 'edit') {
-    drupal_write_record('webform_validation_rule', $values, 'ruleid');
-    $ruleid = $values['ruleid'];
-    // delete earlier component records for this rule id*/
+  // Delete existing component records for this ruleid.
+  if ($values['action'] === 'edit') {
     db_delete('webform_validation_rule_components')
-    ->condition('ruleid', $ruleid)
-    ->execute();
-    if ($components = array_filter($values['rule_components'])) {
-      webform_validation_save_rule_components($ruleid, $components);
-      module_invoke_all('webform_validation', 'rule', 'edit', $values);
-    }
+      ->condition('ruleid', $values['ruleid'])
+      ->execute();
   }
+
+  $components = array_filter($values['rule_components']);
+  if ($values['ruleid'] && $components) {
+    webform_validation_save_rule_components($values['ruleid'], $components);
+    module_invoke_all('webform_validation', 'rule', $values['action'], $values);
+  }
+
+  return $values['ruleid'];
 }
 
 /**
- * Save components attached to a specific rule
+ * Save components attached to a specific rule.
+ *
+ * @param int $ruleid
+ *   The ruleid of the rule being saved.
+ * @param array $components
+ *   An array in which the keys are the cid's of the components attached to the rule.
+ * @return array
+ *   An array of the return statuses for each query keyed by cid.
  */
 function webform_validation_save_rule_components($ruleid, $components) {
+  $return_status = array();
   foreach ($components as $cid => $component) {
-    $id = db_insert('webform_validation_rule_components')
-    ->fields(array(
-      'ruleid' => $ruleid,
-      'cid' => $cid,
-    ))
-  ->execute();
+    $return_status[$cid] = db_merge('webform_validation_rule_components')
+      ->key(array(
+        'ruleid' => $ruleid,
+        'cid' => $cid,
+      ))
+      ->fields(array(
+        'ruleid' => $ruleid,
+        'cid' => $cid,
+      ))
+      ->execute();
   }
+  return $return_status;
 }
 
 /**
@@ -404,6 +464,7 @@ function webform_validation_webform_validator_alter(&$validators) {
         if (in_array('textfield', $validator_info['component_types'])) {
           $validators[$validator_name]['component_types'][] = 'select';
         }
+        $validators[$validator_name]['component_types'] = array_unique($validators[$validator_name]['component_types']);
       }
     }
   }
diff --git a/drupal7/sites/all/modules/webform_validation/webform_validation.rules.inc b/drupal7/sites/all/modules/webform_validation/webform_validation.rules.inc
index 19c1c24f..d3e2fc99 100644
--- a/drupal7/sites/all/modules/webform_validation/webform_validation.rules.inc
+++ b/drupal7/sites/all/modules/webform_validation/webform_validation.rules.inc
@@ -9,7 +9,7 @@
  * Get a rule entry
  */
 function webform_validation_get_rule($ruleid) {
-  $result = db_query("SELECT ruleid, rulename, nid, validator, data, error_message, negate FROM {webform_validation_rule} WHERE ruleid = :ruleid", array(':ruleid' => $ruleid), array('fetch' => PDO::FETCH_ASSOC));
+  $result = db_query("SELECT ruleid, rulename, nid, validator, data, error_message, negate, weight FROM {webform_validation_rule} WHERE ruleid = :ruleid", array(':ruleid' => $ruleid), array('fetch' => PDO::FETCH_ASSOC));
   $rule = $result->fetchAssoc();
   $rule['components'] = webform_validation_get_rule_components($ruleid, $rule['nid']);
   $rule['negate'] = (bool) $rule['negate'];
@@ -21,7 +21,7 @@ function webform_validation_get_rule($ruleid) {
  */
 function webform_validation_get_node_rules($nid) {
   $rules = array();
-  $result = db_query("SELECT ruleid, rulename, nid, validator, data, error_message, negate FROM {webform_validation_rule} WHERE nid = :nid ORDER BY ruleid DESC", array(':nid' => $nid), array('fetch' => PDO::FETCH_ASSOC));
+  $result = db_query("SELECT ruleid, rulename, nid, validator, data, error_message, negate, weight FROM {webform_validation_rule} WHERE nid = :nid ORDER BY weight ASC, ruleid DESC", array(':nid' => $nid), array('fetch' => PDO::FETCH_ASSOC));
   foreach ($result as $rule) {
     $rule['components'] = webform_validation_get_rule_components($rule['ruleid'], $rule['nid']);
     $rule['negate'] = (bool) $rule['negate'];
@@ -79,15 +79,18 @@ function webform_validation_rule_components_basic($components) {
 }
 
 /**
- * Delete a rule and dependencies
+ * Delete a rule and dependencies.
+ *
+ * @param int $ruleid
+ *   The ruleid of the rule to delete.
  */
 function webform_dynamic_delete_rule($ruleid) {
-  // delete rule
+  // Delete rule.
   db_delete('webform_validation_rule')
-  ->condition('ruleid', $ruleid)
-  ->execute();
-  // delete rule components
+    ->condition('ruleid', $ruleid)
+    ->execute();
+  // Delete rule components.
   db_delete('webform_validation_rule_components')
-  ->condition('ruleid', $ruleid)
-  ->execute();
+    ->condition('ruleid', $ruleid)
+    ->execute();
 }
diff --git a/drupal7/sites/all/modules/webform_validation/webform_validation.validators.inc b/drupal7/sites/all/modules/webform_validation/webform_validation.validators.inc
index 3e6329a9..c58ff927 100644
--- a/drupal7/sites/all/modules/webform_validation/webform_validation.validators.inc
+++ b/drupal7/sites/all/modules/webform_validation/webform_validation.validators.inc
@@ -31,7 +31,7 @@ function webform_validation_webform_validation_validators() {
       'custom_data' => array(
         'label' => t('Specify numeric validation range'),
         'description' => t('Optionally specify the minimum-maximum range to validate the user-entered numeric value against.') . ' ' . t('Usage') . ':'
-        . theme('item_list', array('items' => array(t('empty: no value validation'), t('"100": greater than or equal to 100'), t('"|100": less than or equal to 100 (including negative numbers)'), t('"0|100": greater than or equal to 0 &amp; less than or equal to 100'), t('"10|100": greater than or equal to 10 &amp; less than or equal to 100'), t('"-100|-10": greater than or equal to -100 &amp; less than or equal to -10')))),
+          . theme('item_list', array('items' => array(t('empty: no value validation'), t('"100": greater than or equal to 100'), t('"|100": less than or equal to 100 (including negative numbers)'), t('"0|100": greater than or equal to 0 &amp; less than or equal to 100'), t('"10|100": greater than or equal to 10 &amp; less than or equal to 100'), t('"-100|-10": greater than or equal to -100 &amp; less than or equal to -10')))),
         'required' => FALSE,
       ),
       'description' => t('Verifies that user-entered values are numeric, with the option to specify min and / or max values.'),
@@ -49,7 +49,7 @@ function webform_validation_webform_validation_validators() {
         'label' => t('Minimum number of characters'),
         'description' => t('Specify the minimum number of characters that have to be entered to pass validation.'),
       ),
-      'description' => t('Verifies that a user-entered value contains at least the specified number of characters'),
+      'description' => t('Verifies that a user-entered value contains at least the specified number of characters.'),
     ),
     'max_length' => array(
       'name' => t('Maximum length'),
@@ -64,7 +64,7 @@ function webform_validation_webform_validation_validators() {
         'label' => t('Maximum number of characters'),
         'description' => t('Specify the maximum number of characters that can be entered to pass validation.'),
       ),
-      'description' => t('Verifies that a user-entered value contains at most the specified number of characters'),
+      'description' => t('Verifies that a user-entered value contains at most the specified number of characters.'),
     ),
     'min_words' => array(
       'name' => t('Minimum number of words'),
@@ -171,8 +171,8 @@ function webform_validation_webform_validation_validators() {
       'custom_error' => TRUE,
       'description' => t('Verifies that the user-entered value is the default value for that component. Negate if you want not the default value.'),
     ),
-    'oneoftwo' => array(
-      'name' => t('Require at least one of two fields'),
+    'someofseveral' => array(
+      'name' => t('At least one or more out of two or more'),
       'component_types' => array(
         'number',
         'textfield',
@@ -180,21 +180,12 @@ function webform_validation_webform_validation_validators() {
         'email',
         'select',
       ),
-      'min_components' => 2,
-      'max_components' => 2,
-      'description' => t('Forces the user to specify / select at least one of two selected webform components'),
-    ),
-    'oneofseveral' => array(
-      'name' => t('Require at least one of several fields'),
-      'component_types' => array(
-        'number',
-        'textfield',
-        'textarea',
-        'email',
-        'select',
+      'custom_data' => array(
+        'label' => t('Minimum number to be completed'),
+        'description' => t('Specify the minimum number that must be completed. For example, enter 1 if the user must complete at least 1 of the selected components.'),
       ),
       'min_components' => 2,
-      'description' => t('Forces the user to specify / select at least one of several selected webform components.'),
+      'description' => t('Requires the user to complete at least one or more of two or more Webform components, such as 3 out of 5 or 1 out of 3.'),
     ),
     'select_min' => array(
       'name' => t('Minimum number of selections required'),
@@ -283,9 +274,9 @@ function webform_validation_webform_validation_validators() {
       'custom_data' => array(
         'label' => t('Pattern'),
         'description' => t('Specify a pattern where:')
-        . theme('item_list', array('items' => array(t('@ = any letter A-Z'), t('# = any numeral 0-9'), t('| = separates two or more acceptable patterns'), t('Any other character must appear in its exact position'))))
-        . t('Examples')
-        . theme('item_list', array('items' => array(t('North American phone number: (###) ###-####'), t('D-2500 series model numbers: D-25##'), t('UK Postal Code: @# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA')))),
+          . theme('item_list', array('items' => array(t('@ = any letter A-Z'), t('# = any numeral 0-9'), t('| = separates two or more acceptable patterns'), t('Any other character must appear in its exact position'))))
+          . t('Examples')
+          . theme('item_list', array('items' => array(t('North American phone number: (###) ###-####'), t('D-2500 series model numbers: D-25##'), t('UK Postal Code: @# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA')))),
       ),
       'description' => t('Verifies that a user-entered value follows to a specified pattern.'),
     ),
@@ -386,15 +377,27 @@ function webform_validation_webform_validation_validators() {
  */
 function webform_validation_webform_validation_validate($validator_name, $items, $components, $rule) {
   if ($items) {
+    // Some components, such as multiple select, send their values as arrays, others don't.
+    // Convert all to arrays and write the rules to handle them that way. Remove empty values.
+    foreach ($items as $key => $val) {
+      $new_values = array();
+      foreach ((array) $val as $val_key => $val_val) {
+        if ((string) $val_val !== '') {
+          $new_values[$val_key] = $val_val;
+        }
+      }
+      $items[$key] = $new_values;
+    }
+
     $errors = array();
     switch ($validator_name) {
       case 'numeric':
         $num_range = _webform_numeric_check_data($rule['data']);
         foreach ($items as $key => $val) {
-          if ($val != '') {
+          foreach ($val as $subval) {
             // first check if the value is numeric
-            if (is_numeric($val)) {
-              $val = (float) $val;
+            if (is_numeric($subval)) {
+              $subval = (float) $subval;
             }
             else {
               $errors[$key] = t('%item is not numeric.', array('%item' => $components[$key]['name']));
@@ -405,20 +408,20 @@ function webform_validation_webform_validation_validate($validator_name, $items,
             // a. validate min & max
             if (isset($num_range['min']) && isset($num_range['max'])) {
               // validate the min - max range
-              if ($val < $num_range['min'] || $val > $num_range['max']) {
+              if ($subval < $num_range['min'] || $subval > $num_range['max']) {
                 $errors[$key] = t('%item is not within the allowed range %range.', array('%item' => $components[$key]['name'], '%range' => str_replace('|' , ' - ', $rule['data'])));
               }
             }
             else {
               // b. validate min
               if (isset($num_range['min'])) {
-                if ($val < $num_range['min']) {
+                if ($subval < $num_range['min']) {
                   $errors[$key] = t('%item should be greater than or equal to %val.', array('%item' => $components[$key]['name'], '%val' => $num_range['min']));
                 }
               }
               // c. validate max
               if (isset($num_range['max'])) {
-                if ($val > $num_range['max']) {
+                if ($subval > $num_range['max']) {
                   $errors[$key] = t('%item should be less than or equal to %val.', array('%item' => $components[$key]['name'], '%val' => $num_range['max']));
                 }
               }
@@ -429,44 +432,49 @@ function webform_validation_webform_validation_validate($validator_name, $items,
       case 'min_length':
         $min_length = $rule['data'];
         foreach ($items as $key => $val) {
-          if ($val != '' && (drupal_strlen($val) < $min_length)) {
-            $errors[$key] = t('%item should be at least %num characters long.', array('%item' => $components[$key]['name'], '%num' => $min_length));
+          foreach ($val as $subval) {
+            if (drupal_strlen($subval) < $min_length) {
+              $errors[$key] = t('%item should be at least %num characters long.', array('%item' => $components[$key]['name'], '%num' => $min_length));
+            }
           }
         }
         return $errors;
       case 'max_length':
         $max_length = $rule['data'];
         foreach ($items as $key => $val) {
-          if ($val != '' && (drupal_strlen($val) > $max_length)) {
-            $errors[$key] = t('%item should be at most %num characters long.', array('%item' => $components[$key]['name'], '%num' => $max_length));
+          foreach ($val as $subval) {
+            if (drupal_strlen($subval) > $max_length) {
+              $errors[$key] = t('%item should be at most %num characters long.', array('%item' => $components[$key]['name'], '%num' => $max_length));
+            }
           }
         }
         return $errors;
       case 'min_words':
         $min_words = $rule['data'];
         foreach ($items as $key => $val) {
-          if ($val != '' && (_webform_validation_count_words($val) < $min_words)) {
-            $error = format_plural($min_words, '%item should be at least 1 word long.', '%item should be at least @count words long.', array('%item' => $components[$key]['name']));
-            $errors[$key] = $error;
+          foreach ($val as $subval) {
+            if (_webform_validation_count_words($subval) < $min_words) {
+              $error = format_plural($min_words, '%item should be at least 1 word long.', '%item should be at least @count words long.', array('%item' => $components[$key]['name']));
+              $errors[$key] = $error;
+            }
           }
         }
         return $errors;
       case 'max_words':
         $max_words = $rule['data'];
         foreach ($items as $key => $val) {
-          if ($val != '' && (_webform_validation_count_words($val) > $max_words)) {
-            $error = format_plural($max_words, '%item should be at most 1 word long.', '%item should be at most @count words long.', array('%item' => $components[$key]['name']));
-            $errors[$key] = $error;
+          foreach ($val as $subval) {
+            if (_webform_validation_count_words($subval) > $max_words) {
+              $error = format_plural($max_words, '%item should be at most 1 word long.', '%item should be at most @count words long.', array('%item' => $components[$key]['name']));
+              $errors[$key] = $error;
+            }
           }
         }
         return $errors;
       case 'equal':
         $first_entry_key = key($items);
         $first_entry = array_shift($items);
-        $first_entry = _webform_validation_flatten_array($first_entry); // flatten in case of array
-        // now check if following components equal the first one
         foreach ($items as $key => $val) {
-          $val = _webform_validation_flatten_array($val); // flatten in case of array
           if ($val !== $first_entry) {
             $errors[$key] = t('%item_checked does not match %item_first.', array('%item_checked' => $components[$key]['name'], '%item_first' => $components[$first_entry_key]['name']));
           }
@@ -537,14 +545,7 @@ function webform_validation_webform_validation_validate($validator_name, $items,
         return $errors;
       case 'unique':
         foreach ($items as $key => $val) {
-          if (is_array($val)) {
-            // make sure to flatten arrays first
-            $items[$key] = _webform_validation_flatten_array($val);
-          }
-          if (empty($items[$key])) {
-            // items without a value selected shouldn't be validated
-            unset($items[$key]);
-          }
+          $items[$key] = _webform_validation_flatten_array($val);
         }
         // now we count how many times each value appears, and find out which values appear more than once
         $items_count = array_count_values(array_map('drupal_strtolower', array_map('trim', $items)));
@@ -559,59 +560,38 @@ function webform_validation_webform_validation_validate($validator_name, $items,
         $specific_values = explode(',', $rule['data']);
         $specific_values = array_map('trim', $specific_values);
         foreach ($items as $key => $val) {
-          if (is_array($val)) {
-            // Selects: Fail if at least one checked and at least one not in the allowed values.
-            $val = array_filter($val);
-            $test = count($val) && count(array_diff($val, $specific_values));
-          }
-          else {
-            // Fail if not empty and not in the allowed values.
-            $test = $val && !in_array($val, $specific_values);
-          }
+          // Selects: Fail if at least one checked and at least one not in the allowed values.
+          $val = array_filter($val);
+          $test = count($val) && count(array_diff($val, $specific_values));
           _webform_validation_test($errors, $key, $rule, $test);
         }
         return $errors;
       case 'default_value':
         foreach ($items as $key => $val) {
-          if (is_array($val)) {
-            $val = _webform_validation_flatten_array($val);
-          }
+          $val = _webform_validation_flatten_array($val);
           $test = $val != $components[$key]['value'];
           _webform_validation_test($errors, $key, $rule, $test);
         }
         return $errors;
-      case 'oneoftwo':
-        // $components should have 2 items
-        $keys = array_keys($items);
-        $item1 = array_shift($keys);
-        $item2 = array_shift($keys);
-        $entry1 = _webform_validation_flatten_array($items[$item1]);
-        $entry2 = _webform_validation_flatten_array($items[$item2]);
-        if (empty($entry1) && empty($entry2)) {
-          return array($item1 => t('You have to specify %item1 or %item2 (or both).', array('%item1' => $components[$item1]['name'], '%item2' => $components[$item2]['name'])));
-        }
-        return;
-      case 'oneofseveral':
+      case 'someofseveral':
         foreach ($items as $key => $val) {
-          if (is_array($val)) {
-            // make sure to flatten arrays first
-            $items[$key] = _webform_validation_flatten_array($val);
-          }
+          $items[$key] = _webform_validation_flatten_array($val);
+          $items[$key] = (bool) strlen($items[$key]);
         }
-        // $components should have at least one of several items
-        if (count(array_filter($items)) < 1) {
+        $min_selections = isset($rule['data']) ? ((int) $rule['data']) : 1;
+        if (count(array_filter($items)) < $min_selections) {
           $keys = array_keys($items);
           $names = array();
           foreach ($keys as $value) {
             $names[] = _webform_filter_xss($components[$value]['name']);
           }
-          return array($keys[0] => t('You have to specify at least one of these items:') . theme('item_list', array('items' => $names)));
+          return array($keys[0] => t('You must complete at least %min_selections of these items:', array('%min_selections' => $min_selections)) . theme('item_list', array('items' => $names)));
         }
         return;
       case 'select_min':
         $min_selections = $rule['data'];
         foreach ($items as $key => $val) {
-          if (is_array($val) && (count(array_filter($val, '_webform_validation_check_false')) < $min_selections)) {
+          if (count(array_filter($val, '_webform_validation_check_false')) < $min_selections) {
             $errors[$key] = t('Please select at least %num options for %item.', array('%num' => $min_selections, '%item' => $components[$key]['name']));
           }
         }
@@ -619,7 +599,7 @@ function webform_validation_webform_validation_validate($validator_name, $items,
       case 'select_max':
         $max_selections = $rule['data'];
         foreach ($items as $key => $val) {
-          if (is_array($val) && (count(array_filter($val, '_webform_validation_check_false')) > $max_selections)) {
+          if (count(array_filter($val, '_webform_validation_check_false')) > $max_selections) {
             $errors[$key] = t('Please select maximum %num options for %item.', array('%num' => $max_selections, '%item' => $components[$key]['name']));
           }
         }
@@ -632,7 +612,7 @@ function webform_validation_webform_validation_validate($validator_name, $items,
             'negated' => 'Please do not select %num options for %item.',
           );
           $error_vars = array('%num' => $allowed_selections, '%item' => $components[$key]['name']);
-          $test = is_array($val) && (count(array_filter($val, '_webform_validation_check_false')) != $allowed_selections);
+          $test = count(array_filter($val, '_webform_validation_check_false')) != $allowed_selections;
           _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
         }
         return $errors;
@@ -643,8 +623,10 @@ function webform_validation_webform_validation_validate($validator_name, $items,
             'negated' => '%item must contain HTML tags.',
           );
           $error_vars = array('%item' => $components[$key]['name']);
-          $test = $val != '' && (strcmp($val, strip_tags($val)));
-          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
+          foreach ($val as $subval) {
+            $test = strcmp($subval, strip_tags($subval));
+            _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
+          }
         }
         return $errors;
       case 'starts_with':
@@ -661,14 +643,15 @@ function webform_validation_webform_validation_validate($validator_name, $items,
         $pattern = '/' . $pattern . '/';
 
         foreach ($items as $key => $val) {
-          $val = (string) $val;
           $error_strings = array(
             'regular' => '%item must %verb with "%string".',
             'negated' => '%item must not %verb with "%string".',
           );
           $error_vars = array('%item' => $components[$key]['name'], '%verb' => $verb, '%string' => $rule['data']);
-          $test = $val && !preg_match($pattern, $val);
-          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
+          foreach ($val as $subval) {
+            $test = !preg_match($pattern, $subval);
+            _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
+          }
         }
         return $errors;
       case 'pattern':
@@ -679,9 +662,10 @@ function webform_validation_webform_validation_validate($validator_name, $items,
         // Un-escape "|" operator.
         $pattern = preg_replace('/(\\s*)(\\\\)(\\|)(\\s*)/', ')|(', $pattern);
         foreach ($items as $key => $val) {
-          $val = (string) $val;
-          $test = $val !== '' && !preg_match('/^(' . $pattern . ')$/', $val);
-          _webform_validation_test($errors, $key, $rule, $test);
+          foreach ($val as $subval) {
+            $test = !preg_match('/^(' . $pattern . ')$/', $subval);
+            _webform_validation_test($errors, $key, $rule, $test);
+          }
         }
         return $errors;
       case 'regex':
@@ -689,13 +673,12 @@ function webform_validation_webform_validation_validate($validator_name, $items,
         mb_regex_encoding('UTF-8');
         $regex = $rule['data'];
         foreach ($items as $key => $val) {
-          $val = (string) $val;
-          if ($val) {
+          foreach ($val as $subval) {
             if ($validator_name === 'regexi') {
-              $match = (bool) mb_eregi($regex, $val);
+              $match = (bool) mb_eregi($regex, $subval);
             }
             else {
-              $match = (bool) mb_ereg($regex, $val);
+              $match = (bool) mb_ereg($regex, $subval);
             }
             $test = !$match;
             _webform_validation_test($errors, $key, $rule, $test);
@@ -704,7 +687,7 @@ function webform_validation_webform_validation_validate($validator_name, $items,
         return $errors;
       case 'must_be_empty':
         foreach ($items as $key => $val) {
-          if ($val) {
+          if (count($val) !== 0) {
             $errors[$key] = t('%item does not contain the correct data.', array('%item' => $components[$key]['name']));
           }
         }
@@ -715,8 +698,10 @@ function webform_validation_webform_validation_validate($validator_name, $items,
         $blacklist = array_map('trim', $blacklist);
         $blacklist = '/\b(' . implode('|', $blacklist) . ')\b/i';
         foreach ($items as $key => $val) {
-          $test = $val != '' && preg_match($blacklist, $val);
-          _webform_validation_test($errors, $key, $rule, $test);
+          foreach ($val as $subval) {
+            $test = preg_match($blacklist, $subval);
+            _webform_validation_test($errors, $key, $rule, $test);
+          }
         }
         return $errors;
       case 'username':
@@ -726,8 +711,10 @@ function webform_validation_webform_validation_validate($validator_name, $items,
             'negated' => 'The %item field matches an active username.',
           );
           $error_vars = array('%item' => $components[$key]['name']);
-          $test = $val != '' && !db_query_range('SELECT 1 FROM {users} WHERE name = :username AND status = 1', 0, 1, array(':username' => $val))->fetchField();
-          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
+          foreach ($val as $subval) {
+            $test = !db_query_range('SELECT 1 FROM {users} WHERE name = :username AND status = 1', 0, 1, array(':username' => $subval))->fetchField();
+            _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
+          }
         }
         return $errors;
       case 'valid_url':
@@ -737,16 +724,20 @@ function webform_validation_webform_validation_validate($validator_name, $items,
             'negated' => '%item must not be a valid URL.',
           );
           $error_vars = array('%item' => $components[$key]['name']);
-          $test = $val && !valid_url($val, TRUE);
-          _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
+          foreach ($val as $subval) {
+            $test = !valid_url($subval, TRUE);
+            _webform_validation_test($errors, $key, $rule, $test, $error_strings, $error_vars);
+          }
         }
         return $errors;
       case 'email_verify':
         if (module_exists('email_verify')) {
           foreach ($items as $key => $val) {
-            $error = email_verify_check($val);
-            if ($error) {
-              $errors[$key] = $error;
+            foreach ($val as $subval) {
+              $error = email_verify_check($subval);
+              if ($error) {
+                $errors[$key] = $error;
+              }
             }
           }
         }
@@ -776,18 +767,17 @@ function _webform_validation_test(&$errors, $key, $rule, $test, array $error_str
 
 /**
  * Count the number of words in a value.
+ *
+ * Strip HTML first.
  */
 function _webform_validation_count_words($val) {
-  // Deal with values from HTML textareas.
-  if (is_array($val) && isset($val['value'])) {
-    $val = strip_tags($val['value']);
-    // Replace entities representing spaces with actual spaces.
-    $val = str_replace('&nbsp;', ' ', $val);
-    $val = str_replace('&#160;', ' ', $val);
-  }
-  else {
-    $val = _webform_validation_flatten_array($val);
-  }
+  $val = _webform_validation_flatten_array($val);
+
+  $val = strip_tags($val);
+  // Replace entities representing spaces with actual spaces.
+  $val = str_replace('&nbsp;', ' ', $val);
+  $val = str_replace('&#160;', ' ', $val);
+
   return str_word_count($val);
 }
 
@@ -804,7 +794,7 @@ function _webform_validation_preg_quote($str, $delimiter = NULL) {
 }
 
 /**
- * Helper function to deal with submitted values that are arrays (e.g. multiple select component)
+ * Helper function to deal with submitted values that are arrays (for example, multiple select component).
  * We flatten the array as a comma-separated list to do the comparison.
  */
 function _webform_validation_flatten_array($val) {
@@ -883,8 +873,8 @@ function webform_validation_get_validator_info($validator_key) {
  */
 function _webform_validation_i18n_error_message($rule) {
   $rule['error_message'] = filter_xss($rule['error_message']);
-  if (module_exists('i18nstrings')) {
-    return i18nstrings('webform_validation:error_message:' . $rule['ruleid'] . ':message', $rule['error_message']);
+  if (module_exists('i18n')) {
+    return i18n_string('webform_validation:error_message:' . $rule['ruleid'] . ':message', $rule['error_message']);
   }
   return $rule['error_message'];
 }
