Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/notification/builder/PhabricatorNotificationBuilder.php
12256 views
1
<?php
2
3
final class PhabricatorNotificationBuilder extends Phobject {
4
5
private $stories;
6
private $parsedStories;
7
private $user = null;
8
private $showTimestamps = true;
9
10
public function __construct(array $stories) {
11
assert_instances_of($stories, 'PhabricatorFeedStory');
12
$this->stories = $stories;
13
}
14
15
public function setUser($user) {
16
$this->user = $user;
17
return $this;
18
}
19
20
public function setShowTimestamps($show_timestamps) {
21
$this->showTimestamps = $show_timestamps;
22
return $this;
23
}
24
25
public function getShowTimestamps() {
26
return $this->showTimestamps;
27
}
28
29
private function parseStories() {
30
31
if ($this->parsedStories) {
32
return $this->parsedStories;
33
}
34
35
$stories = $this->stories;
36
$stories = mpull($stories, null, 'getChronologicalKey');
37
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.
44
45
// Build up a map of all the possible aggregations.
46
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;
55
}
56
}
57
58
// Repeatedly select the largest available aggregation until none remain.
59
60
$aggregated_stories = array();
61
while ($aggregation_map) {
62
63
// Count the size of each aggregation, removing any which will consume
64
// fewer than 2 stories.
65
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]);
72
}
73
}
74
75
// If we're out of aggregations, break out.
76
77
if (!$aggregation_map) {
78
break;
79
}
80
81
// Select the aggregation we're going to make, and remove it from the
82
// map.
83
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]);
88
89
// Select all the stories it aggregates, and remove them from the master
90
// list of stories and from all other possible aggregations.
91
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]);
98
}
99
unset($chronokey_map[$chronokey]);
100
}
101
102
// Build the aggregate story.
103
104
krsort($sub_stories);
105
$story_class = $agg_types[$agg_key];
106
$conv = array(head($sub_stories)->getStoryData());
107
108
$new_story = newv($story_class, $conv);
109
$new_story->setAggregateStories($sub_stories);
110
$aggregated_stories[] = $new_story;
111
}
112
113
// Combine the aggregate stories back into the list of stories.
114
115
$stories = array_merge($stories, $aggregated_stories);
116
$stories = mpull($stories, null, 'getChronologicalKey');
117
krsort($stories);
118
119
$this->parsedStories = $stories;
120
return $stories;
121
}
122
123
public function buildView() {
124
$stories = $this->parseStories();
125
$null_view = new AphrontNullView();
126
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;
133
}
134
135
$view->setShowTimestamp($this->getShowTimestamps());
136
137
$null_view->appendChild($view->renderNotification($this->user));
138
}
139
140
return $null_view;
141
}
142
143
public function buildDict() {
144
$stories = $this->parseStories();
145
$dict = array();
146
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);
152
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(),
162
);
163
} else {
164
$dict[] = array(
165
'showWebNotification' => false,
166
'showDesktopNotification' => false,
167
'title' => null,
168
'body' => null,
169
'href' => null,
170
'icon' => null,
171
);
172
}
173
}
174
175
return $dict;
176
}
177
}
178
179