From 30522b8cdec1c7a3ca7835b44dd0b69457425ae3 Mon Sep 17 00:00:00 2001
From: Derek Wright <git@dwwright.net>
Date: Sat, 25 Feb 2012 20:09:30 -0800
Subject: [PATCH] [#112805] by moshe weitzman, greg.1.anderson, xjm, hunmonk, sun, dww:
 Added a JSON menu callback (node/%/project-issue/json) for issue metadata.

This also adds hook_project_issue_json_alter().
---
 README.txt                      |   27 ++++++++-
 includes/project_issue.json.inc |  127 +++++++++++++++++++++++++++++++++++++++
 project_issue.api.php           |   32 ++++++++++
 project_issue.module            |   16 +++++
 4 files changed, 200 insertions(+), 2 deletions(-)
 create mode 100644 includes/project_issue.json.inc

diff --git a/README.txt b/README.txt
index 76de5a5..5795450 100644
--- a/README.txt
+++ b/README.txt
@@ -2,11 +2,15 @@ This module allows teams to track outstanding items which need
 resolution. It provides e-mail notifications to members about updates
 to items.  Similar to many issue tracking systems.
 
+INSTALLATION AND UPGRADE
+------------------------
+
 For installation instructions, see INSTALL.txt.
 
 For instructions when upgrading to newer versions, see UPGRADE.txt.
 
-CAVEATS:
+CAVEATS
+-------
 
 The filter which automatically turns references to project issues into
 links conflicts with filter caching in the following ways:
@@ -24,7 +28,26 @@ Send feature requests and bug reports to the issue tracking system for
 the project module: http://drupal.org/node/add/project_issue/project_issue.
 A TODO list can be found at http://groups.drupal.org/node/5489
 
+JSON WEB SERVICE
+----------------
+
+In addition to an HTML page at node/[nid], each issue's metadata may be viewed
+in JSON format at node/[nid]/project-issue/json.
+
+Supported request parameters:
+
+  api_version=[major]
+    If passed, the specified major version of the JSON API will be used for the
+    call. If not passed, then the newest version of the API will be used.
+
+    The current API Version is 1.
+
+    Modules may alter the JSON through hook_project_issue_json_alter().
+    See project_issue.api.php.
+
+
+MAINTENANCE
+-----------
 The project family of modules is currently being co-maintained by:
 - Derek Wright (http://drupal.org/user/46549) a.k.a. "dww"
 - Chad Phillips (http://drupal.org/user/22079) a.k.a. "hunmonk"
-
diff --git a/includes/project_issue.json.inc b/includes/project_issue.json.inc
new file mode 100644
index 0000000..07124df
--- /dev/null
+++ b/includes/project_issue.json.inc
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * Menu callback. Returns JSON for an issue node.
+ *
+ * @todo
+ * When the major API version of this callback changes, it will need to be
+ * refactored. See http://drupal.org/node/112805#comment-5469522.
+ *
+ * @param $issue
+ *   A node of type project_issue.
+ * @return void
+ */
+function project_issue_json($issue) {
+  if ($project = node_load($issue->project_issue['pid'])) {
+    // All returned URLs need to be absolute.
+    $options = array('absolute' => TRUE);
+
+    // Retrieve the associated project release.
+    if (isset($issue->project_issue['rid'])) {
+      $release = node_load($issue->project_issue['rid']);
+    }
+    if (empty($release)) {
+      $release = (object) array(
+        'nid' => NULL,
+        'project_release' => array('version' => NULL),
+      );
+    }
+
+    // Retrieve the assigned user.
+    if (isset($issue->project_issue['assigned'])) {
+      $assigned = user_load($issue->project_issue['assigned']);
+    }
+    if (empty($assigned)) {
+      $assigned = (object) array(
+        'uid' => NULL,
+        'name' => NULL,
+      );
+    }
+    $assigned->url = !empty($assigned->uid) ? url('user/' . $assigned->uid, $options) : NULL;
+
+    // Retrieve the issue author.
+    if (!empty($issue->uid)) {
+      $author = user_load($issue->uid);
+    }
+    if (empty($author)) {
+      $author = (object) array(
+        'uid' => NULL,
+        'name' => NULL,
+      );
+    }
+    $author->url = !empty($author->uid) ? url('user/' . $author->uid, $options) : NULL;
+
+    // Retrieve file attachments on the issue node.
+    // Does not contain attachments on comments.
+    // @see comment_upload_project_issue_json_alter()
+    $attachments = array();
+    foreach ($issue->files as $fid => $file) {
+      // Attachments are keyed by comment ID. We treat the original issue node
+      // as post 0, so as to have all issue attachments in one property that can
+      // be processed by API consumers.
+      $attachments[0]['contributor'] = $author->uid;
+      $attachments[0]['comment'] = 0;
+      $attachments[0]['urls'][$fid] = file_create_url($file->filepath);
+    }
+
+    // Populate basic issue data.
+    $return = array(
+      // Increase minor version when adding information.
+      // Increase major version when breaking backwards compatibility.
+      'api-version' => '1.0',
+      'title' => $issue->title,
+      'id' => $issue->nid,
+      'created' => $issue->created,
+      'changed' => $issue->changed,
+      'url' => url('node/' . $issue->nid, $options),
+      'component' => $issue->project_issue['component'],
+      'category' => $issue->project_issue['category'],
+      'priority-id' => $issue->project_issue['priority'],
+      'priority' => project_issue_priority($issue->project_issue['priority']),
+      'status-id' => $issue->project_issue['sid'],
+      'status' => project_issue_state($issue->project_issue['sid']),
+      'version-id' => $release->nid,
+      'version' => $release->project_release['version'],
+      'author-id' => $author->uid,
+      'author-name' => $author->name,
+      'author-url' => $author->url,
+      'assigned-id' => $assigned->uid,
+      'assigned-name' => $assigned->name,
+      'assigned-url' => $assigned->url,
+      'project-id' => $project->nid,
+      'project-title' => $project->title,
+      'project-url' => url('node/' . $project->nid, $options),
+      'project-name' => $project->project['uri'],
+      'attachments' => $attachments,
+    );
+
+    // Retrieve and populate comments and contributors (comment authors).
+    $result = db_query("SELECT c.cid, c.status, c.timestamp, c.thread, u.name, u.uid FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.nid = %d AND c.status = %d ORDER BY c.cid ASC", $issue->nid, COMMENT_PUBLISHED);
+    while ($row = db_fetch_object($result)) {
+      $return['contributors'][$row->uid] = array(
+        'name' => $row->name,
+        'uid' => $row->uid,
+        'url' => url('user/' . $row->uid, $options),
+      );
+      $return['comments'][$row->cid] = array(
+        'contributor' => $row->uid,
+        'url' => url('node/' . $issue->nid, array('absolute' => TRUE, 'fragment' => 'comment-' . $row->cid)),
+        'created' => $row->timestamp,
+        'status' => $row->status,
+        'thread' => $row->thread,
+      );
+    }
+
+    // Allow modules to change or enhance the returned data.
+    $context = array(
+      'issue' => $issue,
+      'project' => $project,
+      'release' => $release,
+      'assigned' => $assigned,
+      'author' => $author,
+    );
+    drupal_alter('project_issue_json', $return, $context);
+
+    drupal_json($return);
+  }
+}
diff --git a/project_issue.api.php b/project_issue.api.php
index a0b6176..5e4dd6b 100644
--- a/project_issue.api.php
+++ b/project_issue.api.php
@@ -21,3 +21,35 @@
 function hook_project_issue_internal_links_alter(&$links, $node) {
   $links[] = l(t('Most excellent comment'), "node/$node->nid", array('fragment' => 'excellent')); // If only it were so easy. ;)
 }
+
+/**
+ * Alter project issue JSON data before it is returned.
+ *
+ * @param array $issue
+ *   An associative array containing information about the project issue node
+ *   for which JSON is returned. Passed by reference.
+ * @param array $context
+ *   An associative array containing contextual information:
+ *   - issue: The project_issue node.
+ *   - project: The project node associated with the issue.
+ *   - release: (optional) The project_release node associated with the issue.
+ *   - assigned: (optional) The user account being assigned to the issue.
+ *   - author: (optional) The author of the project_issue node.
+ *   Optional contexts may contain NULL values for basic entity properties if
+ *   no associated data could be loaded.
+ *
+ * Modules should not change the 'api-version' unless required, as this implies
+ * an API change for consumers, and Project Issue module may change the API
+ * version on its own.
+ *
+ * @see project_issue_json()
+ * @see drupal_alter()
+ */
+function hook_project_issue_json_alter(&$issue) {
+  $result = comment_upload_fetch_all($issue['id'], FALSE);
+  while ($row = db_fetch_object($result)) {
+    $issue['attachments'][$row->cid]['contributor'] = $row->uid;
+    $issue['attachments'][$row->cid]['comment'] = $row->cid;
+    $issue['attachments'][$row->cid]['urls'][$row->fid] = file_create_url($row->filepath);
+  }
+}
diff --git a/project_issue.module b/project_issue.module
index b0e709a..cf2beb2 100644
--- a/project_issue.module
+++ b/project_issue.module
@@ -234,6 +234,15 @@ function project_issue_menu() {
     'type' => MENU_CALLBACK,
   );
 
+  $items['node/%node/project-issue/json'] = array(
+    'title' => 'JSON',
+    'page callback' => 'project_issue_json',
+    'page arguments' => array(1),
+    'access callback' => 'project_issue_menu_access_view',
+    'access arguments' => array(1),
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/project_issue.json.inc',
+  );
   // Autocomplete paths.
 
   // Autocomplete a comma-separated list of projects that have issues enabled.
@@ -300,6 +309,13 @@ function project_issue_menu_access($type) {
   return user_access('access project issues') || user_access('access own project issues');
 }
 
+/**
+ * Menu access callback; Checks whether $node is an issue and the user is allowed to view it.
+ */
+function project_issue_menu_access_view($node) {
+  return $node->type == 'project_issue' && node_access('view', $node);
+}
+
 function project_issue_help($path, $arg) {
   switch ($path) {
     case 'admin/help#project_issue':
-- 
1.7.3.5

