PageRenderTime 59ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/src/applications/maniphest/editor/transaction/ManiphestTransactionEditor.php

http://github.com/facebook/phabricator
PHP | 352 lines | 281 code | 46 blank | 25 comment | 26 complexity | 143ac8c28014044bcabb8b8ae4f05f28 MD5 | raw file
Possible License(s): JSON, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause, LGPL-2.0, MIT, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /*
  3. * Copyright 2011 Facebook, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @group maniphest
  19. */
  20. class ManiphestTransactionEditor {
  21. private $parentMessageID;
  22. public function setParentMessageID($parent_message_id) {
  23. $this->parentMessageID = $parent_message_id;
  24. return $this;
  25. }
  26. public function applyTransactions($task, array $transactions) {
  27. $email_cc = $task->getCCPHIDs();
  28. $email_to = array();
  29. $email_to[] = $task->getOwnerPHID();
  30. foreach ($transactions as $key => $transaction) {
  31. $type = $transaction->getTransactionType();
  32. $new = $transaction->getNewValue();
  33. $email_to[] = $transaction->getAuthorPHID();
  34. $value_is_phid_set = false;
  35. switch ($type) {
  36. case ManiphestTransactionType::TYPE_NONE:
  37. $old = null;
  38. break;
  39. case ManiphestTransactionType::TYPE_STATUS:
  40. $old = $task->getStatus();
  41. break;
  42. case ManiphestTransactionType::TYPE_OWNER:
  43. $old = $task->getOwnerPHID();
  44. break;
  45. case ManiphestTransactionType::TYPE_CCS:
  46. $old = $task->getCCPHIDs();
  47. $value_is_phid_set = true;
  48. break;
  49. case ManiphestTransactionType::TYPE_PRIORITY:
  50. $old = $task->getPriority();
  51. break;
  52. case ManiphestTransactionType::TYPE_ATTACH:
  53. $old = $task->getAttached();
  54. break;
  55. case ManiphestTransactionType::TYPE_TITLE:
  56. $old = $task->getTitle();
  57. break;
  58. case ManiphestTransactionType::TYPE_DESCRIPTION:
  59. $old = $task->getDescription();
  60. break;
  61. case ManiphestTransactionType::TYPE_PROJECTS:
  62. $old = $task->getProjectPHIDs();
  63. $value_is_phid_set = true;
  64. break;
  65. case ManiphestTransactionType::TYPE_AUXILIARY:
  66. $aux_key = $transaction->getMetadataValue('aux:key');
  67. if (!$aux_key) {
  68. throw new Exception(
  69. "Expected 'aux:key' metadata on TYPE_AUXILIARY transaction.");
  70. }
  71. $old = $task->getAuxiliaryAttribute($aux_key);
  72. break;
  73. default:
  74. throw new Exception('Unknown action type.');
  75. }
  76. $old_cmp = $old;
  77. $new_cmp = $new;
  78. if ($value_is_phid_set) {
  79. // Normalize the old and new values if they are PHID sets so we don't
  80. // get any no-op transactions where the values differ only by keys,
  81. // order, duplicates, etc.
  82. if (is_array($old)) {
  83. $old = array_filter($old);
  84. $old = array_unique($old);
  85. sort($old);
  86. $old = array_values($old);
  87. $old_cmp = $old;
  88. }
  89. if (is_array($new)) {
  90. $new = array_filter($new);
  91. $new = array_unique($new);
  92. $transaction->setNewValue($new);
  93. $new_cmp = $new;
  94. sort($new_cmp);
  95. $new_cmp = array_values($new_cmp);
  96. }
  97. }
  98. if (($old !== null) && ($old_cmp == $new_cmp)) {
  99. if (count($transactions) > 1 && !$transaction->hasComments()) {
  100. // If we have at least one other transaction and this one isn't
  101. // doing anything and doesn't have any comments, just throw it
  102. // away.
  103. unset($transactions[$key]);
  104. continue;
  105. } else {
  106. $transaction->setOldValue(null);
  107. $transaction->setNewValue(null);
  108. $transaction->setTransactionType(ManiphestTransactionType::TYPE_NONE);
  109. }
  110. } else {
  111. switch ($type) {
  112. case ManiphestTransactionType::TYPE_NONE:
  113. break;
  114. case ManiphestTransactionType::TYPE_STATUS:
  115. $task->setStatus($new);
  116. break;
  117. case ManiphestTransactionType::TYPE_OWNER:
  118. if ($new) {
  119. $handles = id(new PhabricatorObjectHandleData(array($new)))
  120. ->loadHandles();
  121. $task->setOwnerOrdering($handles[$new]->getName());
  122. } else {
  123. $task->setOwnerOrdering(null);
  124. }
  125. $task->setOwnerPHID($new);
  126. break;
  127. case ManiphestTransactionType::TYPE_CCS:
  128. $task->setCCPHIDs($new);
  129. break;
  130. case ManiphestTransactionType::TYPE_PRIORITY:
  131. $task->setPriority($new);
  132. break;
  133. case ManiphestTransactionType::TYPE_ATTACH:
  134. $task->setAttached($new);
  135. break;
  136. case ManiphestTransactionType::TYPE_TITLE:
  137. $task->setTitle($new);
  138. break;
  139. case ManiphestTransactionType::TYPE_DESCRIPTION:
  140. $task->setDescription($new);
  141. break;
  142. case ManiphestTransactionType::TYPE_PROJECTS:
  143. $task->setProjectPHIDs($new);
  144. break;
  145. case ManiphestTransactionType::TYPE_AUXILIARY:
  146. $aux_key = $transaction->getMetadataValue('aux:key');
  147. $task->setAuxiliaryAttribute($aux_key, $new);
  148. break;
  149. default:
  150. throw new Exception('Unknown action type.');
  151. }
  152. $transaction->setOldValue($old);
  153. $transaction->setNewValue($new);
  154. }
  155. }
  156. $task->save();
  157. foreach ($transactions as $transaction) {
  158. $transaction->setTaskID($task->getID());
  159. $transaction->save();
  160. }
  161. $email_to[] = $task->getOwnerPHID();
  162. $email_cc = array_merge(
  163. $email_cc,
  164. $task->getCCPHIDs());
  165. $this->publishFeedStory($task, $transactions);
  166. // TODO: Do this offline via timeline
  167. PhabricatorSearchManiphestIndexer::indexTask($task);
  168. $this->sendEmail($task, $transactions, $email_to, $email_cc);
  169. }
  170. protected function getSubjectPrefix() {
  171. return PhabricatorEnv::getEnvConfig('metamta.maniphest.subject-prefix');
  172. }
  173. private function sendEmail($task, $transactions, $email_to, $email_cc) {
  174. $email_to = array_filter(array_unique($email_to));
  175. $email_cc = array_filter(array_unique($email_cc));
  176. $phids = array();
  177. foreach ($transactions as $transaction) {
  178. foreach ($transaction->extractPHIDs() as $phid) {
  179. $phids[$phid] = true;
  180. }
  181. }
  182. foreach ($email_to as $phid) {
  183. $phids[$phid] = true;
  184. }
  185. foreach ($email_cc as $phid) {
  186. $phids[$phid] = true;
  187. }
  188. $phids = array_keys($phids);
  189. $handles = id(new PhabricatorObjectHandleData($phids))
  190. ->loadHandles();
  191. $view = new ManiphestTransactionDetailView();
  192. $view->setTransactionGroup($transactions);
  193. $view->setHandles($handles);
  194. list($action, $body) = $view->renderForEmail($with_date = false);
  195. $is_create = $this->isCreate($transactions);
  196. $task_uri = PhabricatorEnv::getURI('/T'.$task->getID());
  197. $reply_handler = $this->buildReplyHandler($task);
  198. if ($is_create) {
  199. $body .=
  200. "\n\n".
  201. "TASK DESCRIPTION\n".
  202. " ".$task->getDescription();
  203. }
  204. $body .=
  205. "\n\n".
  206. "TASK DETAIL\n".
  207. " ".$task_uri."\n";
  208. $reply_instructions = $reply_handler->getReplyHandlerInstructions();
  209. if ($reply_instructions) {
  210. $body .=
  211. "\n".
  212. "REPLY HANDLER ACTIONS\n".
  213. " ".$reply_instructions."\n";
  214. }
  215. $thread_id = '<maniphest-task-'.$task->getPHID().'>';
  216. $task_id = $task->getID();
  217. $title = $task->getTitle();
  218. $prefix = $this->getSubjectPrefix();
  219. $subject = trim("{$prefix} [{$action}] T{$task_id}: {$title}");
  220. $template = id(new PhabricatorMetaMTAMail())
  221. ->setSubject($subject)
  222. ->setFrom($transaction->getAuthorPHID())
  223. ->setParentMessageID($this->parentMessageID)
  224. ->addHeader('Thread-Topic', 'Maniphest Task '.$task->getID())
  225. ->setThreadID($thread_id, $is_create)
  226. ->setRelatedPHID($task->getPHID())
  227. ->setIsBulk(true)
  228. ->setBody($body);
  229. $mails = $reply_handler->multiplexMail(
  230. $template,
  231. array_select_keys($handles, $email_to),
  232. array_select_keys($handles, $email_cc));
  233. foreach ($mails as $mail) {
  234. $mail->saveAndSend();
  235. }
  236. }
  237. public function buildReplyHandler(ManiphestTask $task) {
  238. $handler_class = PhabricatorEnv::getEnvConfig(
  239. 'metamta.maniphest.reply-handler');
  240. $handler_object = newv($handler_class, array());
  241. $handler_object->setMailReceiver($task);
  242. return $handler_object;
  243. }
  244. private function publishFeedStory(ManiphestTask $task, array $transactions) {
  245. $actions = array(ManiphestAction::ACTION_UPDATE);
  246. $comments = null;
  247. foreach ($transactions as $transaction) {
  248. if ($transaction->hasComments()) {
  249. $comments = $transaction->getComments();
  250. }
  251. switch ($transaction->getTransactionType()) {
  252. case ManiphestTransactionType::TYPE_OWNER:
  253. $actions[] = ManiphestAction::ACTION_ASSIGN;
  254. break;
  255. case ManiphestTransactionType::TYPE_STATUS:
  256. if ($task->getStatus() != ManiphestTaskStatus::STATUS_OPEN) {
  257. $actions[] = ManiphestAction::ACTION_CLOSE;
  258. } else if ($this->isCreate($transactions)) {
  259. $actions[] = ManiphestAction::ACTION_CREATE;
  260. }
  261. break;
  262. default:
  263. break;
  264. }
  265. }
  266. $action_type = ManiphestAction::selectStrongestAction($actions);
  267. $owner_phid = $task->getOwnerPHID();
  268. $actor_phid = head($transactions)->getAuthorPHID();
  269. $author_phid = $task->getAuthorPHID();
  270. id(new PhabricatorFeedStoryPublisher())
  271. ->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_MANIPHEST)
  272. ->setStoryData(array(
  273. 'taskPHID' => $task->getPHID(),
  274. 'transactionIDs' => mpull($transactions, 'getID'),
  275. 'ownerPHID' => $owner_phid,
  276. 'action' => $action_type,
  277. 'comments' => $comments,
  278. 'description' => $task->getDescription(),
  279. ))
  280. ->setStoryTime(time())
  281. ->setStoryAuthorPHID($actor_phid)
  282. ->setRelatedPHIDs(
  283. array_merge(
  284. array_filter(
  285. array(
  286. $task->getPHID(),
  287. $author_phid,
  288. $actor_phid,
  289. $owner_phid,
  290. )),
  291. $task->getProjectPHIDs()))
  292. ->publish();
  293. }
  294. private function isCreate(array $transactions) {
  295. $is_create = false;
  296. foreach ($transactions as $transaction) {
  297. $type = $transaction->getTransactionType();
  298. if (($type == ManiphestTransactionType::TYPE_STATUS) &&
  299. ($transaction->getOldValue() === null) &&
  300. ($transaction->getNewValue() == ManiphestTaskStatus::STATUS_OPEN)) {
  301. $is_create = true;
  302. }
  303. }
  304. return $is_create;
  305. }
  306. }