Index: password_policy.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/password_policy/password_policy.admin.inc,v
retrieving revision 1.11
diff -u -p -r1.11 password_policy.admin.inc
--- password_policy.admin.inc	31 Jul 2009 16:24:51 -0000	1.11
+++ password_policy.admin.inc	10 Feb 2010 02:30:28 -0000
@@ -113,66 +113,58 @@ function password_policy_admin_settings_
   }
 }
 
+function _password_policy_admin_list_roles($pid) {
+  $roles = array();
+  $result = db_query('SELECT name FROM {role} r INNER JOIN {password_policy_role} p ON r.rid = p.rid WHERE p.pid = %d', $pid);
+  while ($row = db_fetch_object($result)) {
+    $roles[] = $row->name;
+  }
+  return $roles;
+}
+
 /**
  * The list of the password policies.
  */
 function password_policy_admin_list() {
-  $header = array(t('Default'), array('data' => t('Name'), 'field' => 'name', 'sort' => 'asc'), t('Enabled'), array('data' => t('Operations'), 'colspan' => 3));
   $form = array();
   $options = array();
-  $result = db_query('SELECT pid, name, enabled, description, created FROM {password_policy}'. tablesort_sql($header));
+  $enabled = array();
+  $result = db_query('SELECT pid, name, enabled, description, created, weight FROM {password_policy} ORDER BY weight');
   while ($row = db_fetch_array($result)) {
     $pid = $row['pid'];
-    $options[$pid] = '';
+    $options[$pid] = $row['enabled'] ? format_date($row['created'], 'medium') : '';
     if ($row['enabled']) {
-      $default_pid = $pid;
-      $form[$pid]['created'] = array('#value' => format_date($row['created'], 'medium'));
+      $enabled[] = $pid;
     }
     $form[$pid]['name'] = array('#value' => $row['name']);
+    $form[$pid]['roles'] = array('#value' => theme('item_list', _password_policy_admin_list_roles($pid)));
+    $form['weight'][$pid] = array('#type' => 'weight', '#default_value' => $row['weight']);
     $form[$pid]['view'] = array('#value' => l(t('view'), 'admin/settings/password_policy/'. $pid));
     $form[$pid]['edit'] = array('#value' => l(t('edit'), 'admin/settings/password_policy/'. $pid .'/edit'));
     $form[$pid]['delete'] = array('#value' => l(t('delete'), 'admin/settings/password_policy/delete/'. $pid));
   }
-  $form['default'] = array('#type' => 'radios', '#options' => $options, '#default_value' => isset($default_pid) ? $default_pid : 0);
-  $form['submit'] = array('#type' => 'submit', '#value' => t('Set default policy'));
-  $form['clear'] = array('#type' => 'submit', '#value' => t('Clear default policy'));
+  $form['weight']['#tree'] = TRUE;
+  $form['enabled'] = array('#type' => 'checkboxes', '#options' => $options, '#default_value' => $enabled);
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save changes'));
   return $form;
 }
 
 /**
- * Submit hook for the form on the default list view for the password policy module.  From the
- * default view, the user can set a new default password policy or clear the default so
- * that no policy is active and the default drupal password mechanism takes affect.
+ * Submit hook for the form on the default list view for the password policy module.
  */
 function password_policy_admin_list_submit($form, &$form_state) {
-  switch ($form_state['clicked_button']['#value']) {
-    case "Clear default policy":
-      _password_policy_admin_clear_default();
-      drupal_set_message(t('No policy is active, all user passwords will be accepted (Drupal default).'));
-      break;
-    case "Set default policy":
-      $pid = $form_state['values']['default'];
-      if (is_numeric($pid)) {
-        $policy = _password_policy_load_policy_by_pid($pid);
-        if ($policy) {
-          _password_policy_admin_clear_default();
-          $time = time();
-          db_query("UPDATE {password_policy} SET enabled = %d, created = %d WHERE pid = %d", 1, $time, $pid);
-          drupal_set_message(t('%name has been set as the default password policy.', array('%name' => $policy['name'])));
-        }
-      }
-      else {
-        drupal_set_message(t('Default password policy was not set.'), 'warning');
-      }
-      break;
+  foreach ($form_state['values']['enabled'] as $pid => $enabled) {
+    db_query("UPDATE {password_policy} SET weight = %d WHERE pid = %d", $form_state['values']['weight'][$pid], $pid);
+    // Ensure we don't reset the timestamp on policies that are already enabled.
+    if ($enabled) {
+      db_query("UPDATE {password_policy} SET enabled = 1, created = %d WHERE pid = %d AND enabled = 0", time(), $pid);
+    }
+    else {
+      db_query("UPDATE {password_policy} SET enabled = 0 WHERE pid = %d", $pid);
+    }
   }
-}
 
-/**
- * Resets the enabled flag for all policies in the database to 0.
- */
-function _password_policy_admin_clear_default() {
-  db_query("UPDATE {password_policy} SET enabled = %d WHERE enabled = %d", 0, 1);
+  drupal_set_message(t('The changes have been saved.'));
 }
 
 /**
@@ -182,6 +174,10 @@ function password_policy_admin_view($pol
   $output = check_plain($policy['description']);
   $header = array(t('Name'), t('Constraint'));
   $rows = array();
+  $roles = _password_policy_admin_list_roles($policy['pid']);
+  if (!empty($roles)) {
+    $rows[] = array(t('Roles'), theme('item_list', $roles));
+  }
   if (!empty($policy['expiration'])) {
     $rows[] = array(t('Expiration'), $policy['expiration']);
   }
@@ -216,6 +212,21 @@ function password_policy_admin_form($for
     '#title' => t('Description'),
     '#default_value' => $policy['description'],
   );
+
+  $form['roles'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Roles'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE
+  );
+  $form['roles']['roles'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Roles'),
+    '#options' => user_roles(),
+    '#default_value' => isset($policy['roles']) ? $policy['roles'] : array(),
+    '#description' => t('Select the roles that this policy will apply to.'),
+  );
+
   $form['expiration'] = array(
     '#type' => 'fieldset',
     '#title' => t('Expiration'),
@@ -276,6 +287,16 @@ function password_policy_admin_form($for
 }
 
 /**
+ * Form validation hook for new or edited password policies.
+ */
+function password_policy_admin_form_validate($form, &$form_state) {
+  $roles = array_filter($form_state['values']['roles']);
+  if (empty($roles)) {
+    form_set_error('roles', t('You must select at least one role for a policy to apply to.'));
+  }
+}
+
+/**
  * Form submission hook for new or edited password policies.
  */
 function password_policy_admin_form_submit($form, &$form_state) {
@@ -299,6 +320,8 @@ function password_policy_admin_form_subm
     db_query("UPDATE {password_policy} SET name = '%s', description = '%s', policy = '%s', expiration = %d, warning = '%s' WHERE pid = %d", $form_state['values']['name'], $form_state['values']['description'], serialize($policy), trim($form_state['values']['expiration']), str_replace(' ', '', $form_state['values']['warning']), $form_state['values']['pid']);
     drupal_set_message(t('Policy %name has been updated.', array('%name' => $form_state['values']['name'])));
     watchdog('password_policy', 'Policy %name updated.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE, l(t('edit'), 'admin/settings/password_policy/'. $form_state['values']['pid'] .'/edit'));
+    $pid = $form_state['values']['pid'];
+    db_query("DELETE FROM {password_policy_role} WHERE pid = %d", $pid);
   }
   else {
     db_query("INSERT INTO {password_policy} (name, description, enabled, policy, expiration, warning) VALUES ('%s', '%s', %d, '%s', %d, '%s')", $form_state['values']['name'], $form_state['values']['description'], 0, serialize($policy), trim($form_state['values']['expiration']), str_replace(' ', '', $form_state['values']['warning']));
@@ -307,6 +330,10 @@ function password_policy_admin_form_subm
     watchdog('password_policy', 'New policy %name created.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE, l(t('edit'), 'admin/settings/password_policy/'. $pid .'/edit'));
   }
 
+  foreach (array_filter($form_state['values']['roles']) as $rid => $enabled) {
+    db_query("INSERT INTO {password_policy_role} (pid, rid) VALUES (%d, %d)", $pid, $rid);
+  }
+
   drupal_goto('admin/settings/password_policy/list');
 }
 
@@ -334,6 +361,7 @@ function password_policy_admin_delete_su
   $policy = _password_policy_load_policy_by_pid($pid);
 
   db_query("DELETE FROM {password_policy} WHERE pid = %d", $pid);
+  db_query("DELETE FROM {password_policy_role} WHERE pid = %d", $pid);
   drupal_set_message(t('Password policy %policy was deleted.', array('%policy' => $policy['name'])));
   watchdog('password_policy', 'Policy %name was deleted.', array('%name' => $policy['name']), WATCHDOG_NOTICE);
   drupal_goto('admin/settings/password_policy/list');
Index: password_policy.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/password_policy/password_policy.install,v
retrieving revision 1.7
diff -u -p -r1.7 password_policy.install
--- password_policy.install	26 Oct 2008 11:21:06 -0000	1.7
+++ password_policy.install	10 Feb 2010 02:30:28 -0000
@@ -35,6 +35,23 @@ function password_policy_uninstall() {
   variable_del('password_policy_warning_body');
 }
 
+function password_policy_update_6000() {
+  $ret = array();
+
+  $schema = password_policy_schema();
+  db_add_field($ret, 'password_policy', 'weight', $schema['password_policy']['fields']['weight']);
+  db_create_table($ret, 'password_policy_role', $schema['password_policy_role']);
+
+  // Ensure existing policies are active for all roles.
+  $result = db_query("SELECT pid FROM {password_policy}");
+  while ($row = db_fetch_object($result)) {
+    db_query("INSERT INTO {password_policy_role} (pid, rid) VALUES (%d, %d)", $row->pid, DRUPAL_AUTHENTICATED_RID);
+    db_query("INSERT INTO {password_policy_role} (pid, rid) VALUES (%d, %d)", $row->pid, DRUPAL_ANONYMOUS_RID);
+  }
+
+  return $ret;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 // Schema API hooks
 
@@ -92,6 +109,13 @@ function password_policy_schema() {
           'type' => 'varchar',
           'length' => 64,
         ),
+        'weight' => array(
+          'description' => t("Weight of the policy, used to order active policies."),
+          'type' => 'int',
+          'size' => 'tiny',
+          'not null' => TRUE,
+          'default'  => 0,
+        ),
       ),
       'primary key' => array('pid'),
     ),
@@ -153,6 +177,25 @@ function password_policy_schema() {
       'primary key' => array('pid'),
       'indexes' => array('uid' => array('uid')),
     ),
+    'password_policy_role' => array(
+      'description' => t("Links policies with roles."),
+      'fields' => array(
+        'rid' => array(
+          'description' => t("Role ID."),
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'pid' => array(
+          'description' => t("Policy ID."),
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+      ),
+      'primary key' => array('rid', 'pid'),
+    ),
   );
-}
 
+  return $schema;
+}
Index: password_policy.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/password_policy/password_policy.module,v
retrieving revision 1.25
diff -u -p -r1.25 password_policy.module
--- password_policy.module	9 Feb 2010 14:15:18 -0000	1.25
+++ password_policy.module	10 Feb 2010 02:30:29 -0000
@@ -220,7 +220,8 @@ function password_policy_user($op, &$edi
       }
       break;
     case "login":
-      $policy = _password_policy_load_active_policy();
+      $roles = is_array($account->roles) ? array_keys($account->roles) : array();
+      $policy = _password_policy_load_active_policy($roles);
       // A value $edit['name'] is NULL for a one time login
       if ($policy && ($account->uid > 1 || PASSWORD_POLICY_ADMIN) && !empty($edit['name'])) {
         // Calculate expiration and warning times.
@@ -229,7 +230,10 @@ function password_policy_user($op, &$edi
         $expiration_seconds = $expiration*60*60*24;
         $warning_seconds = $warning*60*60*24;
         // The policy was enabled
-        $policy_start = _password_policy_start($expiration_seconds);
+        $policy_start = $policy['created'];
+        if (PASSWORD_POLICY_BEGIN == 1) {
+          $policy_start -= $expiration_seconds;
+        }
         if (!empty($expiration)) {
           // Account expiration is active.
           // Get the last password change time.
@@ -301,7 +305,9 @@ function password_policy_form_alter(&$fo
       // Password change form.
       $uid = isset($form['#uid']) ? $form['#uid'] : NULL;
       //if ($uid == 1 && !PASSWORD_POLICY_ADMIN) { break; }
-      $policy = _password_policy_load_active_policy();
+      $roles = isset($form['_account']['#value']) ? array_keys($form['_account']['#value']->roles) : array();
+      $policy = _password_policy_load_active_policy($roles);
+      
       $translate = array();
       if (!empty($policy['policy'])) {
         // Some policy constraints are active.
@@ -327,34 +333,48 @@ function password_policy_form_alter(&$fo
  * Implementation of hook_cron().
  */
 function password_policy_cron() {
-  $policy = _password_policy_load_active_policy();
-  if ($policy) {
-    $expiration = $policy['expiration'];
-    $warnings = !empty($policy['warning']) ? explode(',', $policy['warning']) : array();
-    if (!empty($expiration)) {
-      // Accounts expiration is active.
-      // Get all users' last password change time. We don't touch blocked accounts
-      $result = db_query("SELECT u.uid AS uid, u.created AS created_u, p.created AS created_p, e.pid AS pid, e.warning AS warning, e.unblocked AS unblocked FROM {users} u LEFT JOIN {password_policy_history} p ON u.uid = p.uid LEFT JOIN {password_policy_expiration} e ON u.uid = e.uid WHERE u.uid > 0 AND u.status = '1' ORDER BY p.created ASC");
-      while ($row = db_fetch_object($result)) {
-        if ($row->uid == 1 && !PASSWORD_POLICY_ADMIN)
-          continue;
+  // Short circuit if no policies are active that use expiration.
+  if (!db_result(db_query("SELECT COUNT(*) FROM {password_policy} WHERE enabled = 1 AND expiration > 0"))) {
+    return;
+  }
 
-        // Use account creation timestamp if there is no entry in password history table.
-        $accounts[$row->uid] = empty($row->created_p) ? $row->created_u : $row->created_p;
-        // Last time a warning was mailed out (if was). We need it because we send warnings only once a day, not on all cron runs.
-        $warns[$row->uid] = $row->warning;
-        // The user was last time unblocked (if was). We don't block this account again for some period of time.
-        $unblocks[$row->uid] = $row->unblocked;
-        // The user was last time unblocked (if was). We don't block this account again for some period of time.
-        $pids[$row->uid] = $row->pid;
+  // Get all users' last password change time. We don't touch blocked accounts
+  $result = db_query("SELECT u.uid AS uid, u.created AS created_u, p.created AS created_p, e.pid AS pid, e.warning AS warning, e.unblocked AS unblocked FROM {users} u LEFT JOIN {password_policy_history} p ON u.uid = p.uid LEFT JOIN {password_policy_expiration} e ON u.uid = e.uid WHERE u.uid > 0 AND u.status = '1' ORDER BY p.created ASC");
+  while ($row = db_fetch_object($result)) {
+    if ($row->uid == 1 && !PASSWORD_POLICY_ADMIN)
+      continue;
+
+    // Use account creation timestamp if there is no entry in password history table.
+    $accounts[$row->uid] = empty($row->created_p) ? $row->created_u : $row->created_p;
+    // Last time a warning was mailed out (if was). We need it because we send warnings only once a day, not on all cron runs.
+    $warns[$row->uid] = $row->warning;
+    // The user was last time unblocked (if was). We don't block this account again for some period of time.
+    $unblocks[$row->uid] = $row->unblocked;
+    // The user was last time unblocked (if was). We don't block this account again for some period of time.
+    $pids[$row->uid] = $row->pid;
+  }
+
+  if ($accounts) {
+    foreach ($accounts as $uid => $last_change) {
+      /* Alternative: $result = db_query("SELECT p.* FROM {password_policy} p INNER JOIN {password_policy_role} r ON p.pid = r.pid INNER JOIN {users_roles} u ON r.rid = u.rid WHERE p.enabled = 1 AND u.uid = %d ORDER BY p.weight LIMIT 1", $uid); */
+      $roles = array(DRUPAL_AUTHENTICATED_RID);
+      $result = db_query("SELECT rid FROM {users_roles} WHERE uid = %d ORDER BY rid", $uid);
+      while ($row = db_fetch_object($result)) {
+        $roles[] = $row->rid;
       }
-      // Calculate expiration time.
-      $expiration_seconds = $expiration*60*60*24;
-      $policy_start = _password_policy_start($expiration_seconds);
-      rsort($warnings, SORT_NUMERIC);
-      $time = time();
-      if ($accounts) {
-        foreach ($accounts as $uid => $last_change) {
+      $policy = _password_policy_load_active_policy($roles);
+      if ($policy) {
+        $expiration = $policy['expiration'];
+        $warnings = !empty($policy['warning']) ? explode(',', $policy['warning']) : array();
+        if (!empty($expiration)) {
+          // Calculate expiration time.
+          $expiration_seconds = $expiration*60*60*24;
+          $policy_start = $policy['created'];
+          if (PASSWORD_POLICY_BEGIN == 1) {
+            $policy_start -= $expiration_seconds;
+          }
+          rsort($warnings, SORT_NUMERIC);
+          $time = time();
           // Check expiration and warning days for each account.
           if (!empty($warnings)) {
             foreach ($warnings as $warning) {
@@ -572,7 +592,8 @@ function password_policy_mail_tokens($pa
  */
 function _password_policy_constraint_validate($pass, &$account) {
   $error = NULL;
-  $policy = _password_policy_load_active_policy();
+  $roles = is_array($account->roles) ? array_keys($account->roles) : array();
+  $policy = _password_policy_load_active_policy($roles);
   if (!empty($policy['policy'])) {
     foreach ($policy['policy'] as $key => $value) {
       if (!call_user_func('password_policy_constraint_'. $key .'_validate', $pass, $value, $account->uid)) {
@@ -648,49 +669,46 @@ function _password_policy_load_policy_by
   if (is_array($row)) {
     // fetch and unserialize the serialized policy
     $row['policy'] = unserialize($row['policy']);
+    // Fetch roles
+    $row['roles'] = array();
+    $result = db_query('SELECT rid FROM {password_policy_role} WHERE pid = %d', $pid);
+    while ($role = db_fetch_object($result)) {
+      $row['roles'][$role->rid] = $role->rid;
+    }
     return $row;
   }
   return NULL;
 }
 
 /**
- * Loads the default (enabled and active) policy.
- *
- * @return
- *   A policy array, or NULL if no active policy exists.
- */
-function _password_policy_load_active_policy() {
-  $result = db_query('SELECT * FROM {password_policy} p WHERE p.enabled = 1');
-  $row = db_fetch_array($result);
-  if (is_array($row)) {
-    // fetch and unserialize the serialized policy
-    $row['policy'] = unserialize($row['policy']);
-    return $row;
-  }
-  return NULL;
-}
-
-/**
- * Returns a timestamp when the policy was enabled.
- * It might be pushed back by the expiration time for the retroactive behaviour
- * based on the configuration options.
+ * Loads the first enabled policy that matches the specified roles.
  *
- * @param $expiration
- *   Expiration time in seconds.
+ * @param $roles
+ *   An array of role IDs.
  *
  * @return
- *   A timestamp when a policy was enabled
+ *   A policy array, or NULL if no active policy exists.
  */
-function _password_policy_start($expiration = 0) {
-  $result = db_query_range("SELECT created FROM {password_policy} WHERE enabled = 1 ORDER BY enabled DESC", 0, 1);
-  if ($row = db_fetch_object($result)) {
-    $policy_start = $row->created;
-  }
-  if (PASSWORD_POLICY_BEGIN == 1) {
-    // Password older than expiration time expires starting from setting the policy.
-    $policy_start -= $expiration;
+function _password_policy_load_active_policy($roles) {
+  static $cache = array();
+  if (empty($roles)) {
+    $roles = array(DRUPAL_ANONYMOUS_RID);
+  }
+  $key = implode(',', $roles);
+  // Use array_key_exists() instead of isset() as NULLs may be in the array.
+  if (!array_key_exists($key, $cache)) {
+    $result = db_query('SELECT p.* FROM {password_policy} p INNER JOIN {password_policy_role} r ON p.pid = r.pid WHERE p.enabled = 1 AND r.rid IN (' . db_placeholders($roles) . ') ORDER BY p.weight LIMIT 1', $roles);
+    $row = db_fetch_array($result);
+    if (is_array($row)) {
+      // fetch and unserialize the serialized policy
+      $row['policy'] = unserialize($row['policy']);
+      $cache[$key] = $row;
+    }
+    else {
+      $cache[$key] = NULL;
+    }
   }
-  return $policy_start;
+  return $cache[$key];
 }
 
 /**
Index: password_policy.theme.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/password_policy/password_policy.theme.inc,v
retrieving revision 1.3
diff -u -p -r1.3 password_policy.theme.inc
--- password_policy.theme.inc	6 Nov 2008 10:50:39 -0000	1.3
+++ password_policy.theme.inc	10 Feb 2010 02:30:29 -0000
@@ -19,9 +19,10 @@ function theme_password_policy_admin_lis
   foreach ($form as $pid => $element) {
     if (isset($element['edit']) && is_array($element['edit'])) {
       $rows[] = array(
-        drupal_render($form['default'][$pid]),
         check_plain($form[$pid]['name']['#value']),
-        drupal_render($form[$pid]['created']),
+        $form[$pid]['roles']['#value'],
+        drupal_render($form['enabled'][$pid]),
+        drupal_render($form['weight'][$pid]),
         drupal_render($form[$pid]['view']),
         drupal_render($form[$pid]['edit']),
         drupal_render($form[$pid]['delete'])
@@ -34,7 +35,7 @@ function theme_password_policy_admin_lis
     unset($form['submit']);
     unset($form['clear']);
   }
-  $header = array(t('Default'), array('data' => t('Name'), 'field' => 'name', 'sort' => 'asc'), t('Enabled'), array('data' => t('Operations'), 'colspan' => 3));
+  $header = array(t('Policy'), t('Roles'), t('Enabled'), t('Weight'), array('data' => t('Operations'), 'colspan' => 3));
   $output = theme('table', $header, $rows);
   $output .= drupal_render($form);
 
