diff --git a/activity/project_activity_issue.info b/activity/project_activity_issue.info
new file mode 100644
index 0000000..96338fd
--- /dev/null
+++ b/activity/project_activity_issue.info
@@ -0,0 +1,8 @@
+name = Project Issue Activity
+description = Part of the Google Summer of Code Project 'Development Activity logging, Activity Streams and Development Statistics'.
+package = Project Activity
+dependencies[] = project_issue
+dependencies[] = project_activity_project
+dependencies[] = trigger
+dependencies[] = activity
+core = 6.x
diff --git a/activity/project_activity_issue.module b/activity/project_activity_issue.module
new file mode 100644
index 0000000..58e85a1
--- /dev/null
+++ b/activity/project_activity_issue.module
@@ -0,0 +1,393 @@
+<?php
+
+/**
+ * @file: Main module of Project Issue Activity: Issue.
+ */
+ 
+/**
+ * Returns a list of triggers for Project Issue.
+ */
+function project_activity_issue_hooks() {
+  return array(
+    'project_issue' =>
+      array(
+        'insert' => array(
+          'runs when' => t('When an new issue gets created'),
+        ),
+        'update_priority' => array(
+          'runs when' => t('When an existing issue\'s priority gets updated'),
+        ),
+        'update_assigned' => array(
+          'runs when' => t('When an existing issue gets assigned'),
+        ),
+        'delete' => array(
+          'runs when' => t('When an issue gets deleted'),
+        ),
+        'comment_insert' => array(
+          'runs when' => t('When a comment gets added to an existing issue'),
+        ),
+        'comment_insert_patch' => array(
+          'runs when' => t('When a comment with a patch attached gets added to an existing issue'),
+        ),
+      ),
+  );
+}
+
+/**
+ * Implementation of hook_hook_info().
+ *
+ * Provides Trigger support for Project Issue.
+ */
+function project_issue_hook_info() {
+  $hooks = project_activity_issue_hooks();
+  
+  return array('project_issue' => array('project_issue' => $hooks['project_issue']));
+}
+
+/**
+ * Implementation of hook_activity_info().
+ *
+ * Provides Activity support for Project Issue.
+ */
+function project_issue_activity_info() {
+  static $cache;
+  
+  if (!isset($cache)) {
+    $cache = project_activity_issue_hooks();
+  
+    foreach ($cache as $module => &$hooks) {
+      $hooks = array_keys($hooks);
+    }
+  }
+  
+  $info = new stdClass();
+  
+  $info->api = 2;
+  $info->name = 'project_issue';
+  $info->object_type = 'project_issue';
+  $info->eid_field = 'nid';
+  $info->objects = array('Actor' => 'project_issue'); // array keys are the labels
+  $info->hooks = array('project_issue' => $cache['project_issue']);
+  $info->type_options = project_issue_category(FALSE, TRUE);
+  $info->list_callback = 'project_issue_activity_list_activity_actions';
+  $info->context_load_callback = 'project_issue_activity_load_activity_context';
+  
+  return $info;
+}
+
+/**
+ * Implementation of hook_activity_type_check().
+ */
+function project_issue_activity_type_check($token_objects, $types) {
+  $issue = $token_objects['project_issue'];
+  $category = (!empty($issue->project_issue['category']) ? $issue->project_issue['category'] : $issue->category);
+  
+  return (in_array($category, $types));
+}
+
+/**
+ * Implementation of hook_activity_objects_alter().
+ */
+function project_activity_issue_activity_objects_alter(&$objects, $type) {
+  switch($type) {
+    case 'project_issue':
+      $issue = $objects['project_issue'];
+      
+      if (!empty($issue->cid)) {
+        $objects['comment'] = _comment_load($issue->cid);
+      }
+
+      $rid = (!empty($issue->project_issue['rid']) ? $issue->project_issue['rid'] : (!empty($issue->rid) ? $issue->rid : NULL));
+      if (!empty($rid)) {
+        $objects['project_release'] = node_load($rid);
+      }
+      
+      $pid = (!empty($issue->project_issue['pid']) ? $issue->project_issue['pid'] : $issue->pid);
+      $objects['node'] = node_load($pid);
+      
+      break;
+  }
+}
+
+/**
+ * Implementation of hook_activity_record_alter().
+ */
+function project_activity_issue_activity_record_alter(&$record, $context) {
+  if (!empty($context['project_issue'])) {
+    $record->nid = $context['project_issue']->nid;
+  }
+}
+
+/**
+ * Implementation of hook_activity_charts_query_alter().
+ */
+function project_issue_activity_charts_query_alter($query) {
+  $issue_table = $query->add_table('project_issues', 'node_activity');
+  
+  $join = new views_join;
+  $join->construct('node', $issue_table, 'pid', 'nid');
+  $issue_project_table = $query->add_relationship('project_issues_project', $join, $issue_table);
+  
+  $query->add_field($issue_table, 'pid', 'activity_id');
+  $query->add_field($issue_project_table, 'title', 'activity_id_name');
+  
+  return $query;
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ *
+ * Here we trigger the actions defined by Activity.
+ */
+function project_activity_issue_nodeapi(&$node, $op, $arg) {
+  global $user;
+  
+  // Check if it's the project_issue type and it's not because of a comment action.
+  if (($node->type == "project_issue")) {
+    switch ($op) {
+      case 'insert':
+      case 'delete':
+        $aids = _trigger_get_hook_aids('project_issue', $op);
+        $context = project_issue_activity_context($op, $node, $user->uid);
+
+        actions_do(array_keys($aids), $node, $context);
+
+        break;
+    }
+  }
+}
+
+/**
+ * Implementation of hook_comment().
+ *
+ * Here we trigger the actions defined by Activity.
+ */
+function project_activity_issue_comment(&$a1, $op) {
+  if (is_array($a1)) {
+    $comment = (object)$a1;
+  } 
+  else {
+    $comment = $a1;
+  }
+  
+  if (!empty($comment->project_info)) {
+    switch ($op) {
+      case 'insert':
+        $issue = node_load($comment->nid);
+        
+        if (($issue->type == 'project_issue')) {
+          $ops = array();
+          
+          $options = array();
+          
+          // Determine if a patch was attached.
+          if (!empty($comment->files)) {
+            foreach($comment->files as $file) {
+              if (!empty($file['filename']) && (project_issue_filename_is_patch($file['filename']))) {
+                array_push($ops, 'comment_insert_patch');
+              }
+            }
+          }
+          
+          $new = $comment->project_info;
+          $old = $issue->project_issue;
+          
+          // Determine if assignement of issue was updated.
+          if ($new['assigned'] != $old['assigned']) {
+            $options['assigned'] = $new['assigned'];
+            array_push($ops, 'update_assigned');
+          }
+          
+          // Determine if priority of issue was updated.
+          if ($comment->priority != $old['priority']) {
+            $options['priority'] = $comment->priority;
+            array_push($ops, 'update_priority');
+          }
+          
+          array_push($ops, 'comment_insert');
+          
+          foreach ($ops as $op) {
+            $aids = _trigger_get_hook_aids('project_issue', $op);
+            $context = project_issue_activity_context($op, $issue, $comment->uid, $comment->cid, $options);
+
+            actions_do(array_keys($aids), $node, $context);
+          }
+        }
+        
+        break;
+    }
+  }
+}
+
+/**
+ * Returns the context array for Project Issues activity.
+ */
+function project_issue_activity_context($op, $node, $uid = FALSE, $cid = FALSE, $options = array()) {
+  if (empty($node)) {
+    return array();
+  } 
+  else {
+    $context = array();
+    
+    if ($cid !== FALSE) {
+      $context['cid'] = $cid;
+      $node->cid = $cid;
+    }
+    
+    if ($uid !== FALSE) {
+      $context['actor'] = $uid;
+    }
+    
+    foreach ($options as $key => $value) {
+      $node->project_issue[$key] = $value;
+    }
+    
+    $context += array(
+      'hook' => 'project_issue',
+      'op' => $op,
+      'action' => $op,
+      'project_issue' => $node,
+      'pid' => (!empty($node->project_issue['pid']) ? $node->project_issue['pid'] : $node->pid),
+    );
+    
+    return $context;
+  }
+}
+
+/**
+ * Returns wheter or not a filename is one of a patch.
+ */
+function project_issue_filename_is_patch($filename) {
+  preg_match("/\.([^\.]+)$/", $filename, $filename);
+  
+  $allowed_types = array('patch', 'diff');
+  
+  return in_array($filename[1], $allowed_types);
+}
+
+/**
+ * Implementation of hook_activity_token_list().
+ */
+function project_issue_activity_token_list($type = 'all') {
+  if ($type == 'project_issue') {
+    $tokens = project_issue_token_list('node');
+    $tokens = $tokens['node'];
+    
+    $tokens['project_issue'] = t('Link to project issue');
+    $tokens['project_issue_assigned-link'] = t('Link to the user the issue is assigned to');
+    
+    return array(
+      'project issue' => $tokens,
+    );
+  }
+  
+  return array();
+}
+
+/**
+ * Implementation of hook_activity_token_values().
+ */
+function project_issue_activity_token_values($type, $object = NULL, $options = array()) {
+  if (($type == 'project_issue')) {
+    $values = project_issue_token_values('node', $object);
+    
+    $values['project_issue'] = l($object->title, "node/{$object->nid}");
+    
+    if (!empty($object->project_issue['assigned'])) {
+      $values['project_issue_assigned-link'] = theme('activity_username', user_load($object->project_issue['assigned']));
+    } 
+    else {
+      $values['project_issue_assigned-link'] = '';
+    }
+    
+    return $values;
+  }
+  
+  return array();
+}
+
+/**
+ * List all the Activity Actions that match the hook and op.
+ *
+ * @param string $hook
+ *  The hook that is to be fired.
+ * @param string $op
+ *  The op to be used in the hook.
+ * @param int $max_age
+ *  The max age from now.
+ *
+ * @return array
+ *  An array of arrays with 'id', 'created' and 'actor' keys.
+ */
+function project_issue_activity_list_activity_actions($hook, $op, $max_age, $from, $count) {
+  $actions = array();
+
+  if (!empty($max_age)) {
+    $min_time = time() - $max_age;
+  }
+  else {
+    $min_time = 0;
+  }
+
+  $sql_strings = array(
+    'insert' => "SELECT n.nid as nid, i.rid as rid, n.created as created, n.uid as actor FROM {node} n LEFT JOIN {project_issues} i ON n.nid = i.nid WHERE created > %d AND type = 'project_issue'",
+    'update' => "SELECT n.nid as nid, i.rid as rid, n.changed as created, n.uid as actor FROM {node} n LEFT JOIN {project_issues} i ON n.nid = i.nid WHERE created > %d AND type = 'project_issue' AND NOT (created = changed)",
+    'update_priority' => "SELECT c1.nid AS nid, c2.timestamp AS created, c.uid AS actor, c1.cid AS cid, c2.assigned AS assigned, c2.priority AS priority, c2.rid AS rid FROM {project_issue_comments} c1, {project_issue_comments} c2 LEFT JOIN {comments} c ON c2.cid = c.cid WHERE c2.timestamp > %d AND c1.nid = c2.nid AND c1.comment_number = (c2.comment_number - 1) AND NOT (c1.priority = c2.priority) AND c.uid IS NOT NULL",
+    'update_assigned' => "SELECT c1.nid AS nid, c2.timestamp AS created, c.uid AS actor, c1.cid AS cid, c2.assigned AS assigned, c2.priority AS priority, c2.rid AS rid FROM {project_issue_comments} c1, {project_issue_comments} c2 LEFT JOIN {comments} c ON c2.cid = c.cid WHERE c2.timestamp > %d AND c1.nid = c2.nid AND c1.comment_number = (c2.comment_number - 1) AND NOT (c1.assigned = c2.assigned) AND c.uid IS NOT NULL",
+    'comment_insert' => "SELECT c1.timestamp AS created, c.uid AS actor, c1.nid AS nid, c1.cid AS cid, c1.rid AS rid FROM {project_issue_comments} c1 LEFT JOIN {comments} c ON c1.cid = c.cid WHERE c1.timestamp > %d AND c.uid IS NOT NULL",
+    'comment_insert_patch' => "SELECT c1.timestamp AS created, c.uid AS actor, cu.description AS filename, c1.nid AS nid, c1.cid AS cid, c1.rid AS rid FROM {project_issue_comments} c1, {comment_upload} cu LEFT JOIN {comments} c ON cu.cid = c.cid WHERE c1.timestamp > %d AND cu.cid = c1.cid AND c.uid IS NOT NULL",
+  );
+
+  if (!empty($sql_strings[$op])) {
+    $result = db_query_range($sql_strings[$op], $min_time, $from, $count);
+    while ($row = db_fetch_array($result)) {
+      $row['id'] = array('nid' => $row['nid'], 'rid' => FALSE, 'cid' => FALSE, 'options' => array());
+      
+      if (!empty($row['cid'])) {
+        $row['id']['cid'] = $row['cid'];
+      }
+      if (!empty($row['rid'])) {
+        $row['id']['options']['rid'] = $row['rid'];
+      }
+      switch ($op) {
+        case 'comment_insert_patch':
+          if (!project_issue_filename_is_patch($row['filename'])) {
+            continue;
+          }
+          
+          break;
+          
+        case 'update_priority':
+        case 'update_assigned':
+          $row['id']['options']['assigned'] = $row['assigned'];
+          $row['id']['options']['priority'] = $row['priority'];
+        
+          break;
+      }
+      
+      $actions[] = $row;
+    }
+  }
+
+  return $actions;
+}
+
+/**
+ * Load up the context array to pass to activity_record.
+ *
+ * @param string $hook
+ *  The hook that is being fired.
+ * @param string $op
+ *  The op for that hook.
+ * @param string $id
+ *  The id for the action.
+ *
+ * @return array
+ *   The context array for activity_record.
+ * @see trigger_nodeapi
+ */
+function project_issue_activity_load_activity_context($hook, $op, $id) {
+  if ($node = node_load($id['nid'])) {
+    return project_issue_activity_context($op, $node, FALSE, $id['cid'], $id['options']);
+  }
+}
\ No newline at end of file
diff --git a/activity/project_activity_views.info b/activity/project_activity_views.info
new file mode 100644
index 0000000..e30afdd
--- /dev/null
+++ b/activity/project_activity_views.info
@@ -0,0 +1,8 @@
+name = Project Activity Views
+description = Part of the Google Summer of Code Project 'Development Activity logging, Activity Streams and Development Statistics'.
+package = Project Activity
+dependencies[] = views
+dependencies[] = project_activity_project
+dependencies[] = project_activity_release
+dependencies[] = project_activity_issue
+core = 6.x
diff --git a/activity/project_activity_views.module b/activity/project_activity_views.module
new file mode 100644
index 0000000..00aa46e
--- /dev/null
+++ b/activity/project_activity_views.module
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file: Main module of Project Activity.
+ */
+
+/**
+ * Implementation of hook_views_api().
+ */
+function project_activity_views_views_api() {
+  return array(
+    'api' => 2.0,
+    'path' => drupal_get_path('module', 'project_activity_views') . '/views',
+  );
+}
\ No newline at end of file
diff --git a/activity/tests/project_activity_issue.test b/activity/tests/project_activity_issue.test
new file mode 100644
index 0000000..11253a8
--- /dev/null
+++ b/activity/tests/project_activity_issue.test
@@ -0,0 +1,384 @@
+<?php
+
+/**
+ * @file: Provide tests for Project Issue Activity module.
+ */
+ 
+include_once drupal_get_path('module', 'project_activity_issue') . '/tests/project_activity_issue_testcase.test';
+
+class ProjectIssueActivityWebTestCase extends BaseProjectActivityIssueWebTestCase {
+
+  /**
+   * Returns information about these tests.
+   *
+   * @return mixed
+   */
+  function getInfo() {
+    return array(
+      'name' => t('Project Issue activity'),
+      'description' => t('Test the basic functionality of Project Issue Activity module.'),
+      'group' => t('Project Activity'),
+    );
+  }
+
+  /**
+   * Sets up each test.
+   */
+  function setUp() {
+    parent::setUp('project_activity_issue', 'project_activity_release', 'project_activity_project', 'project_issue', 'comment_upload', 'project_release', 'project', 'comment', 'views', 'activity', 'token', 'trigger', 'upload');
+    
+    // Create the user that's going to create the project
+    $this->projectUser = $this->drupalCreateUser(array('access content', 'create full projects', 'administer nodes', 'administer projects'));
+    
+    // Create another user that's going to edit stuff.
+    $this->issueUser = $this->drupalCreateUser(array('access content', 'create full projects', 'administer nodes', 'administer projects'));
+    
+    // Login the user
+    $this->drupalLogin($this->projectUser);
+    
+    // Create a new project
+    $this->project_title = $this->randomName(8);
+    $this->project = $this->createProject($this->project_title, $this->randomName(16));
+  }
+  
+  /**
+   * Test if the creation of a project issue is properly logged.
+   *
+   * We assume that this extensive tests of all posisble combinations of Issue types is enough
+   * to guarantee the correct working of the filtering.
+   */
+  function testProjectIssueCreate() {
+    $categories = array('bug', 'task', 'feature', 'support');
+    
+    // First we create all possible scenario's (powerset of $categories).
+    $testcases = array(array());
+    foreach ($categories as $category) {
+      foreach ($testcases as $combination) {
+        array_push($testcases, array_merge(array($category), $combination));
+      }
+    }
+
+    // Now we try every possible scenario.
+    foreach ($testcases as $testcase) {
+      // First we create an activity template.
+      $hash = $this->createActivityTemplate('project_issue', 'insert', $testcase);
+      
+      // Determine which categories shouldn't be logged.
+      $anti_testcase = array_diff($categories, $testcase);
+      
+      // Calling createActivityTemplate always logs in a user with only Activity creation permissions.
+      // We log in our regular user.
+      $this->drupalLogin($this->projectUser);
+      $user = $this->getUrl();
+      
+      // Variable to count amount of activity.
+      $activity_count = 0;
+      
+      // And now we perform actions.
+      foreach ($testcase as $category) {
+        // Produce some activity.
+        $this->createProjectIssue($this->project, $this->randomName(8), $this->randomName(16), $category);
+        $activity_count ++;
+        
+        // Also create issues that shouldn't be logged.
+        foreach ($anti_testcase as $anti_category) {
+          $this->createProjectIssue($this->project, $this->randomName(8), $this->randomName(16), $anti_category);
+        }
+        
+        // Assert activity has happened.
+        $this->assertActivity($user, $hash, $this->project, 0, $activity_count);
+      }
+      
+      $this->clearAllActivityTemplates(1, TRUE);
+    }
+    
+  }
+  
+  /**
+   * Test wheter updating an issue is properly logged when nothing is changed in the assignment or
+   * priority.
+   */
+  function testProjectIssueUpdate() {
+    $hash_assigned = $this->createActivityTemplate('project_issue', 'update_assigned');
+    $hash_priority = $this->createActivityTemplate('project_issue', 'update_priority');
+    
+    // Calling createActivityTemplate always logs in a user with only Activity creation permissions.
+    // We log in our project user.
+    $this->drupalLogin($this->projectUser);
+    
+    // Create an issue.
+    $title = $this->randomName(8);
+    $issue = $this->createProjectIssue($this->project, $title, $this->randomName(16));
+    
+    // We also want to check if another user updates the issue, he or she gets credited for the
+    // activity, and not the original owner.
+    $this->drupalLogin($this->issueUser);
+    $user = $this->getUrl();
+    
+    // Update an issue.
+    $this->updateProjectIssue($issue, $title, $this->randomName(16));
+    
+    // Assert activity has happened.
+    $this->assertActivity(NULL, $hash_assigned, $this->project, NULL, NULL);
+  }
+  
+  /**
+   * Test wheter deleting an issue is propery logged.
+   */
+  function testProjectIssueDelete() {
+    $hash = $this->createActivityTemplate('project_issue', 'delete');
+    
+    // Calling createActivityTemplate always logs in a user with only Activity creation permissions.
+    // We log in our project user.
+    $this->drupalLogin($this->projectUser);
+    
+    // Create an issue.
+    $title = $this->randomName(8);
+    $issue = $this->createProjectIssue($this->project, $title, $this->randomName(16));
+    
+    // We also want to check if another user updates the issue, he or she gets credited for the
+    // activity, and not the original owner.
+    $this->drupalLogin($this->issueUser);
+    $user = $this->getUrl();
+    
+    // Delete an issue.
+    $this->deleteProjectIssue($issue, $title);
+    
+    // Assert activity has happened.
+    $this->assertActivity($user, $hash, $this->project, 0, 1);
+  }
+  
+  /**
+   * Test the Project Issue token integration (standard).
+   */
+  public function testProjectIssueTokens() {
+    $message = '[project_issue_pid] [project_issue_project_title] [project_issue_project_title-raw] [project_issue_project_shortname] [project_issue_category] [project_issue_component] [project_issue_priority] [project_issue_version] [project_issue_assigned] [project_issue_status]';
+    
+    $this->_createActivityTemplate('project_issue', 'insert', $message);
+    
+    // Login the user that's going to create a new release.
+    $this->drupalLogin($this->projectUser);
+    $user = $this->getUrl();
+    
+    // Create an issue.
+    $title = $this->randomName(8);
+    $issue = $this->createProjectIssue($this->project, $title, $this->randomName(16));
+    
+    // Load the release as a node and do token replacement.
+    $node = node_load($this->getIdentifier($issue), NULL, TRUE);
+    $render = token_replace_multiple($message, array('node' => $node), '[', ']', array(), TRUE);
+    
+    // Assert activity has happened.
+    $this->_assertActivity($render, 0, 1);
+  }
+  
+  /**
+   * Test the Comment token integration.
+   */
+  public function testProjectIssueCommentTokens() {
+    $message = '[comment-body-raw]';
+    
+    $this->_createActivityTemplate('project_issue', 'comment_insert', $message);
+    
+    // Login the user that's going to create a new release.
+    $this->drupalLogin($this->projectUser);
+    $user = $this->getUrl();
+    
+    // Create an issue.
+    $title = $this->randomName(8);
+    $issue = $this->createProjectIssue($this->project, $title, $this->randomName(16));
+      
+    // Comment on the issue.
+    $comment_body = $this->randomName(16);
+    $this->createIssueComment($issue, $comment_body);
+    
+    // Assert activity has happened.
+    $this->_assertActivity($comment_body, 0, 1);
+  }
+  
+  /**
+   * Test the Project Release token integration.
+   */
+  public function testProjectIssueReleaseTokens() {
+    $message = '[project_release_project_title-raw]';
+    
+    $this->_createActivityTemplate('project_issue', 'insert', $message);
+    
+    // Login the user that's going to create a new release.
+    $this->drupalLogin($this->projectUser);
+    $user = $this->getUrl();
+    
+    // Create a release.
+    $release = $this->createProjectRelease($this->project, $this->project_title, $this->randomName(16), 1, 0, 0);
+    
+    // Create an issue.
+    $title = $this->randomName(8);
+    $issue = $this->createProjectIssue($this->project, $title, $this->randomName(16), "task", "Code", $this->getIdentifier($release));
+    
+    // Assert activity has happened.
+    $this->_assertActivity($this->project_title, 0, 1);
+  }
+  
+  /**
+   * Test the Project Release token integration (custom).
+   */
+  function testCustomTokens() {
+    $message = '[project_issue] [project_issue_assigned-link]';
+    
+    $this->_createActivityTemplate('project_issue', 'insert', $message);
+    
+    // Login the user that's going to create a new release.
+    $this->drupalLogin($this->projectUser);
+    $user = $this->getUrl();
+    
+    // Create an issue.
+    $title = $this->randomName(8);
+    $issue = $this->createProjectIssue($this->project, $title, $this->randomName(16));
+    
+    // Load the release as a node and do token replacement.
+    $node = node_load($this->getIdentifier($issue), NULL, TRUE);
+    $render = token_replace_multiple($message, array('project_issue' => $node), '[', ']', array(), TRUE);
+    
+    // Assert activity has happened.
+    $this->_assertActivity($render, 0, 1);
+  }
+  
+  /**
+   * Test if the commeting on an issue doesn't trigger any other actions.
+   */
+  function testProjectIssueNoActivityComment() {
+    // Create an issue.
+    $issue = $this->createProjectIssue($this->project, $this->randomName(8), $this->randomName(16));
+    
+    // Add Activity templates.
+    $hash_insert = $this->createActivityTemplate('project_issue', 'insert');
+    $hash_update = $this->createActivityTemplate('project_issue', 'update_assigned');
+    $hash_update = $this->createActivityTemplate('project_issue', 'update_priority');
+    $hash_delete = $this->createActivityTemplate('project_issue', 'delete');
+    
+    // Calling createActivityTemplate always logs in a user with only Activity creation permissions.
+    // We log in our regular user.
+    $this->drupalLogin($this->projectUser);
+    
+    // Comment on the issue.
+    $this->createIssueComment($issue, $this->randomName(16));
+    
+    // Assert activity has NOT happened.
+    $this->assertActivity(NULL, NULL, $issue, 0, 0);
+  }
+  
+  /**
+   * Test if assigning the issue to someone gets properly logged.
+   */
+  function testProjectIssueUpdateAssigned() {
+    // Create an issue.
+    $issue = $this->createProjectIssue($this->project, $this->randomName(8), $this->randomName(16));
+    
+    // Add Activity templates.
+    $hash = $this->createActivityTemplate('project_issue', 'update_assigned');
+    
+    // Calling createActivityTemplate always logs in a user with only Activity creation permissions.
+    // We log in our regular user.
+    $this->drupalLogin($this->projectUser);
+    $user = $this->getUrl();
+    
+    // Comment on the issue, assigning it to the current user.
+    $this->createIssueComment($issue, $this->randomName(16), $this->projectUser->uid);
+    
+    // Assert activity has happened.
+    $this->assertActivity($user, $hash, $this->project, 0, 1);
+  }
+  
+  /**
+   * Test if changing the issue's priority gets properly logged.
+   */
+  function testProjectIssueUpdatePriority() {
+    // Create an issue.
+    $issue = $this->createProjectIssue($this->project, $this->randomName(8), $this->randomName(16));
+    
+    // Add Activity templates.
+    $hash = $this->createActivityTemplate('project_issue', 'update_priority');
+    
+    // Calling createActivityTemplate always logs in a user with only Activity creation permissions.
+    // We log in our regular user.
+    $this->drupalLogin($this->projectUser);
+    $user = $this->getUrl();
+    
+    // Comment on the issue, changing the priority to critical.
+    $this->createIssueComment($issue, $this->randomName(16), NULL, 1);
+    
+    // Assert activity has happened.
+    $this->assertActivity($user, $hash, $this->project, 0, 1);
+  }
+  
+  /**
+   * Test if changing the issue's priority and assigning a project in one go gets properly logged.
+   */
+  function testProjectIssueUpdateAssignedPriority() {
+    // Create an issue.
+    $issue = $this->createProjectIssue($this->project, $this->randomName(8), $this->randomName(16));
+    
+    // Add Activity templates.
+    $hash_assigned = $this->createActivityTemplate('project_issue', 'update_assigned');
+    $hash_priority = $this->createActivityTemplate('project_issue', 'update_priority');
+    
+    // Calling createActivityTemplate always logs in a user with only Activity creation permissions.
+    // We log in our regular user.
+    $this->drupalLogin($this->projectUser);
+    $user = $this->getUrl();
+    
+    // Comment on the issue, changing the priority to critical.
+    $this->createIssueComment($issue, $this->randomName(16), $this->projectUser->uid, 1);
+    
+    // Assert activity has happened.
+    $this->assertActivity($user, $hash_priority, $this->project, 0, 2);
+    $this->assertActivity($user, $hash_assigned, $this->project, 1, 2);
+  }
+  
+  /**
+   * Tests if uploading a patch is correctly logged.
+   */
+  function testProjectIssueUploadPatch() {
+    // Create an issue.
+    $issue = $this->createProjectIssue($this->project, $this->randomName(8), $this->randomName(16));
+    
+    // Add Activity templates.
+    $hash = $this->createActivityTemplate('project_issue', 'comment_insert_patch');
+    
+    // Calling createActivityTemplate always logs in a user with only Activity creation permissions.
+    // We log in our regular user.
+    $this->drupalLogin($this->projectUser);
+    $user = $this->getUrl();
+    
+    // Simulate patch uploading.
+    $this->createIssuePatch($this->getIdentifier($issue), $this->getIdentifier($this->project), $this->randomName(8).'.patch');
+    $this->createIssuePatch($this->getIdentifier($issue), $this->getIdentifier($this->project), $this->randomName(8).'.diff');
+    
+    // Assert activity has happened.
+    $this->assertActivity($user, $hash, $this->project, 0, 2);
+    $this->assertActivity($user, $hash, $this->project, 1, 2);
+  }
+  
+  /**
+   * Tests if new comments are properly logged.
+   */
+  function testProjectIssueComment() {
+    // Create an issue.
+    $issue = $this->createProjectIssue($this->project, $this->randomName(8), $this->randomName(16));
+    
+    // Add Activity templates.
+    $hash = $this->createActivityTemplate('project_issue', 'comment_insert');
+    
+    // Calling createActivityTemplate always logs in a user with only Activity creation permissions.
+    // We log in our regular user.
+    $this->drupalLogin($this->projectUser);
+    $user = $this->getUrl();
+    
+    // Comment on the issue, upload patch
+    $this->createIssueComment($issue, $this->randomName(16), NULL, NULL);
+    
+    // Assert activity has happened.
+    $this->assertActivity($user, $hash, $this->project, 0, 1);
+  }
+  
+}
diff --git a/activity/tests/project_activity_issue_testcase.test b/activity/tests/project_activity_issue_testcase.test
new file mode 100644
index 0000000..29f8ca6
--- /dev/null
+++ b/activity/tests/project_activity_issue_testcase.test
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * @file: Provide tests for Project Issue Activity module.
+ */
+ 
+include_once drupal_get_path('module', 'project_activity_project') . '/tests/project_activity_testcase.test';
+
+class BaseProjectActivityIssueWebTestCase extends BaseProjectActivityWebTestCase {
+  
+  /**
+   * Creates an issue manually.
+   *
+   * @param  $project  string  The URL to the project.
+   * @param  $title  string  The title of the issue.
+   * @param  $body  string  The body of the issue.
+   * @param  $category  string  The category of the issue.
+   * @param  $component  string  The component the issue applies to.
+   *
+   * @return  string  Absolute URL to the created project.
+   */
+  protected function createProjectIssue($project, $title, $body, $category = "task", $component = "Code", $release_nid = FALSE) {
+    $edit = array();
+    $edit['title'] = $title;
+    $edit['component'] = $component;
+    $edit['category'] = $category;
+    $edit['body'] = $body;
+    
+    if ($release_nid !== FALSE) {
+      $edit['rid'] = $release_nid;
+    }
+    
+    $this->drupalPost('node/add/project-issue/'. $this->getIdentifier($project), $edit, t('Save'));
+    
+    $this->assertText(t('Issue @title has been created.', array('@title' => $title)));
+    
+    return $this->getUrl();
+  }
+  
+  /**
+   * Updates an issue manually.
+   *
+   * @param  $page  string  URL to the page node.
+   * @param  $title  string  The title of the page.
+   * @param  $body  string  The new body of the page.
+   */
+  protected function updateProjectIssue($issue, $title, $body) {
+    $edit = array();
+    $edit['body'] = $body;
+    
+    $this->drupalPost($issue .'/edit', $edit, t('Save'));
+    
+    $this->assertText(t('Issue @title has been updated.', array('@title' => $title)));
+  }
+  
+  /**
+   * Deletes an issue manually.
+   *
+   * @param  $issue  string  URL to the issue.
+   * @param  $title  string  The title of the issue.
+   */
+  protected function deleteProjectIssue($issue, $title) {
+    $this->drupalPost($issue .'/delete', array(), t('Delete'));
+    
+    $this->assertText(t('Issue @title has been deleted.', array('@title' => $title)));
+  }
+  
+  protected function createIssueComment($issue, $body, $assigned = NULL, $priority = NULL, $upload = NULL) {
+    $edit = array();
+    $edit['comment'] = $body;
+    
+    if (!is_null($assigned)) {
+      $edit['project_info[assigned]'] = $assigned;
+    }
+    
+    if (!is_null($priority)) {
+      $edit['priority'] = $priority;
+    }
+    
+    if (!is_null($upload)) {
+      $edit['files[upload]'] = realpath($upload);
+    }
+    
+    $this->drupalPost('comment/reply/'. $this->getIdentifier($issue), $edit, t('Preview'));
+    
+    // Due to an apparant bug in Project Issue, the following line is dissabled.
+    // Will see if patchable.
+    // $this->drupalPost(NULL, array(), t('Save'));
+    
+    // $this->assertUrl($issue);
+  }
+  
+  protected function createIssuePatch($issue_nid, $pid, $filename) {
+    $comment = array(
+                  'author' => $this->randomName(8),
+                  'subject' => $this->randomName(8),
+                  'comment' => $this->randomName(8),
+                  'format' => 1,
+                  'cid' => 0,
+                  'pid' => $pid,
+                  'nid' => $issue_nid,
+                  'uid' => $this->loggedInUser->uid,
+                  'status' => 0,
+                  'timestamp' => time(),
+                  'op' => 'insert',
+                  'project_info' => array(
+                                      'pid' => $pid,
+                                      'component' => 'Code',
+                                      'assigned' => 0,
+                                    ),
+                  'category' => 'task',
+                  'priority' => 2,
+                  'sid' => 1,
+                  'files' => array(
+                              array(
+                                'description' => '',
+                                'remove' => 0,
+                                'list' => 1,
+                                'weight' => 0,
+                                'filename' => $filename,
+                                'filepath' => $filename,
+                                'filemime' => 'text/txt',
+                                'filesize' => 0,
+                                'fid' => 0,
+                                'new' => 1,
+                              ),
+                            ),
+                  'date' => 'now',
+                  'name' => $this->randomName(8),
+              );
+              
+    project_activity_issue_comment($comment, 'insert');
+  }
+  
+}
diff --git a/activity/tests/project_activity_views.test b/activity/tests/project_activity_views.test
new file mode 100644
index 0000000..fd27c5f
--- /dev/null
+++ b/activity/tests/project_activity_views.test
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * @file: Provide tests for Project Activity Views integration.
+ */
+ 
+include_once drupal_get_path('module', 'project_activity_issue') . '/tests/project_activity_issue_testcase.test';
+
+class ProjectActivityViewsWebTestCase extends BaseProjectActivityIssueWebTestCase {
+
+  /**
+   * Returns information about these tests.
+   *
+   * @return mixed
+   */
+  function getInfo() {
+    return array(
+      'name' => t('Project Activity views integration'),
+      'description' => t('Test the basic functionality of Project Activity views integration.'),
+      'group' => t('Project Activity'),
+    );
+  }
+
+  /**
+   * Sets up each test.
+   */
+  function setUp() {
+    parent::setUp('project_activity_views', 'project_activity_issue', 'project_activity_release', 'project_activity_project', 'project_issue', 'comment_upload', 'project_release', 'project', 'comment', 'views', 'activity', 'token', 'trigger', 'upload');
+    
+    // Create the user that's going to create the project
+    $this->projectUser = $this->drupalCreateUser(array('access content', 'create full projects', 'create sandbox projects', 'administer projects', 'create page content', 'edit any page content', 'administer nodes'));
+    
+    // Login the user
+    $this->drupalLogin($this->projectUser);
+  }
+  
+  /**
+   * Asserts that a given activity message has been logged.
+   *
+   * @param  $user  string  Absolute URL to user profile.
+   * @param  $hash  string  Hash received from the createActivityTemplate function.
+   * @param  $node  string  Absolute URL to node.
+   * @param  $index  integer  The location of the activity in the activity log; lower is sooner.
+   * @param  $amount  integer  The total amount of activity messages expected.
+   * @param  $display  string  The display that needs to be shown.
+   *
+   * ... any additional parameters will be passed as arguments to the view.
+   */
+  protected function assertProjectActivity($user, $hash, $node, $index = 0, $amount = 0, $display_id = NULL) {
+    $args = array_slice(func_get_args(), 6);
+    array_unshift($args, 'project_activity', $display_id);
+    
+    $activity = call_user_func_array('views_get_view_result', $args);
+    
+    if (!empty($activity)) {
+      $this->assertEqual($activity[$index]->activity_messages_message, $user . $hash . $node);
+    } 
+    else {
+      $this->assertEqual($user, NULL);
+    }
+    
+    if ($amount) {
+        $this->assertEqual(count($activity), $amount);
+    }
+  }
+  
+  /**
+   * Test if the Project-specific Activity feed works and only displays activity belonging to that project.
+   */
+  function testProjectActivityFeed() {
+    $hash_insert = $this->createActivityTemplate('project', 'insert');
+    $hash_update = $this->createActivityTemplate('project', 'update');
+    $hash_maintainer = $this->createActivityTemplate('project', 'maintainer_new');
+    $hash_promote = $this->createActivityTemplate('project', 'promote_sandbox');
+    $hash_release_insert = $this->createActivityTemplate('project_release', 'insert');
+    $hash_issue_insert = $this->createActivityTemplate('project_issue', 'insert');
+    
+    // Create a possible maintainer.
+    $maintainer = $this->drupalCreateUser();
+    $user_maintainer = url('user/'. $maintainer->uid, array('absolute' => TRUE));
+    
+    // Login the user that's going to create some projects and do some actions against them.
+    $this->drupalLogin($this->projectUser);
+    $user = $this->getUrl();
+    
+    // Create a first sandbox project
+    $first_title = $this->randomName(8);
+    $first = $this->createProject($first_title, $this->randomName(16), TRUE);
+    
+    // Create a second project
+    $second_title = $this->randomName(8);
+    $second = $this->createProject($second_title, $this->randomName(16));
+    
+    // We promote the sandbox (first project).
+    $this->promoteSandbox($first, $first_title);
+    
+    // We update the second project.
+    $this->updateProject($second, $second_title, $this->randomName(16));
+    
+    // We add a maintainer to the first project.
+    $this->addMaintainer($first, $maintainer->name);
+    
+    // We update the first project.
+    $this->updateProject($first, $first_title, $this->randomName(16));
+    
+    // We add a release and an issue to the project.
+    $this->createProjectIssue($first, $this->randomName(8), $this->randomName(16));
+    $this->createProjectRelease($first, $first_title, $this->randomName(16));
+    
+    // Assert activity has happened for the first project.
+    // We expect maintainer_new, insert, update, promote, maintainer_new and update.
+    $this->assertProjectActivity($user, $hash_maintainer, $first, 7, 8, 'project', 1);
+    $this->assertProjectActivity($user, $hash_insert, $first, 6, 8, 'project', 1);
+    $this->assertProjectActivity($user, $hash_update, $first, 5, 8, 'project', 1);
+    $this->assertProjectActivity($user, $hash_promote, $first, 4, 8, 'project', 1);
+    $this->assertProjectActivity($user, $hash_maintainer, $first, 3, 8, 'project', 1);
+    $this->assertProjectActivity($user, $hash_update, $first, 2, 8, 'project', 1);
+    $this->assertProjectActivity($user, $hash_issue_insert, $first, 1, 8, 'project', 1);
+    $this->assertProjectActivity($user, $hash_release_insert, $first, 0, 8, 'project', 1);
+    
+    // Assert activity has happened for the second project.
+    // We expect maintainer_new, insert and update.
+    $this->assertProjectActivity($user, $hash_update, $second, 0, 3, 'project', 2);
+    $this->assertProjectActivity($user, $hash_insert, $second, 1, 3, 'project', 2);
+    $this->assertProjectActivity($user, $hash_maintainer, $second, 2, 3, 'project', 2);
+  }
+  
+  /**
+   * Following test is there to make sure that activity tracked for other node types does not show
+   * up in the Activity Feed for projects.
+   */
+  function testProjectActivityFeedOtherNodes() {
+    $hash_insert = $this->createActivityTemplate('nodeapi', 'insert', array(), 'node');
+    $hash_update = $this->createActivityTemplate('nodeapi', 'update', array(), 'node');
+    
+    // Login the user that's going to create a new page.
+    $this->drupalLogin($this->projectUser);
+    $user = $this->getUrl();
+    
+    // Create a new page node.
+    $title = $this->randomName(8);
+    $page = $this->createPage($title, $this->randomName(16));
+    
+    // Assert that the Project-specific Activity Feed does not list activity.
+    // Assume that the nid of the page is 1.
+    $this->assertProjectActivity(NULL, $hash_insert, $page, 0, 0, 'project', 1);
+    
+    // Update the page node.
+    $this->updatePage($page, $title, $this->randomName(16));
+    
+    // Assert global activity has happened.
+    $this->assertActivity($user, $hash_update, $page, 0, 2);
+    $this->assertActivity($user, $hash_insert, $page, 1, 2);
+    
+    // Assert that the Project-specific Activity Feed does not list activity.
+    // Assume that the nid of the page is 1.
+    $this->assertProjectActivity(NULL, $hash_insert, $page, 0, 0, 'project', 1);
+  }
+  
+}
\ No newline at end of file
diff --git a/activity/views/project_activity_views.views.inc b/activity/views/project_activity_views.views.inc
new file mode 100644
index 0000000..3a73a72
--- /dev/null
+++ b/activity/views/project_activity_views.views.inc
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * Implementation of hook_views_query_alter().
+ */
+function project_activity_views_views_query_alter(&$view, &$query) {
+  if ($view->name == 'project_activity') {
+    $group = $query->set_where_group('OR');
+    
+    // Project
+    $query->add_where($group, 'node_activity.type = "project_project"');
+    
+    // Project Release
+    $query->add_where($group, 'node_activity.type = "project_release"');
+    
+    // Project Issue
+    $query->add_where($group, 'node_activity.type = "project_issue"');
+  }
+}
+
+/**
+ * Implementation of hook_views_handlers().
+ *
+ * This is not required, but it's 
+ */
+function project_activity_views_views_handlers() {
+  return array(
+    'info' => array(
+      'path' => drupal_get_path('module', 'project_activity') . '/views',
+    ),
+    'handlers' => array(
+      'project_activity_views_handler_argument_pid' => array(
+        'parent' => 'views_handler_argument_numeric',
+      ),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_views_pre_build().
+ */
+function project_activity_views_views_pre_build($view) {
+  $handlers = $view->display_handler->get_handlers('argument');
+  
+  if (($view->name == 'project_activity') && (!empty($handlers['nid']))) {
+    $handler =& $handlers['nid'];
+
+    $definition = array(
+      'handler' => 'project_activity_views_handler_argument_pid',
+      'path' => drupal_get_path('module', 'project_activity_views') . '/views',
+      'file' => 'project_activity_views_handler_argument_pid.inc',
+    );
+    views_include_handler($definition, 'handler');
+    
+    $options = $handler->options;
+    
+    $handler = new project_activity_views_handler_argument_pid();
+    $handler->set_definition($definition);
+    
+    $handler->construct();
+    $handler->init($view, $options);
+    
+    $view->display_handler->handlers['argument'] = $handlers;
+  }
+}
\ No newline at end of file
diff --git a/activity/views/project_activity_views.views_default.inc b/activity/views/project_activity_views.views_default.inc
new file mode 100644
index 0000000..ca1551a
--- /dev/null
+++ b/activity/views/project_activity_views.views_default.inc
@@ -0,0 +1,192 @@
+<?php
+
+/**
+ * Implementation of hook_views_default_views().
+ *
+ * Construct a basic default activity view that can be used as a basis for other
+ * views.
+ */
+function project_activity_views_views_default_views() {
+  $view = new view;
+  $view->name = 'project_activity';
+  $view->description = 'All project-related activity on a site.';
+  $view->tag = 'activity';
+  $view->view_php = '';
+  $view->base_table = 'activity';
+  $view->is_cacheable = FALSE;
+  $view->api_version = 2;
+  $view->disabled = TRUE; /* Edit this to true to make a default view disabled initially */
+  $handler = $view->new_display('default', 'Defaults', 'default');
+  $handler->override_option('relationships', array(
+      'nid' => array(
+        'label' => 'Node',
+        'required' => 0,
+        'id' => 'nid',
+        'table' => 'activity',
+        'field' => 'nid',
+        'relationship' => 'none',
+      ),
+      'uid' => array(
+        'label' => 'User',
+        'required' => 1,
+        'id' => 'uid',
+        'table' => 'activity',
+        'field' => 'uid',
+        'relationship' => 'none',
+      ),
+    ));
+  $handler->override_option('fields', array(
+      'message' => array(
+        'label' => '',
+        'alter' => array(
+          'alter_text' => 0,
+          'text' => '',
+          'make_link' => 0,
+          'path' => '',
+          'alt' => '',
+          'prefix' => '',
+          'suffix' => '',
+          'help' => '',
+          'trim' => 0,
+          'max_length' => '',
+          'word_boundary' => 1,
+          'ellipsis' => 1,
+          'html' => 0,
+        ),
+        'exclude' => 0,
+        'id' => 'message',
+        'table' => 'activity_messages',
+        'field' => 'message',
+        'relationship' => 'none',
+      ),
+    ));
+  $handler->override_option('sorts', array(
+    'created' => array(
+      'order' => 'DESC',
+      'id' => 'created',
+      'table' => 'activity',
+      'field' => 'created',
+      'relationship' => 'none',
+    ),
+    'aid' => array(
+      'order' => 'DESC',
+      'id' => 'aid',
+      'table' => 'activity',
+      'field' => 'aid',
+      'relationship' => 'none',
+    ),
+  ));
+  $handler->override_option('filters', array(
+      'status' => array(
+        'operator' => '=',
+        'value' => '1',
+        'group' => '0',
+        'exposed' => FALSE,
+        'expose' => array(
+          'operator' => FALSE,
+          'label' => '',
+        ),
+        'id' => 'status',
+        'table' => 'activity',
+        'field' => 'status',
+        'relationship' => 'none',
+      ),
+    ));
+  $handler->override_option('access', array(
+      'type' => 'none',
+    ));
+  $handler->override_option('cache', array(
+      'type' => 'none',
+    ));
+  $handler->override_option('use_ajax', TRUE);
+  $handler->override_option('items_per_page', 25);
+  $handler->override_option('use_pager', '1');
+  $handler = $view->new_display('page', 'Activity', 'all');
+  $handler->override_option('path', 'activity/project');
+  $handler->override_option('menu', array(
+    'type' => 'none',
+    'title' => '',
+    'description' => '',
+    'weight' => 0,
+    'name' => 'navigation',
+  ));
+  $handler->override_option('tab_options', array(
+    'type' => 'none',
+    'title' => '',
+    'description' => '',
+    'weight' => 0,
+    'name' => 'navigation',
+  ));
+  $handler = $view->new_display('page', 'Project', 'project');
+  $handler->override_option('arguments', array(
+    'nid' => array(
+      'default_action' => 'not found',
+      'style_plugin' => 'default_summary',
+      'style_options' => array(),
+      'wildcard' => 'all',
+      'wildcard_substitution' => 'All',
+      'title' => '',
+      'breadcrumb' => '',
+      'default_argument_type' => 'node',
+      'default_argument' => '',
+      'validate_type' => 'node',
+      'validate_fail' => 'not found',
+      'break_phrase' => 0,
+      'not' => 0,
+      'id' => 'nid',
+      'table' => 'node',
+      'field' => 'nid',
+      'relationship' => 'nid',
+      'validate_user_argument_type' => 'uid',
+      'validate_user_roles' => array(
+        '2' => 0,
+        '3' => 0,
+      ),
+      'override' => array(
+        'button' => 'Use default',
+      ),
+      'default_options_div_prefix' => '',
+      'default_argument_fixed' => '',
+      'default_argument_user' => 0,
+      'default_argument_php' => '',
+      'validate_argument_node_type' => array(
+        'project_project' => 'project_project',
+        'project_release' => 0,
+        'project_issue' => 0,
+        'page' => 0,
+        'story' => 0,
+      ),
+      'validate_argument_node_access' => 0,
+      'validate_argument_nid_type' => 'nid',
+      'validate_argument_vocabulary' => array(
+        '1' => 0,
+      ),
+      'validate_argument_type' => 'tid',
+      'validate_argument_transform' => 0,
+      'validate_user_restrict_roles' => 0,
+      'validate_argument_project_term_argument_type' => 'tid',
+      'validate_argument_project_term_argument_action_top_without' => 'pass',
+      'validate_argument_project_term_argument_action_top_with' => 'pass',
+      'validate_argument_project_term_argument_action_child' => 'pass',
+      'validate_argument_php' => '',
+    ),
+  ));
+  $handler->override_option('path', 'node/%/activity');
+  $handler->override_option('menu', array(
+    'type' => 'tab',
+    'title' => 'Activity',
+    'description' => '',
+    'weight' => '0',
+    'name' => 'navigation',
+  ));
+  $handler->override_option('tab_options', array(
+    'type' => 'none',
+    'title' => '',
+    'description' => '',
+    'weight' => 0,
+    'name' => 'navigation',
+  ));
+  $views[$view->name] = $view;
+  
+  return $views;
+}
diff --git a/activity/views/project_activity_views_handler_argument_pid.inc b/activity/views/project_activity_views_handler_argument_pid.inc
new file mode 100644
index 0000000..4478878
--- /dev/null
+++ b/activity/views/project_activity_views_handler_argument_pid.inc
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Argument handler to accept a project pid.
+ */
+class project_activity_views_handler_argument_pid extends views_handler_argument_numeric {
+
+  /**
+   * Override the behavior of title(). Get the title of the node.
+   */
+  function query() {
+    $this->node_table = $this->query->ensure_table('node_activity', 'activity');
+    
+    $this->release_table = $this->query->add_table('project_release_nodes', 'node_activity');
+    
+    $this->issue_table = $this->query->add_table('project_issues', 'node_activity');
+    $this->query->table_queue[$this->issue_table]['join']->type = 'LEFT';
+    
+    $this->field = "COALESCE($this->issue_table.pid, $this->release_table.pid, $this->node_table.nid)";
+    
+    if (!empty($this->options['break_phrase'])) {
+      views_break_phrase($this->argument, $this);
+    }
+    else {
+      $this->value = array($this->argument);
+    }
+    
+    if (count($this->value) > 1) {
+      $operator = empty($this->options['not']) ? 'IN' : 'NOT IN';
+      $placeholders = implode(', ', array_fill(0, sizeof($this->value), '%d'));
+      $this->query->add_where(0, "$this->field $operator ($placeholders)", $this->value);
+    }
+    else {
+      $operator = empty($this->options['not']) ? '=' : '!=';
+      $this->query->add_where(0, "$this->field $operator %d", $this->argument, $this->argument);
+    }
+  }
+}
\ No newline at end of file
