Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.2
diff -u -p -r1.2 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	28 Apr 2008 08:36:06 -0000	1.2
+++ modules/simpletest/drupal_web_test_case.php	30 Apr 2008 00:41:10 -0000
@@ -273,9 +273,7 @@ class DrupalWebTestCase extends UnitTest
   private function _drupalCreateRole($permissions = NULL) {
     // Generate string version of permissions list.
     if ($permissions === NULL) {
-      $permission_string = 'access comments, access content, post comments, post comments without approval';
-    } else {
-      $permission_string = implode(', ', $permissions);
+      $permissions = array('access comments', 'access content', 'post comments', 'post comments without approval');
     }
 
     // Create new role.
@@ -285,8 +283,11 @@ class DrupalWebTestCase extends UnitTest
     $this->assertTrue($role, t('Created role of name: @role_name, id: @rid', array('@role_name' => $role_name, '@rid' => (isset($role->rid) ? $role->rid : t('-n/a-')))), t('Role'));
     if ($role && !empty($role->rid)) {
       // Assign permissions to role and mark it for clean-up.
-      db_query("INSERT INTO {permission} (rid, perm) VALUES (%d, '%s')", $role->rid, $permission_string);
-      $this->assertTrue(db_affected_rows(), t('Created permissions: @perms', array('@perms' => $permission_string)), t('Role'));
+      foreach ($permissions as $permission_string) {
+        db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", $role->rid, $permission_string);
+      }
+      $count = db_result(db_query("SELECT COUNT(*) FROM {role_permission} WHERE rid = %d", $role->rid));
+      $this->assertTrue($count == count($permissions), t('Created permissions: @perms', array('@perms' => implode(', ', $permissions))), t('Role'));
       return $role->rid;
     }
     else {
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.250
diff -u -p -r1.250 system.install
--- modules/system/system.install	16 Apr 2008 11:35:52 -0000	1.250
+++ modules/system/system.install	30 Apr 2008 00:41:10 -0000
@@ -366,11 +366,18 @@ function system_install() {
   // This sets uid 1 (superuser). We skip uid 2 but that's not a big problem.
   db_query("UPDATE {users} SET uid = 1 WHERE name = '%s'", 'placeholder-for-uid-1');
 
+  // Built-in roles.
   db_query("INSERT INTO {role} (name) VALUES ('%s')", 'anonymous user');
   db_query("INSERT INTO {role} (name) VALUES ('%s')", 'authenticated user');
 
-  db_query("INSERT INTO {permission} (rid, perm, tid) VALUES (%d, '%s', %d)", 1, 'access content', 0);
-  db_query("INSERT INTO {permission} (rid, perm, tid) VALUES (%d, '%s', %d)", 2, 'access comments, access content, post comments, post comments without approval', 0);
+  // Anonymous role permissions.
+  db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 1, 'access content');
+
+  // Authenticated role permissions.
+  db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'access comments');
+  db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'access content');
+  db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'post comments');
+  db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'post comments without approval');
 
   db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", 'theme_default', 's:7:"garland";');
   db_query("UPDATE {system} SET status = %d WHERE type = '%s' AND name = '%s'", 1, 'theme', 'garland');
@@ -2864,5 +2871,3 @@ function system_update_7005() {
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
-
-
Index: modules/user/user.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.admin.inc,v
retrieving revision 1.21
diff -u -p -r1.21 user.admin.inc
--- modules/user/user.admin.inc	14 Apr 2008 17:48:43 -0000	1.21
+++ modules/user/user.admin.inc	30 Apr 2008 00:41:11 -0000
@@ -485,6 +485,8 @@ function user_admin_settings() {
   return system_settings_form($form);
 }
 
+
+
 /**
  * Menu callback: administer permissions.
  *
@@ -493,27 +495,20 @@ function user_admin_settings() {
  * @see theme_user_admin_perm()
  */
 function user_admin_perm($form_state, $rid = NULL) {
-  if (is_numeric($rid)) {
-    $result = db_query('SELECT r.rid, p.perm FROM {role} r LEFT JOIN {permission} p ON r.rid = p.rid WHERE r.rid = %d', $rid);
-  }
-  else {
-    $result = db_query('SELECT r.rid, p.perm FROM {role} r LEFT JOIN {permission} p ON r.rid = p.rid ORDER BY name');
-  }
-
-  // Compile role array:
-  // Add a comma at the end so when searching for a permission, we can
-  // always search for "$perm," to make sure we do not confuse
-  // permissions that are substrings of each other.
-  while ($role = db_fetch_object($result)) {
-    $role_permissions[$role->rid] = $role->perm . ',';
-  }
 
   // Retrieve role names for columns.
   $role_names = user_roles();
   if (is_numeric($rid)) {
     $role_names = array($rid => $role_names[$rid]);
   }
+  // Fetch permissions for all roles or the one selected role.
+  $role_permissions = user_role_permissions($role_names);
 
+  // Store $role_names for use when saving the data.
+  $form['role_names'] = array(
+    '#type' => 'value',
+    '#value' => $role_names,
+  );
   // Render role/permission overview:
   $options = array();
   $hide_descriptions = !system_admin_compact_mode();
@@ -537,7 +532,7 @@ function user_admin_perm($form_state, $r
         );
         foreach ($role_names as $rid => $name) {
           // Builds arrays for checked boxes for each role
-          if (strpos($role_permissions[$rid], $perm . ',') !== FALSE) {
+          if (isset($role_permissions[$rid][$perm])) {
             $status[$rid][] = $perm;
           }
         }
@@ -555,24 +550,24 @@ function user_admin_perm($form_state, $r
   return $form;
 }
 
+/**
+ * Save permissions selected on the administer permissions page.
+ *
+ * @see user_admin_perm
+ */
 function user_admin_perm_submit($form, &$form_state) {
-  // Save permissions:
-  $result = db_query('SELECT * FROM {role}');
-  while ($role = db_fetch_object($result)) {
-    if (isset($form_state['values'][$role->rid])) {
-      // Delete, so if we clear every checkbox we reset that role;
-      // otherwise permissions are active and denied everywhere.
-      db_query('DELETE FROM {permission} WHERE rid = %d', $role->rid);
-      $form_state['values'][$role->rid] = array_filter($form_state['values'][$role->rid]);
-      if (count($form_state['values'][$role->rid])) {
-        db_query("INSERT INTO {permission} (rid, perm) VALUES (%d, '%s')", $role->rid, implode(', ', array_keys($form_state['values'][$role->rid])));
-      }
+  foreach ($form_state['values']['role_names'] as $rid => $name) {
+    $checked = array_filter($form_state['values'][$rid]);
+    // Delete existing permissions for the role. This handles "unchecking" checkboxes.
+    db_query("DELETE FROM {role_permission} WHERE rid = %d", $rid);
+    foreach ($checked as $permission) {
+      db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", $rid, $permission);
     }
   }
 
   drupal_set_message(t('The changes have been saved.'));
 
-  // Clear the cached pages
+  // Clear the cached pages and blocks.
   cache_clear_all();
 }
 
@@ -697,7 +692,7 @@ function user_admin_role_submit($form, &
   }
   else if ($form_state['values']['op'] == t('Delete role')) {
     db_query('DELETE FROM {role} WHERE rid = %d', $form_state['values']['rid']);
-    db_query('DELETE FROM {permission} WHERE rid = %d', $form_state['values']['rid']);
+    db_query('DELETE FROM {role_permission} WHERE rid = %d', $form_state['values']['rid']);
     // Update the users who have this role set:
     db_query('DELETE FROM {users_roles} WHERE rid = %d', $form_state['values']['rid']);
 
Index: modules/user/user.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.install,v
retrieving revision 1.11
diff -u -p -r1.11 user.install
--- modules/user/user.install	16 Apr 2008 11:26:29 -0000	1.11
+++ modules/user/user.install	30 Apr 2008 00:41:11 -0000
@@ -41,38 +41,26 @@ function user_schema() {
     'primary key' => array('aid'),
   );
 
-  $schema['permission'] = array(
-    'description' => t('Stores permissions for users.'),
+  $schema['role_permission'] = array(
+    'description' => t('Stores the permissions assigned to user roles.'),
     'fields' => array(
-      'pid' => array(
-        'type' => 'serial',
-        'not null' => TRUE,
-        'description' => t('Primary Key: Unique permission ID.'),
-      ),
       'rid' => array(
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
-        'default' => 0,
-        'description' => t('The {role}.rid to which the permissions are assigned.'),
+        'description' => t('Foreign Key: {role}.rid.'),
       ),
-      'perm' => array(
-        'type' => 'text',
-        'not null' => FALSE,
-        'size' => 'big',
-        'description' => t('List of permissions being assigned.'),
-      ),
-      'tid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
+      'permission' => array(
+        'type' => 'varchar',
+        'length' => 64,
         'not null' => TRUE,
-        'default' => 0,
-        'description' => t('Originally intended for taxonomy-based permissions, but never used.'),
+        'default' => '',
+        'description' => t('A single permission granted to the role identified by rid.'),
       ),
     ),
-    'primary key' => array('pid'),
+    'primary key' => array('rid', 'permission'),
     'indexes' => array(
-      'rid' => array('rid'),
+      'permission' => array('permission'),
     ),
   );
 
@@ -83,7 +71,7 @@ function user_schema() {
         'type' => 'serial',
         'unsigned' => TRUE,
         'not null' => TRUE,
-        'description' => t('Primary Key: Unique role id.'),
+        'description' => t('Primary Key: Unique role ID.'),
       ),
       'name' => array(
         'type' => 'varchar',
@@ -304,6 +292,47 @@ function user_update_7001() {
 }
 
 /**
+ * Convert to new method of storing permissions.
+ */
+function user_update_7002() {
+  $ret = array();
+
+  $schema['role_permission'] = array(
+    'fields' => array(
+      'rid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'permission' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+    ),
+    'primary key' => array('rid', 'permission'),
+    'indexes' => array(
+      'permission' => array('permission'),
+    ),
+  );
+
+  db_create_table($ret, 'role_permission', $schema['role_permission']);
+
+  // Copy the permissions from the old {permission} table to the new {role_permission} table.
+  $result = db_query("SELECT rid, perm FROM {permission} ORDER BY rid ASC");
+  while ($role = db_fetch_object($result)) {
+    foreach (explode(', ', $role->perm) as $perm) {
+      db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", $role->rid, $perm);
+    }
+  }
+
+  db_drop_table($ret, 'permission');
+
+  return $ret;
+}
+
+/**
  * @} End of "defgroup user-updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.904
diff -u -p -r1.904 user.module
--- modules/user/user.module	23 Apr 2008 20:01:56 -0000	1.904
+++ modules/user/user.module	30 Apr 2008 00:41:11 -0000
@@ -434,6 +434,57 @@ function user_password($length = 10) {
 }
 
 /**
+ * Determine the permissions for one or more roles.
+ *
+ * @param $roles
+ *   An array whose keys are the role IDs of interest, such as $user->roles.
+ * @param $reset
+ *   Optional parameter - if TRUE data in the static variable is rebuilt.
+ *
+ * @return
+ *   An array indexed by role ID.  Each value is an array whose keys are the
+ *   permission strings for the given role ID.
+ */
+function user_role_permissions($roles = array(), $reset = FALSE) {
+  static $stored_permissions = array();
+
+  if ($reset) {
+    // Clear the data cached in the static variable.
+    $stored_permissions = array();
+  }
+
+  $role_permissions = $fetch = array();
+
+  if ($roles) {
+    foreach ($roles as $rid => $name) {
+      if (isset($role_permissions[$rid])) {
+        $role_permissions[$rid] = $stored_permissions[$rid];
+      }
+      else {
+        // Add this rid to the list of those needing to be fetched.
+        $fetch[] = $rid;
+      }
+    }
+
+    if ($fetch) {
+      // Get from the database permissions that were not in the static variable.
+      $result = db_query("SELECT r.rid, p.permission FROM {role} r LEFT JOIN {role_permission} p ON p.rid = r.rid WHERE r.rid IN (" . db_placeholders($fetch) . ")", $fetch);
+
+      while ($row = db_fetch_array($result)) {
+        $stored_permissions[$row['rid']][$row['permission']] = TRUE;
+      }
+      foreach ($fetch as $rid) {
+        if (isset($stored_permissions[$rid])) {
+          $role_permissions[$rid] = $stored_permissions[$rid];
+        }
+      }
+    }
+  }
+
+  return $role_permissions;
+}
+
+/**
  * Determine whether the user has a given privilege.
  *
  * @param $string
@@ -472,11 +523,11 @@ function user_access($string, $account =
   // To reduce the number of SQL queries, we cache the user's permissions
   // in a static variable.
   if (!isset($perm[$account->uid])) {
-    $result = db_query("SELECT p.perm FROM {role} r INNER JOIN {permission} p ON p.rid = r.rid WHERE r.rid IN (" . db_placeholders($account->roles) . ")", array_keys($account->roles));
+    $role_permissions = user_role_permissions($account->roles, $reset);
 
     $perms = array();
-    while ($row = db_fetch_object($result)) {
-      $perms += array_flip(explode(', ', $row->perm));
+    foreach ($role_permissions as $one_role) {
+      $perms += $one_role;
     }
     $perm[$account->uid] = $perms;
   }
@@ -1608,7 +1659,7 @@ function user_roles($membersonly = FALSE
   );
 
   if (!empty($permission)) {
-    $result = db_query("SELECT r.* FROM {role} r INNER JOIN {permission} p ON r.rid = p.rid WHERE p.perm LIKE '%%%s%%' ORDER BY r.name", $permission);
+    $result = db_query("SELECT r.* FROM {role} r INNER JOIN {role_permission} p ON r.rid = p.rid WHERE p.permission = '%s' ORDER BY r.name", $permission);
   }
   else {
     $result = db_query('SELECT * FROM {role} ORDER BY name');
@@ -1872,8 +1923,8 @@ function user_filters() {
   ksort($options);
   $filters['permission'] = array(
     'title' => t('permission'),
-    'join' => 'LEFT JOIN {permission} p ON ur.rid = p.rid',
-    'where' => " ((p.perm IS NOT NULL AND p.perm LIKE '%%%s%%') OR u.uid = 1) ",
+    'join' => 'LEFT JOIN {role_permission} p ON ur.rid = p.rid',
+    'where' => " (p.permission = '%s' OR u.uid = 1) ",
     'options' => $options,
   );
 
