Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / notification / builder / PhabricatorNotificationBuilder.php
blobb9a79be3f333d7cea8e044c55dfe6a6d57208bad
1 <?php
3 final class PhabricatorNotificationBuilder extends Phobject {
5 private $stories;
6 private $parsedStories;
7 private $user = null;
8 private $showTimestamps = true;
10 public function __construct(array $stories) {
11 assert_instances_of($stories, 'PhabricatorFeedStory');
12 $this->stories = $stories;
15 public function setUser($user) {
16 $this->user = $user;
17 return $this;
20 public function setShowTimestamps($show_timestamps) {
21 $this->showTimestamps = $show_timestamps;
22 return $this;
25 public function getShowTimestamps() {
26 return $this->showTimestamps;
29 private function parseStories() {
31 if ($this->parsedStories) {
32 return $this->parsedStories;
35 $stories = $this->stories;
36 $stories = mpull($stories, null, 'getChronologicalKey');
38 // Aggregate notifications. Generally, we can aggregate notifications only
39 // by object, e.g. "a updated T123" and "b updated T123" can become
40 // "a and b updated T123", but we can't combine "a updated T123" and
41 // "a updated T234" into "a updated T123 and T234" because there would be
42 // nowhere sensible for the notification to link to, and no reasonable way
43 // to unambiguously clear it.
45 // Build up a map of all the possible aggregations.
47 $chronokey_map = array();
48 $aggregation_map = array();
49 $agg_types = array();
50 foreach ($stories as $chronokey => $story) {
51 $chronokey_map[$chronokey] = $story->getNotificationAggregations();
52 foreach ($chronokey_map[$chronokey] as $key => $type) {
53 $agg_types[$key] = $type;
54 $aggregation_map[$key]['keys'][$chronokey] = true;
58 // Repeatedly select the largest available aggregation until none remain.
60 $aggregated_stories = array();
61 while ($aggregation_map) {
63 // Count the size of each aggregation, removing any which will consume
64 // fewer than 2 stories.
66 foreach ($aggregation_map as $key => $dict) {
67 $size = count($dict['keys']);
68 if ($size > 1) {
69 $aggregation_map[$key]['size'] = $size;
70 } else {
71 unset($aggregation_map[$key]);
75 // If we're out of aggregations, break out.
77 if (!$aggregation_map) {
78 break;
81 // Select the aggregation we're going to make, and remove it from the
82 // map.
84 $aggregation_map = isort($aggregation_map, 'size');
85 $agg_info = idx(last($aggregation_map), 'keys');
86 $agg_key = last_key($aggregation_map);
87 unset($aggregation_map[$agg_key]);
89 // Select all the stories it aggregates, and remove them from the master
90 // list of stories and from all other possible aggregations.
92 $sub_stories = array();
93 foreach ($agg_info as $chronokey => $ignored) {
94 $sub_stories[$chronokey] = $stories[$chronokey];
95 unset($stories[$chronokey]);
96 foreach ($chronokey_map[$chronokey] as $key => $type) {
97 unset($aggregation_map[$key]['keys'][$chronokey]);
99 unset($chronokey_map[$chronokey]);
102 // Build the aggregate story.
104 krsort($sub_stories);
105 $story_class = $agg_types[$agg_key];
106 $conv = array(head($sub_stories)->getStoryData());
108 $new_story = newv($story_class, $conv);
109 $new_story->setAggregateStories($sub_stories);
110 $aggregated_stories[] = $new_story;
113 // Combine the aggregate stories back into the list of stories.
115 $stories = array_merge($stories, $aggregated_stories);
116 $stories = mpull($stories, null, 'getChronologicalKey');
117 krsort($stories);
119 $this->parsedStories = $stories;
120 return $stories;
123 public function buildView() {
124 $stories = $this->parseStories();
125 $null_view = new AphrontNullView();
127 foreach ($stories as $story) {
128 try {
129 $view = $story->renderView();
130 } catch (Exception $ex) {
131 // TODO: Render a nice debuggable notice instead?
132 continue;
135 $view->setShowTimestamp($this->getShowTimestamps());
137 $null_view->appendChild($view->renderNotification($this->user));
140 return $null_view;
143 public function buildDict() {
144 $stories = $this->parseStories();
145 $dict = array();
147 $viewer = $this->user;
148 $key = PhabricatorNotificationsSetting::SETTINGKEY;
149 $setting = $viewer->getUserSetting($key);
150 $desktop_ready = PhabricatorNotificationsSetting::desktopReady($setting);
151 $web_ready = PhabricatorNotificationsSetting::webReady($setting);
153 foreach ($stories as $story) {
154 if ($story instanceof PhabricatorApplicationTransactionFeedStory) {
155 $dict[] = array(
156 'showAnyNotification' => $web_ready,
157 'showDesktopNotification' => $desktop_ready,
158 'title' => $story->renderText(),
159 'body' => $story->renderTextBody(),
160 'href' => $story->getURI(),
161 'icon' => $story->getImageURI(),
163 } else {
164 $dict[] = array(
165 'showWebNotification' => false,
166 'showDesktopNotification' => false,
167 'title' => null,
168 'body' => null,
169 'href' => null,
170 'icon' => null,
175 return $dict;