@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class PhabricatorBoardResponseEngine extends Phobject {
4
5 private $viewer;
6 private $objects;
7 private $boardPHID;
8 private $visiblePHIDs = array();
9 private $updatePHIDs = array();
10 private $ordering;
11 private $sounds;
12
13 public function setViewer(PhabricatorUser $viewer) {
14 $this->viewer = $viewer;
15 return $this;
16 }
17
18 public function getViewer() {
19 return $this->viewer;
20 }
21
22 public function setBoardPHID($board_phid) {
23 $this->boardPHID = $board_phid;
24 return $this;
25 }
26
27 public function getBoardPHID() {
28 return $this->boardPHID;
29 }
30
31 public function setObjects(array $objects) {
32 $this->objects = $objects;
33 return $this;
34 }
35
36 public function getObjects() {
37 return $this->objects;
38 }
39
40 public function setVisiblePHIDs(array $visible_phids) {
41 $this->visiblePHIDs = $visible_phids;
42 return $this;
43 }
44
45 public function getVisiblePHIDs() {
46 return $this->visiblePHIDs;
47 }
48
49 public function setUpdatePHIDs(array $update_phids) {
50 $this->updatePHIDs = $update_phids;
51 return $this;
52 }
53
54 public function getUpdatePHIDs() {
55 return $this->updatePHIDs;
56 }
57
58 public function setOrdering(PhabricatorProjectColumnOrder $ordering) {
59 $this->ordering = $ordering;
60 return $this;
61 }
62
63 public function getOrdering() {
64 return $this->ordering;
65 }
66
67 public function setSounds(array $sounds) {
68 $this->sounds = $sounds;
69 return $this;
70 }
71
72 public function getSounds() {
73 return $this->sounds;
74 }
75
76 public function buildResponse() {
77 $viewer = $this->getViewer();
78 $board_phid = $this->getBoardPHID();
79 $ordering = $this->getOrdering();
80
81 $update_phids = $this->getUpdatePHIDs();
82 $update_phids = array_fuse($update_phids);
83
84 $visible_phids = $this->getVisiblePHIDs();
85 $visible_phids = array_fuse($visible_phids);
86
87 $all_phids = $update_phids + $visible_phids;
88
89 // Load all the other tasks that are visible in the affected columns and
90 // perform layout for them.
91
92 if ($this->objects !== null) {
93 $all_objects = $this->getObjects();
94 $all_objects = mpull($all_objects, null, 'getPHID');
95 } else {
96 $all_objects = id(new ManiphestTaskQuery())
97 ->setViewer($viewer)
98 ->withPHIDs($all_phids)
99 ->execute();
100 $all_objects = mpull($all_objects, null, 'getPHID');
101 }
102
103 // NOTE: The board layout engine is sensitive to PHID input order, and uses
104 // the input order as a component of the "natural" column ordering if no
105 // explicit ordering is specified. Rearrange the PHIDs in ID order.
106
107 $all_objects = msort($all_objects, 'getID');
108 $ordered_phids = mpull($all_objects, 'getPHID');
109
110 $layout_engine = id(new PhabricatorBoardLayoutEngine())
111 ->setViewer($viewer)
112 ->setBoardPHIDs(array($board_phid))
113 ->setObjectPHIDs($ordered_phids)
114 ->executeLayout();
115
116 $natural = array();
117
118 $update_columns = array();
119 foreach ($update_phids as $update_phid) {
120 $update_columns += $layout_engine->getObjectColumns(
121 $board_phid,
122 $update_phid);
123 }
124
125 foreach ($update_columns as $column_phid => $column) {
126 $column_object_phids = $layout_engine->getColumnObjectPHIDs(
127 $board_phid,
128 $column_phid);
129 $natural[$column_phid] = array_values($column_object_phids);
130 }
131
132 if ($ordering) {
133 $vectors = $ordering->getSortVectorsForObjects($all_objects);
134 $header_keys = $ordering->getHeaderKeysForObjects($all_objects);
135 $headers = $ordering->getHeadersForObjects($all_objects);
136 $headers = mpull($headers, 'toDictionary');
137 } else {
138 $vectors = array();
139 $header_keys = array();
140 $headers = array();
141 }
142
143 $templates = $this->newCardTemplates();
144
145 $cards = array();
146 foreach ($all_objects as $card_phid => $object) {
147 $card = array(
148 'vectors' => array(),
149 'headers' => array(),
150 'properties' => array(),
151 'nodeHTMLTemplate' => null,
152 );
153
154 if ($ordering) {
155 $order_key = $ordering->getColumnOrderKey();
156
157 $vector = idx($vectors, $card_phid);
158 if ($vector !== null) {
159 $card['vectors'][$order_key] = $vector;
160 }
161
162 $header = idx($header_keys, $card_phid);
163 if ($header !== null) {
164 $card['headers'][$order_key] = $header;
165 }
166
167 $card['properties'] = self::newTaskProperties($object);
168 }
169
170 if (isset($templates[$card_phid])) {
171 $card['nodeHTMLTemplate'] = hsprintf('%s', $templates[$card_phid]);
172 $card['update'] = true;
173 } else {
174 $card['update'] = false;
175 }
176
177 $card['vectors'] = (object)$card['vectors'];
178 $card['headers'] = (object)$card['headers'];
179 $card['properties'] = (object)$card['properties'];
180
181 $cards[$card_phid] = $card;
182 }
183
184 // Mark cards which are currently visible on the client but not visible
185 // on the board on the server for removal from the client view of the
186 // board state.
187 foreach ($visible_phids as $card_phid) {
188 if (!isset($cards[$card_phid])) {
189 $cards[$card_phid] = array(
190 'remove' => true,
191 );
192 }
193 }
194
195 $payload = array(
196 'columnMaps' => $natural,
197 'cards' => $cards,
198 'headers' => $headers,
199 'sounds' => $this->getSounds(),
200 );
201
202 return id(new AphrontAjaxResponse())
203 ->setContent($payload);
204 }
205
206 public static function newTaskProperties($task) {
207 return array(
208 'points' => (float)$task->getPoints(),
209 'status' => $task->getStatus(),
210 'priority' => (int)$task->getPriority(),
211 'owner' => $task->getOwnerPHID(),
212 );
213 }
214
215 private function loadExcludedProjectPHIDs() {
216 $viewer = $this->getViewer();
217 $board_phid = $this->getBoardPHID();
218
219 $exclude_phids = array($board_phid);
220
221 $descendants = id(new PhabricatorProjectQuery())
222 ->setViewer($viewer)
223 ->withAncestorProjectPHIDs($exclude_phids)
224 ->execute();
225
226 foreach ($descendants as $descendant) {
227 $exclude_phids[] = $descendant->getPHID();
228 }
229
230 return array_fuse($exclude_phids);
231 }
232
233 private function newCardTemplates() {
234 $viewer = $this->getViewer();
235
236 $update_phids = $this->getUpdatePHIDs();
237 if (!$update_phids) {
238 return array();
239 }
240 $update_phids = array_fuse($update_phids);
241
242 if ($this->objects === null) {
243 $objects = id(new ManiphestTaskQuery())
244 ->setViewer($viewer)
245 ->withPHIDs($update_phids)
246 ->needProjectPHIDs(true)
247 ->execute();
248 } else {
249 $objects = $this->getObjects();
250 $objects = mpull($objects, null, 'getPHID');
251 $objects = array_select_keys($objects, $update_phids);
252 }
253
254 if (!$objects) {
255 return array();
256 }
257
258 $excluded_phids = $this->loadExcludedProjectPHIDs();
259
260 $rendering_engine = id(new PhabricatorBoardRenderingEngine())
261 ->setViewer($viewer)
262 ->setObjects($objects)
263 ->setExcludedProjectPHIDs($excluded_phids);
264
265 $templates = array();
266 foreach ($objects as $object) {
267 $object_phid = $object->getPHID();
268
269 $card = $rendering_engine->renderCard($object_phid);
270 $item = $card->getItem();
271 $template = hsprintf('%s', $item);
272
273 $templates[$object_phid] = $template;
274 }
275
276 return $templates;
277 }
278
279}