diff --git a/includes/admin.settings.inc b/includes/admin.settings.inc
index c65c20c..a1e504a 100644
--- a/includes/admin.settings.inc
+++ b/includes/admin.settings.inc
@@ -26,6 +26,21 @@ function project_issue_settings_form(&$form_state) {
     '#description' => t('If selected, user signatures will be appended to the display of issue followups.'),
   );
 
+  if (module_exists('flag')) {
+    $flags = flag_get_flags();
+    $flag_options[0] = t('- None -');
+    foreach ($flags as $flag) {
+      $flag_options[$flag->name] = $flag->title;
+    }
+    $form['project_issue_subscription_flag'] = array(
+      '#title' => t('Issue subscription flag'),
+      '#type' => 'select',
+      '#options' => $flag_options,
+      '#default_value' => variable_get('project_issue_subscription_flag', NULL),
+      '#description' => t('Allows users to receive e-mail notifications about issues they flagged. If none is selected, users may only subscribe by commenting on an issue.'),
+    );
+  }
+
   $form['project_issue_reply_to'] = array(
     '#type' => 'textfield',
     '#title' => t('Reply-to address on e-mail notifications'),
diff --git a/includes/mail.inc b/includes/mail.inc
index 3a554ec..061529a 100644
--- a/includes/mail.inc
+++ b/includes/mail.inc
@@ -249,18 +249,104 @@ function project_issue_mail_notify($nid) {
     }
   }
 
-  if (count($uids)) {
+  // Retrieve list of users being globally subscribed to all issues.
+  $accounts_all_issues = array();
+  $result = db_query("SELECT pisu.uid, u.name, u.mail FROM {project_issue_subscriptions_user} pisu INNER JOIN {users} u ON pisu.uid = u.uid WHERE u.status = 1 AND pisu.level = " . PROJECT_ISSUE_SUBSCRIPTIONS_ALL);
+  while ($account = db_fetch_object($result)) {
+    $accounts_all_issues[$account->uid] = $account;
+  }
+
+  // Check whether Flag module integration is enabled for e-mail notifications.
+  $flag_integration = (module_exists('flag') && variable_get('project_issue_subscription_flag', 0));
+
+  // Retrieve list of users being globally subscribed to "own" issues (without
+  // Flag integration).
+  $accounts_flagged_issues = array();
+  if (!$flag_integration) {
+    $result = db_query("SELECT pisu.uid, u.name, u.mail FROM {project_issue_subscriptions_user} pisu INNER JOIN {users} u ON pisu.uid = u.uid WHERE u.status = 1 AND pisu.level = " . PROJECT_ISSUE_SUBSCRIPTIONS_FLAGGED);
+    while ($account = db_fetch_object($result)) {
+      $accounts_flagged_issues[$account->uid] = $account;
+    }
+  }
+
+  // Check per-project subscriptions of involved users.
+  if (!empty($uids)) {
     $placeholders = implode(',', array_fill(0, count($uids), '%d'));
-    array_unshift($uids, $node->project_issue['pid']);
-    $result = db_query("SELECT p.*, u.uid, u.name, u.mail FROM {project_subscriptions} p INNER JOIN {users} u ON p.uid = u.uid WHERE u.status = 1 AND p.nid = %d AND (p.level = 2 OR (p.level = 1 AND u.uid IN ($placeholders)))", $uids);
+    $args = $uids;
+    array_unshift($args, $node->project_issue['pid']);
+
+    // Check which involved users are subscribed to all issues of the project.
+    $result = db_query("SELECT pisp.uid, u.name, u.mail
+      FROM {project_issue_subscriptions_project} pisp
+      INNER JOIN {users} u ON pisp.uid = u.uid
+      WHERE u.status = 1 AND pisp.nid = %d AND pisp.uid IN ($placeholders) AND pisp.level = " . PROJECT_ISSUE_SUBSCRIPTIONS_ALL, $args);
+    while ($account = db_fetch_object($result)) {
+      $accounts_all_issues[$account->uid] = $account;
+    }
+
+    // Check which involved users are subscribed to "own" issues of the project
+    // (without Flag integration).
+    if (!$flag_integration) {
+      $result = db_query("SELECT pisp.uid, u.name, u.mail
+        FROM {project_issue_subscriptions_project} pisp
+        INNER JOIN {users} u ON pisp.uid = u.uid
+        WHERE u.status = 1 AND pisp.nid = %d AND pisp.uid IN ($placeholders) AND pisp.level = " . PROJECT_ISSUE_SUBSCRIPTIONS_FLAGGED, $args);
+      while ($account = db_fetch_object($result)) {
+        $accounts_flagged_issues[$account->uid] = $account;
+      }
+    }
   }
-  else {
-    $result = db_query('SELECT p.*, u.uid, u.name, u.mail FROM {project_subscriptions} p INNER JOIN {users} u ON p.uid = u.uid WHERE u.status = 1 AND p.nid = %d AND p.level = 2', $node->project_issue['pid']);
+
+  // Check which users subscribed to the issue via Flag.
+  if ($flag_integration) {
+    // Retrieve all users who flagged the issue.
+    $flag_contents = flag_get_content_flags('node', $node->nid, variable_get('project_issue_subscription_flag', 0));
+
+    // Now use the list of users that flagged the issue to retrieve the list of
+    // users who are subscribed to flagged issues of the project. For example, a
+    // user who globally subscribed to no issues should not get an e-mail
+    // notification after flagging the issue. That should only happen when the
+    // user subscribed to flagged issues for the project.
+    if (!empty($flag_contents)) {
+      $flags_uids = array_keys($flag_contents);
+      $placeholders = implode(',', array_fill(0, count($flags_uids), '%d'));
+      $args = $flags_uids;
+
+      // Check which users flagged the issue and globally subscribed to flagged
+      // issues (across projects).
+      $result = db_query("SELECT pisu.uid, u.name, u.mail
+        FROM {project_issue_subscriptions_user} pisu
+        INNER JOIN {users} u ON pisu.uid = u.uid
+        WHERE u.status = 1 AND pisu.uid IN ($placeholders) AND pisu.level = " . PROJECT_ISSUE_SUBSCRIPTIONS_FLAGGED, $args);
+      while ($account = db_fetch_object($result)) {
+        $accounts_flagged_issues[$account->uid] = $account;
+      }
+
+      // Check which users flagged the issue and subscribed to flagged issues of
+      // the project.
+      array_unshift($args, $node->project_issue['pid']);
+      $result = db_query("SELECT pisp.uid, u.name, u.mail
+        FROM {project_issue_subscriptions_project} pisp
+        INNER JOIN {users} u ON pisp.uid = u.uid
+        WHERE u.status = 1 AND pisp.nid = %d AND pisp.uid IN ($placeholders) AND pisp.level = " . PROJECT_ISSUE_SUBSCRIPTIONS_FLAGGED, $args);
+      while ($account = db_fetch_object($result)) {
+        $accounts_flagged_issues[$account->uid] = $account;
+      }
+    }
   }
 
+  // Lastly, join the lists of users being
+  // - globally subscribed to all issues
+  // - globally subscribed to own issues (without Flag integration)
+  // - globally subscribed to flagged issues
+  // - subscribed to all issues of the project
+  // - subscribed to own issues of the project (without Flag integration)
+  // - subscribed to flagged issues of the project
+  $recipients = $accounts_all_issues + $accounts_flagged_issues;
+
   // To save workload, check here if either the anonymous role or the
   // authenticated role has the 'view uploaded files' permission, since
-  // we only need to process each user's file access permission if this
+  // we only need to process each user's file\ access permission if this
   // is NOT the case.
   $check_file_perms = !db_result(db_query("SELECT COUNT(*) FROM {permission} WHERE perm LIKE '%view uploaded files%' AND rid IN (%d, %d)", DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID));
 
@@ -290,7 +376,7 @@ function project_issue_mail_notify($nid) {
   $from = '"' . mime_header_encode($sender->name) . "\" <$sender->mail>";
 
   // Send notification to each connected user.
-  while ($recipient = db_fetch_object($result)) {
+  foreach ($recipients as $recipient) {
     // To save work, only go through a user_load if we need it.
     if ($check_file_perms || $check_node_access) {
       $account = user_load(array('uid' => $recipient->uid));
diff --git a/includes/project_node.inc b/includes/project_node.inc
index ce6c661..f62e18a 100644
--- a/includes/project_node.inc
+++ b/includes/project_node.inc
@@ -65,6 +65,6 @@ function project_issue_project_delete($node) {
     node_delete($issue->nid);
   }
   db_query('DELETE FROM {project_issue_projects} WHERE nid = %d', $node->nid);
-  db_query('DELETE FROM {project_subscriptions} WHERE nid = %d', $node->nid);
+  db_query('DELETE FROM {project_issue_subscriptions_project} WHERE nid = %d', $node->nid);
 }
 
diff --git a/includes/subscribe.inc b/includes/subscribe.inc
index 703114b..1459cc3 100644
--- a/includes/subscribe.inc
+++ b/includes/subscribe.inc
@@ -1,172 +1,192 @@
 <?php
 
-function project_issue_subscribe($form_state, $project_nid = 0) {
-  global $user;
+/**
+ * @file
+ * UI functionality for project issue e-mail notifications.
+ *
+ * Note: Internal function and variable names use "subscriptions" instead of
+ * "notifications" for historical reasons.
+ *
+ * @see mail.inc
+ */
+
+/**
+ * Returns project issue subscription levels.
+ */
+function _project_issue_subscriptions_levels() {
+  return array(
+    PROJECT_ISSUE_SUBSCRIPTIONS_NONE => t('None'),
+    PROJECT_ISSUE_SUBSCRIPTIONS_FLAGGED => t('Subscribed issues'),
+    PROJECT_ISSUE_SUBSCRIPTIONS_ALL => t('All issues'),
+  );
+}
 
-  if (!valid_email_address($user->mail)) {
-    drupal_set_message(t('You need to provide a valid e-mail address to subscribe to issue e-mails. Please edit your user information.'), 'error');
-    drupal_goto('user/'. $user->uid .'/edit');
-  }
+/**
+ * Form constructor for global user project issue subscriptions.
+ */
+function project_issue_subscriptions_user_form(&$form_state, $account) {
+  // Global subscription level.
+  project_issue_subscriptions_user_settings_load($account);
 
-  $levels = array(0 => t('None'), 1 => t('Own issues'), 2 => t('All issues'));
+  $options = _project_issue_subscriptions_levels();
 
-  if ($project_nid) {
-    if (!is_numeric($project_nid)) {
-      $project_nid = db_result(db_query(db_rewrite_sql("SELECT p.nid FROM {project_projects} p WHERE p.uri = '%s'", 'p'), $project_nid));
-    }
-    if (!$project_nid) {
-      return drupal_not_found();
-    }
+  $form['account'] = array(
+    '#type' => 'value',
+    '#value' => $account,
+  );
+  $form['#tree'] = TRUE;
 
-    $project = node_load($project_nid);
-    project_project_set_breadcrumb($project, TRUE);
+  $form['project_issue_subscriptions'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Issue notifications'),
+    '#collapsible' => TRUE,
+  );
 
-    $level = db_result(db_query('SELECT level FROM {project_subscriptions} WHERE nid = %d AND uid = %d', $project->nid, $user->uid));
-    $form['single'] = array(
-      '#type' => 'value',
-      '#value' => $project->nid,
-    );
-    $form['#project'] = array(
-      '#type' => 'value',
-      '#value' => $project,
-    );
-    $form['subscribe'] = array(
-      '#type' => 'markup',
-      '#value' => '<p>'. t('Subscribe to receive e-mail notification when an issue for this project is updated.') .'</p>',
+  $form['project_issue_subscriptions']['level'] = array(
+    '#type' => 'radios',
+    '#title' => t('Global issue notification level'),
+    '#options' => $options,
+    '#default_value' => $account->project_issue_subscriptions['level'],
+  );
+
+  // Per-project subscription level (only enabled).
+  // We only allow to change (and remove) per-project subscriptions on this
+  // form. Users are able to subscribe to further projects by visiting the
+  // individual project pages. In terms of UX, that's preferred anyway, since
+  // a user normally wants to know and be sure what exactly she subscribes to.
+  $form['project_issue_subscriptions']['projects'] = array(
+    '#theme' => 'project_issue_subscriptions_projects_table',
+    '#header' => array(t('Project'), t('Notification level')),
+  );
+  $result = db_query(db_rewrite_sql("SELECT pisp.nid, n.title, pisp.level
+    FROM {project_issue_subscriptions_project} pisp
+    INNER JOIN {node} n ON n.nid = pisp.nid
+    WHERE n.status = 1 AND pisp.uid = %d ORDER BY n.title
+    ", 'n'), $account->uid);
+  while ($project = db_fetch_object($result)) {
+    $form['project_issue_subscriptions']['projects'][$project->nid]['title'] = array(
+      '#value' => l($project->title, "node/$project->nid"),
     );
-    $form['options']['#tree'] = TRUE;
-    $form['options'][$project->nid] = array(
-      '#type' => 'radios',
-      '#title' => t('Subscribe to @project issues', array('@project' => $project->title)),
-      '#default_value' => isset($level) ? $level : 0,
-      '#options' => $levels,
+    $form['project_issue_subscriptions']['projects'][$project->nid]['level'] = array(
+      '#type' => 'select',
+      '#options' => $options,
+      '#default_value' => $project->level,
+      // Adjust #parents to get a simple $project->nid => $level mapping in the
+      // submit handler. Without adjustment, the value would be
+      // array('level' => $level).
+      '#parents' => array('project_issue_subscriptions', 'projects', $project->nid),
     );
-
   }
-  else {
 
-    $form['buttons']['all'] = array(
-      '#type' => 'markup',
-      '#value' => t('All projects'),
-    );
-    foreach ($levels as $key => $level) {
-      $form['buttons'][$level] = array(
-        '#type' => 'submit',
-        '#name' => 'all',
-        '#value' => $level,
-      );
-    }
-    $nids = array();
-
-    $result = db_query(db_rewrite_sql("SELECT s.nid, n.title, s.level, p.uri FROM {project_subscriptions} s INNER JOIN {node} n ON n.nid = s.nid INNER JOIN {project_projects} p ON n.nid = p.nid WHERE n.type = 'project_project' AND n.status = 1 AND s.uid = %d ORDER BY n.title", 's'), $user->uid);
-    while ($project = db_fetch_object($result)) {
-      $form['project'][$project->nid]['title'] = array(
-        '#value' => l($project->title, "node/$project->nid"),
-      );
-      foreach ($levels as $key => $level) {
-        if ($project->level == $key) {
-          $status[$project->nid] = $key;
-        }
-      }
-      $nids[] = $project->nid;
-    }
-
-    if (empty($nids)) {
-      $placeholders = '';
-    }
-    else {
-      $placeholders = " AND n.nid NOT IN (". implode(',', array_fill(0, count($nids), '%d')) .")";
-    }
-
-    $result = db_query(db_rewrite_sql("SELECT n.nid, n.title, p.uri FROM {node} n INNER JOIN {project_projects} p ON n.nid = p.nid WHERE n.type = 'project_project' AND n.status = 1". ($nids ?  $placeholders : "") ." ORDER BY n.title"), $nids);
-    while ($project = db_fetch_object($result)) {
-      $form['project'][$project->nid]['title'] = array(
-        '#value' => l($project->title, "node/$project->nid"),
-      );
-      $nids[] = $project->nid;
-    }
-
-    foreach ($nids as $nid) {
-      $form['options']['#tree'] = TRUE;
-      $form['options'][$nid] = array(
-        '#type' => 'radios',
-        '#default_value' => isset($status[$nid]) ? $status[$nid] : 0,
-        '#options' => $levels,
-      );
-    }
-  }
-  $form['submit'] = array(
+  $form['actions']['submit'] = array(
     '#type' => 'submit',
-    '#value' => t('Subscribe'),
+    '#value' => t('Save'),
+    '#weight' => 100,
   );
   return $form;
 }
 
-function theme_project_issue_subscribe($form) {
-  global $user;
-
-  $output = '';
-
-  if (empty($form['#project'])) {
-    $output .= project_issue_query_result_links();
-  }
-  else {
-    $project = $form['#project']['#value'];
-    $output .= project_issue_query_result_links($project->project['uri']);
+/**
+ * Form submission handler for project_issue_user_subscribe_form().
+ */
+function project_issue_subscriptions_user_form_submit($form, &$form_state) {
+  // Update the global issue subscription settings.
+  $account = $form_state['values']['account'];
+  $account->project_issue_subscriptions = $form_state['values']['project_issue_subscriptions'];
+  project_issue_subscriptions_user_settings_save($account);
+
+  // Insert the new per-project settings.
+  if (!empty($form_state['values']['project_issue_subscriptions']['projects'])) {
+    foreach ($form_state['values']['project_issue_subscriptions']['projects'] as $nid => $level) {
+      project_issue_subscriptions_project_setting_save($account->uid, $nid, $level);
+    }
   }
 
-  if (!isset($form['single'])) {
-    $levels = array(0 => t('None'), 1 => t('Own issues'), 2 => t('All issues'));
-    $headers = array_merge(array(t('Project')), $levels);
+  drupal_set_message(t('Your notification settings have been updated.'));
+}
 
+/**
+ * Returns HTML for per-project subscription levels table in project_issue_user_subscribe_form().
+ */
+function theme_project_issue_subscriptions_projects_table($element) {
+  $output = '';
+  $rows = array();
+  foreach (element_children($element) as $nid) {
     $row = array();
-    foreach (element_children($form['buttons']) as $key) {
-      $row[] = drupal_render($form['buttons'][$key]);
-    }
-    $rows = array($row);
-
-    foreach (element_children($form['project']) as $key) {
-      $row = array(drupal_render($form['project'][$key]['title']));
-      foreach ($levels as $level => $name) {
-        $row[] = drupal_render($form['options'][$key][$level]);
-      }
-      $rows[] = $row;
-    }
-    $output .= theme('table', $headers, $rows);
+    $row[] = drupal_render($element[$nid]['title']);
+    $row[] = drupal_render($element[$nid]['level']);
+    $rows[] = $row;
+  }
+  if (!empty($rows)) {
+    $output .= theme('table', $element['#header'], $rows);
   }
 
-  $output .= drupal_render($form);
+  $output .= drupal_render($element);
   return $output;
 }
 
-function project_issue_subscribe_submit($form, &$form_state) {
-
+/**
+ * Form constructor for per-project issue subscription.
+ */
+function project_issue_subscriptions_project_form($form_state, $project_nid) {
   global $user;
-  $all = $form_state['clicked_button']['#value'];
 
-  $levels = array(0 => t('None'), 1 => t('Own issues'), 2 => t('All issues'));
+  // Ensure the account has a valid e-mail address; may not be the case for
+  // accounts created by external authentication providers.
+  if (!valid_email_address($user->mail)) {
+    drupal_set_message(t('You need to provide a valid e-mail address to subscribe to issue e-mails. Please edit your user information.'), 'error');
+    drupal_goto('user/' . $user->uid . '/edit');
+  }
 
-    // Remove previous subscriptions for user.
-    if (isset($form_state['values']['single'])) {
-      db_query('DELETE FROM {project_subscriptions} WHERE nid = %d AND uid = %d', $form_state['values']['single'], $user->uid);
-    }
-    else {
-      db_query('DELETE FROM {project_subscriptions} WHERE uid = %d', $user->uid);
-    }
+  if (!is_numeric($project_nid)) {
+    $project_nid = project_get_nid_from_uri($project_nid);
+  }
+  if (!$project_nid || !($project = node_load($project_nid))) {
+    return drupal_not_found();
+  }
 
-    $_level = array_search($all, $levels);
+  project_project_set_breadcrumb($project, TRUE);
+  drupal_set_title(t('Subscribe to @project issues', array('@project' => $project->title)));
 
-    foreach ($form_state['values']['options'] as $nid => $level) {
-      if ($_level !== 0 && $level !== 0) {
-        db_query('INSERT INTO {project_subscriptions} (nid, uid, level) VALUES (%d, %d, %d)', $nid, $user->uid, $_level ? $_level : $level);
-      }
-    }
-    drupal_set_message(t('Subscription settings saved.'));
+  $form['help'] = array(
+    '#prefix' => '<p>',
+    '#value' => t('These settings apply to the %project project only. You can see and change settings for all projects in your <a href="@account-url">user account</a>.', array(
+      '%project' => $project->title,
+      '@account-url' => url('user/' . $user->uid . '/project-issue'),
+    )),
+    '#suffix' => '</p>',
+  );
 
-    if (isset($form['single'])) {
-      $form_state['redirect'] = 'project/issues/subscribe-mail/'. $form['#project']['#value']->project['uri'];
-    }
-    else {
-      $form_state['redirect'] = 'project/issues/subscribe-mail';
-    }
+  $form['nid'] = array(
+    '#type' => 'value',
+    '#value' => $project->nid,
+  );
+  $form['uid'] = array(
+    '#type' => 'value',
+    '#value' => $user->uid,
+  );
+
+  $level = project_issue_subscriptions_project_setting_load($user->uid, $project->nid);
+  $form['level'] = array(
+    '#type' => 'radios',
+    '#title' => t('Notification level'),
+    '#options' => _project_issue_subscriptions_levels(),
+    '#default_value' => $level,
+    '#description' => t('Receive an e-mail notification when an issue for this project is updated.'),
+  );
+
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Subscribe'),
+  );
+  return $form;
+}
+
+/**
+ * Form submission handler for project_issue_user_subscribe_form().
+ */
+function project_issue_subscriptions_project_form_submit($form, &$form_state) {
+  project_issue_subscriptions_project_setting_save($form_state['values']['uid'], $form_state['values']['nid'], $form_state['values']['level']);
+
+  drupal_set_message(t('Your subscription setting has been updated.'));
 }
diff --git a/project_issue.info b/project_issue.info
index 2225d1a..2883ad5 100644
--- a/project_issue.info
+++ b/project_issue.info
@@ -1,9 +1,11 @@
 name = Project issue tracking
 description = Provides issue tracking for the project.module.
+package = Project
+core = 6.x
 dependencies[] = project
 dependencies[] = views
 dependencies[] = comment
 dependencies[] = comment_upload
 dependencies[] = upload
-package = Project
-core = 6.x
+; @see http://drupal.org/project/module_supports
+recommends[] = flag
diff --git a/project_issue.install b/project_issue.install
index 78eff23..c28ea27 100644
--- a/project_issue.install
+++ b/project_issue.install
@@ -282,7 +282,32 @@ function project_issue_schema() {
     'primary key' => array('priority'),
   );
 
-  $schema['project_subscriptions'] = array(
+  $schema['project_issue_subscriptions_user'] = array(
+    'description' => 'Stores global issue subscriptions per user.',
+    'fields' => array(
+      'uid' => array(
+        'description' => 'The {users}.uid for this subscriber.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'level' => array(
+        'description' => 'The global subscription setting level. 0 = None, 1 = Flagged, 2 = All.',
+        'type' => 'int',
+        'size' => 'tiny',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('uid'),
+    'indexes' => array(
+      'uid_level' => array('uid', 'level'),
+    ),
+  );
+
+  $schema['project_issue_subscriptions_project'] = array(
     'description' => 'Table keeping track of per-user project_issue subscriptions.',
     'fields' => array(
       'nid' => array(
@@ -308,6 +333,7 @@ function project_issue_schema() {
         'default' => 0,
       ),
     ),
+    'primary key' => array('uid', 'nid'),
     'indexes' => array(
       'project_subscriptions_nid_uid_level' => array('nid', 'uid', 'level'),
     ),
@@ -705,3 +731,105 @@ function project_issue_update_6007() {
   db_create_table($ret, 'project_issue_user_preference', $table);
   return $ret;
 }
+
+/**
+ * Clean up duplicates in {project_subscriptions}.
+ */
+function project_issue_update_6007(&$sandbox) {
+  $ret = array();
+
+  if (!isset($sandbox['total'])) {
+    // Pull counts of all duplicate entries.
+    $result = db_query("SELECT COUNT(*) AS count FROM {project_subscriptions} GROUP BY nid, uid HAVING COUNT(*) > 1");
+    $sandbox['count'] = 0;
+    $sandbox['total'] = 0;
+    // Sum up the duplicates, removing the row we'll be keeping.
+    while ($row = db_fetch_object($result)) {
+      $sandbox['total'] = $sandbox['total'] + $row->count - 1;
+    }
+  }
+
+  $limit = 100;
+
+  $result = db_query("SELECT nid, uid FROM {project_subscriptions} GROUP BY nid, uid HAVING COUNT(*) > 1 LIMIT %d", $limit);
+
+  while ($row = db_fetch_object($result)) {
+    $sandbox['count']++;
+    $nid = intval($row->nid);
+    $uid = intval($row->uid);
+    // Only delete one row at a time.
+    $ret[] = update_sql("DELETE FROM {project_subscriptions} WHERE nid = $nid AND uid = $uid LIMIT 1");
+  }
+
+  // Check to see if finished, report progress if not.
+  if ($sandbox['count'] >= $sandbox['total']) {
+    $ret['#finished'] = 1;
+  }
+  else {
+    $ret['#finished'] = $sandbox['count'] / $sandbox['total'];
+  }
+  return $ret;
+}
+
+/**
+ * Revamp project issue subscriptions for Flag integration.
+ */
+function project_issue_update_6008() {
+  $ret = array();
+
+  // Delete obsolete 'project_issue_global_subscribe_page' variable.
+  variable_del('project_issue_global_subscribe_page');
+
+  // Rename {project_subscriptions} to {project_issue_subscriptions_project}.
+  db_rename_table($ret, 'project_subscriptions', 'project_issue_subscriptions_project');
+
+  // Create new global issue subscription setting table.
+  db_create_table($ret, 'project_issue_subscriptions_user', array(
+    'description' => 'Stores global issue subscriptions per user.',
+    'fields' => array(
+      'uid' => array(
+        'description' => 'The {users}.uid for this subscriber.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'level' => array(
+        'description' => 'The global subscription setting level. 0 = None, 1 = Flagged, 2 = All.',
+        'type' => 'int',
+        'size' => 'tiny',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('uid'),
+    'indexes' => array(
+      'uid_level' => array('uid', 'level'),
+    ),
+  ));
+
+  return $ret;
+}
+
+/**
+ * Ensure that existing project subscriptions are clean.
+ */
+function project_issue_update_6009() {
+  $ret = array();
+  $ret[] = update_sql("DELETE FROM {project_issue_subscriptions_project} WHERE level = 0");
+  return $ret;
+}
+
+/**
+ * Add primary key to {project_issue_subscriptions_project}.
+ *
+ * WARNING: This update can take a *very* long time (potentially hours) if
+ * there are a high number of rows in the table. The test table had 2.34
+ * million rows, and the query took over three hours on reasonable hardware.
+ */
+function project_issue_update_6010() {
+  $ret = array();
+  db_add_primary_key($ret, 'project_issue_subscriptions_project', array('uid', 'nid'));
+  return $ret;
+}
diff --git a/project_issue.module b/project_issue.module
index c754844..3a2f0b6 100644
--- a/project_issue.module
+++ b/project_issue.module
@@ -16,6 +16,21 @@ define('PROJECT_ISSUE_MAIL_BODY_NEW_CONTENT', 1);
 
 
 /**
+ * Project issue subscriptions level: Not subscribed.
+ */
+define('PROJECT_ISSUE_SUBSCRIPTIONS_NONE', 0);
+
+/**
+ * Project issue subscriptions level: Own/flagged issues.
+ */
+define('PROJECT_ISSUE_SUBSCRIPTIONS_FLAGGED', 1);
+
+/**
+ * Project issue subscriptions level: All issues.
+ */
+define('PROJECT_ISSUE_SUBSCRIPTIONS_ALL', 2);
+
+/**
  * Implementation of hook_init().
  */
 function project_issue_init() {
@@ -66,20 +81,28 @@ function project_issue_menu() {
     'type' => MENU_NORMAL_ITEM,
     'file' => 'includes/statistics.inc',
   );
-  $path = 'project/issues/subscribe-mail';
-  if (!variable_get('project_issue_global_subscribe_page', TRUE)) {
-    // If we don't want the global subscribe page, require an argument.
-    $path .= '/%';
-  }
-  $items[$path] = array(
+
+  // Project issue subscriptions.
+  $items['project/issues/subscribe-mail/%'] = array(
     'title' => 'Subscribe',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('project_issue_subscribe', 3),
+    'page arguments' => array('project_issue_subscriptions_project_form', 3),
     'access callback' => 'project_issue_menu_access',
     'access arguments' => array('auth'),
     'type' => MENU_NORMAL_ITEM,
     'file' => 'includes/subscribe.inc',
   );
+  $items['user/%user/project-issue'] = array(
+    'title' => 'Notifications',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('project_issue_subscriptions_user_form', 1),
+    'access callback' => 'project_issue_menu_access',
+    'access arguments' => array('auth'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'includes/subscribe.inc',
+  );
+
+  // Search.
   if (module_exists('search')) {
     $items['search/issues'] = array(
       'title' => 'Issues',
@@ -313,10 +336,10 @@ function project_issue_theme() {
         'change' => NULL,
       ),
     ),
-    'project_issue_subscribe' => array(
-      'file' => 'issue.inc',
+    'project_issue_subscriptions_projects_table' => array(
+      'file' => 'includes/subscribe.inc',
       'arguments' => array(
-        'form' => NULL,
+        'element' => NULL,
       ),
     ),
     'project_issue_summary' => array(
@@ -910,6 +933,152 @@ function _project_issue_followup_get_user() {
 }
 
 /**
+ * Loads per-user account settings for project issue subscriptions.
+ *
+ * @param $account
+ *   A user account object to attach project issue subscription settings to.
+ *   Required properties:
+ *   - uid: The ID of the user account.
+ *   Attached properties:
+ *   - project_issue_subscriptions: An associative array containing:
+ *     - level: An integer denoting the user's global issue subscription level:
+ *       - PROJECT_ISSUE_SUBSCRIPTIONS_NONE
+ *       - PROJECT_ISSUE_SUBSCRIPTIONS_FLAGGED
+ *       - PROJECT_ISSUE_SUBSCRIPTIONS_ALL
+ */
+function project_issue_subscriptions_user_settings_load($account) {
+  // Setup defaults.
+  $defaults = array(
+    'level' => PROJECT_ISSUE_SUBSCRIPTIONS_NONE,
+  );
+
+  // For existing accounts, load account settings.
+  if (!empty($account->uid)) {
+    $settings = db_fetch_array(db_query("SELECT * FROM {project_issue_subscriptions_user} WHERE uid = %d", array($account->uid)));
+    $settings = ($settings ? $settings : array());
+    $account->project_issue_subscriptions = array_merge($defaults, $settings);
+  }
+  // Otherwise, attach default settings.
+  else {
+    $account->project_issue_subscriptions = $defaults;
+  }
+}
+
+/**
+ * Saves per-user account settings for project issue subscriptions.
+ *
+ * @param $account
+ *   A user account object containing at least the properties:
+ *   - uid: The ID of the user account.
+ *   - project_issue_subscriptions: An associative array containing:
+ *     - level: An integer denoting the user's global issue subscription level.
+ *       - PROJECT_ISSUE_SUBSCRIPTIONS_NONE
+ *       - PROJECT_ISSUE_SUBSCRIPTIONS_FLAGGED
+ *       - PROJECT_ISSUE_SUBSCRIPTIONS_ALL
+ *     When passing PROJECT_ISSUE_SUBSCRIPTIONS_NONE the user's per-user
+ *     subscription setting is deleted.
+ *
+ * @see project_issue_subscriptions_user_settings_load()
+ */
+function project_issue_subscriptions_user_settings_save($account) {
+  $level = $account->project_issue_subscriptions['level'];
+  if ($level > PROJECT_ISSUE_SUBSCRIPTIONS_NONE) {
+    db_query("UPDATE {project_issue_subscriptions_user} SET level = %d WHERE uid = %d", array(
+      $level,
+      $account->uid,
+    ));
+    if (!db_affected_rows()) {
+      db_query("INSERT INTO {project_issue_subscriptions_user} (uid, level) VALUES (%d, %d)", array(
+        $account->uid,
+        $level,
+      ));
+    }
+  }
+  else {
+    db_query("DELETE FROM {project_issue_subscriptions_user} WHERE uid = %d", array(
+      $account->uid,
+    ));
+  }
+}
+
+/**
+ * Loads the project subscription setting for a user account.
+ *
+ * @param $uid
+ *   The ID of a user account.
+ * @param $nid
+ *   The node ID of a project node.
+ *
+ * @return
+ *   An integer denoting the user's project issue subscription level:
+ *   - PROJECT_ISSUE_SUBSCRIPTIONS_NONE
+ *   - PROJECT_ISSUE_SUBSCRIPTIONS_FLAGGED
+ *   - PROJECT_ISSUE_SUBSCRIPTIONS_ALL
+ *
+ * @see project_issue_subscriptions_project_setting_save()
+ */
+function project_issue_subscriptions_project_setting_load($uid, $nid) {
+  $level = db_result(db_query("SELECT level FROM {project_issue_subscriptions_project} WHERE uid = %d AND nid = %d", array(
+    $uid,
+    $nid,
+  )));
+  return ($level !== FALSE ? $level : PROJECT_ISSUE_SUBSCRIPTIONS_NONE);
+}
+
+/**
+ * Saves project subscription setting for a user account.
+ *
+ * @param $uid
+ *   The ID of a user account.
+ * @param $nid
+ *   The node ID of a project node.
+ * @param $level
+ *   An integer denoting the user's project issue subscription level:
+ *   - PROJECT_ISSUE_SUBSCRIPTIONS_NONE
+ *   - PROJECT_ISSUE_SUBSCRIPTIONS_FLAGGED
+ *   - PROJECT_ISSUE_SUBSCRIPTIONS_ALL
+ *   When passing PROJECT_ISSUE_SUBSCRIPTIONS_NONE the user's project
+ *   subscriptions settings are deleted.
+ *
+ * @see project_issue_subscriptions_project_setting_load()
+ */
+function project_issue_subscriptions_project_setting_save($uid, $nid, $level) {
+  if ($level > PROJECT_ISSUE_SUBSCRIPTIONS_NONE) {
+    db_query("UPDATE {project_issue_subscriptions_project} SET level = %d WHERE uid = %d AND nid = %d", array(
+      $level,
+      $uid,
+      $nid,
+    ));
+    if (!db_affected_rows()) {
+      db_query("INSERT INTO {project_issue_subscriptions_project} (uid, nid, level) VALUES (%d, %d, %d)", array(
+        $uid,
+        $nid,
+        $level,
+      ));
+    }
+  }
+  else {
+    db_query("DELETE FROM {project_issue_subscriptions_project} WHERE uid = %d AND nid = %d", array(
+      $uid,
+      $nid,
+    ));
+  }
+}
+
+/**
+ * Implements hook_user().
+ */
+function project_issue_user($op, $edit, $account) {
+  // Delete project maintainer assignments and all e-mail notification settings
+  // in case a user account is deleted or disabled/banned.
+  if ($op == 'delete') {
+    db_query("DELETE FROM {project_issue_subscriptions_user} WHERE uid = %d", $account->uid);
+    db_query("DELETE FROM {project_issue_subscriptions_project} WHERE uid = %d", $account->uid);
+    db_query("DELETE FROM {project_issue_project_maintainer} WHERE uid = %d", $account->uid);
+  }
+}
+
+/**
  * hook_nodeapi() implementation. This just decides what type of node
  * is being passed, and calls the appropriate type-specific hook.
  *
@@ -1878,13 +2047,6 @@ function project_issue_query_result_links($project_arg = NULL) {
         'attributes' => array('title' => t('See statistics about issues.')),
       );
     }
-    if (!empty($user->uid) && variable_get('project_issue_global_subscribe_page', TRUE)) {
-      $links['subscribe'] = array(
-        'title' => t('Subscribe'),
-        'href' => "project/issues/subscribe-mail",
-        'attributes' => array('title' => t('Receive e-mail updates about issues.')),
-      );
-    }
   }
   else {
     // We know the project, make project-specific links.
