issues.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. <?php
  2. namespace Controller\Api;
  3. class Issues extends \Controller\Api {
  4. /**
  5. * Converts an issue into a Redmine API-style multidimensional array
  6. * This isn't pretty.
  7. * @param Detail $issue
  8. * @return array
  9. */
  10. protected function _issueMultiArray(\Model\Issue\Detail $issue) {
  11. $casted = $issue->cast();
  12. // Convert ALL the fields!
  13. $result = array();
  14. $result["tracker"] = array(
  15. "id" => $issue->type_id,
  16. "name" => $issue->type_name
  17. );
  18. $result["status"] = array(
  19. "id" => $issue->status,
  20. "name" => $issue->status_name
  21. );
  22. $result["priority"] = array(
  23. "id" => $issue->priority_id,
  24. "name" => $issue->priority_name
  25. );
  26. $result["author"] = array(
  27. "id" => $issue->author_id,
  28. "name" => $issue->author_name,
  29. "username" => $issue->author_username,
  30. "email" => $issue->author_email,
  31. "task_color" => $issue->author_task_color
  32. );
  33. $result["owner"] = array(
  34. "id" => $issue->owner_id,
  35. "name" => $issue->owner_name,
  36. "username" => $issue->owner_username,
  37. "email" => $issue->owner_email,
  38. "task_color" => $issue->owner_task_color
  39. );
  40. if(!empty($issue->sprint_id)) {
  41. $result["sprint"] = array(
  42. "id" => $issue->sprint_id,
  43. "name" => $issue->sprint_name,
  44. "start_date" => $issue->sprint_start_date,
  45. "end_date" => $issue->sprint_end_date,
  46. );
  47. }
  48. // Remove redundant fields
  49. foreach($issue->schema() as $i=>$val) {
  50. if(preg_match("/(type|status|priority|author|owner|sprint)_.+|has_due_date/", $i)) {
  51. unset($casted[$i]);
  52. }
  53. }
  54. return array_replace($casted, $result);
  55. }
  56. // Get a list of issues
  57. public function get($f3) {
  58. $issue = new \Model\Issue\Detail();
  59. $result = $issue->paginate(
  60. $f3->get("GET.offset") / ($f3->get("GET.limit") ?: 30),
  61. $f3->get("GET.limit") ?: 30
  62. );
  63. $issues = array();
  64. foreach($result["subset"] as $iss) {
  65. $issues[] = $this->_issueMultiArray($iss);
  66. }
  67. $this->_printJson(array(
  68. "total_count" => $result["total"],
  69. "limit" => $result["limit"],
  70. "issues" => $issues,
  71. "offset" => $result["pos"] * $result["limit"]
  72. ));
  73. }
  74. // Create a new issue
  75. public function post($f3) {
  76. if($_REQUEST) {
  77. // By default, use standard HTTP POST fields
  78. $post = $_REQUEST;
  79. } else {
  80. // For Redmine compatibility, also accept a JSON object
  81. try {
  82. $post = json_decode(file_get_contents('php://input'), true);
  83. } catch (Exception $e) {
  84. throw new Exception("Unable to parse input");
  85. }
  86. if(!empty($post["issue"])) {
  87. $post = $post["issue"];
  88. }
  89. // Convert Redmine names to Phproject names
  90. if(!empty($post["subject"])) {
  91. $post["name"] = $post["subject"];
  92. }
  93. if(!empty($post["parent_issue_id"])) {
  94. $post["parent_id"] = $post["parent_issue_id"];
  95. }
  96. if(!empty($post["tracker_id"])) {
  97. $post["type_id"] = $post["tracker_id"];
  98. }
  99. if(!empty($post["assigned_to_id"])) {
  100. $post["owner_id"] = $post["assigned_to_id"];
  101. }
  102. if(!empty($post["fixed_version_id"])) {
  103. $post["sprint_id"] = $post["fixed_version_id"];
  104. }
  105. }
  106. // Ensure a status ID is added
  107. if(!empty($post["status_id"])) {
  108. $post["status"] = $post["status_id"];
  109. }
  110. if(empty($post["status"])) {
  111. $post["status"] = 1;
  112. }
  113. // Verify the required "name" field is passed
  114. if(empty($post["name"])) {
  115. $f3->error("The 'name' value is required.");
  116. return;
  117. }
  118. // Verify given values are valid (types, statueses, priorities)
  119. if(!empty($post["type_id"])) {
  120. $type = new \Model\Issue\Type;
  121. $type->load($post["type_id"]);
  122. if(!$type->id) {
  123. $f3->error("The 'type_id' field is not valid.");
  124. return;
  125. }
  126. }
  127. if(!empty($post["parent_id"])) {
  128. $parent = new \Model\Issue;
  129. $parent->load($post["parent_id"]);
  130. if(!$parent->id) {
  131. $f3->error("The 'type_id' field is not valid.");
  132. return;
  133. }
  134. }
  135. if(!empty($post["status"])) {
  136. $status = new \Model\Issue\Status;
  137. $status->load($post["status"]);
  138. if(!$status->id) {
  139. $f3->error("The 'status' field is not valid.");
  140. return;
  141. }
  142. }
  143. if(!empty($post["priority_id"])) {
  144. $priority = new \Model\Issue\Priority;
  145. $priority->load(array("value" => $post["priority_id"]));
  146. if(!$priority->id) {
  147. $f3->error("The 'priority_id' field is not valid.");
  148. return;
  149. }
  150. }
  151. // Create a new issue based on the data
  152. $issue = new \Model\Issue();
  153. $issue->author_id = !empty($post["author_id"]) ? $post["author_id"] : $this->_userId;
  154. $issue->name = trim($post["name"]);
  155. $issue->type_id = empty($post["type_id"]) ? 1 : $post["type_id"];
  156. $issue->priority_id = empty($post["priority_id"]) ? 0 : $post["priority_id"];
  157. // Set due date if valid
  158. if(!empty($post["due_date"]) && preg_match("/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}( [0-9:]{8})?$/", $post["due_date"])) {
  159. $issue->due_date = $post["due_date"];
  160. } elseif(!empty($post["due_date"]) && $due_date = strtotime($post["due_date"])) {
  161. $issue->due_date = date("Y-m-d", $due_date);
  162. }
  163. if(!empty($post["description"])) {
  164. $issue->description = $post["description"];
  165. }
  166. if(!empty($post["parent_id"])) {
  167. $issue->parent_id = $post["parent_id"];
  168. }
  169. if(!empty($post["owner_id"])) {
  170. $issue->owner_id = $post["owner_id"];
  171. }
  172. $issue->save();
  173. $this->_printJson(array(
  174. "issue" => $issue->cast()
  175. ));
  176. }
  177. // Update an existing issue
  178. public function single_put($f3, $params) {
  179. $issue = new \Model\Issue;
  180. $issue->load($params["id"]);
  181. if(!$issue->id) {
  182. $f3->error(404);
  183. return;
  184. }
  185. $updated = array();
  186. foreach($f3->get("REQUEST") as $key => $val) {
  187. if(is_scalar($val) && $issue->exists($key)) {
  188. $updated[] = $key;
  189. $issue->set($key, $val);
  190. }
  191. }
  192. if($updated) {
  193. $issue->save();
  194. }
  195. $this->printJson(array("updated_fields" => $updated, "issue" => $this->_issueMultiArray($issue)));
  196. }
  197. // Get a single issue's details
  198. public function single_get($f3, $params) {
  199. $issue = new \Model\Issue\Detail;
  200. $issue->load($params["id"]);
  201. if($issue->id) {
  202. $this->_printJson(array("issue" => $this->_issueMultiArray($issue)));
  203. } else {
  204. $f3->error(404);
  205. }
  206. }
  207. // Delete a single issue
  208. public function single_delete($f3, $params) {
  209. $issue = new \Model\Issue;
  210. $issue->load($params["id"]);
  211. $issue->delete();
  212. if(!$issue->id) {
  213. $f3->error(404);
  214. return;
  215. }
  216. $this->_printJson(array(
  217. "deleted" => $params["id"]
  218. ));
  219. }
  220. }