4 * @task config Configuring the Query
5 * @task exec Query Execution
7 final class PhabricatorNotificationQuery
8 extends PhabricatorCursorPagedPolicyAwareQuery
{
15 /* -( Configuring the Query )---------------------------------------------- */
18 public function withUserPHIDs(array $user_phids) {
19 $this->userPHIDs
= $user_phids;
23 public function withKeys(array $keys) {
30 * Filter results by read/unread status. Note that `true` means to return
31 * only unread notifications, while `false` means to return only //read//
32 * notifications. The default is `null`, which returns both.
34 * @param mixed True or false to filter results by read status. Null to remove
39 public function withUnread($unread) {
40 $this->unread
= $unread;
45 /* -( Query Execution )---------------------------------------------------- */
48 protected function loadPage() {
49 $story_table = new PhabricatorFeedStoryData();
50 $notification_table = new PhabricatorFeedStoryNotification();
52 $conn = $story_table->establishConnection('r');
56 'SELECT story.*, notification.hasViewed FROM %R notification
57 JOIN %R story ON notification.chronologicalKey = story.chronologicalKey
59 ORDER BY notification.chronologicalKey DESC
63 $this->buildWhereClause($conn),
64 $this->buildLimitClause($conn));
69 protected function buildWhereClauseParts(AphrontDatabaseConnection
$conn) {
70 $where = parent
::buildWhereClauseParts($conn);
72 if ($this->userPHIDs
!== null) {
75 'notification.userPHID IN (%Ls)',
79 if ($this->unread
!== null) {
82 'notification.hasViewed = %d',
86 if ($this->keys
!== null) {
89 'notification.chronologicalKey IN (%Ls)',
96 protected function willFilterPage(array $rows) {
97 // See T13623. The policy model here is outdated and awkward.
99 // Users may have notifications about objects they can no longer see.
100 // Two ways this can arise: destroy an object; or change an object's
101 // view policy to exclude a user.
103 // "PhabricatorFeedStory::loadAllFromRows()" does its own policy filtering.
104 // This doesn't align well with modern query sequencing, but we should be
105 // able to get away with it by loading here.
107 // See T13623. Although most queries for notifications return unique
108 // stories, this isn't a guarantee.
109 $story_map = ipull($rows, null, 'chronologicalKey');
111 $viewer = $this->getViewer();
112 $stories = PhabricatorFeedStory
::loadAllFromRows($story_map, $viewer);
113 $stories = mpull($stories, null, 'getChronologicalKey');
116 foreach ($rows as $row) {
117 $story_key = $row['chronologicalKey'];
118 $has_viewed = $row['hasViewed'];
120 if (!isset($stories[$story_key])) {
121 // NOTE: We can't call "didRejectResult()" here because we don't have
122 // a policy object to pass.
126 $story = id(clone $stories[$story_key])
127 ->setHasViewed($has_viewed);
129 if (!$story->isVisibleInNotifications()) {
139 protected function getDefaultOrderVector() {
143 public function getBuiltinOrders() {
146 'vector' => array('key'),
147 'name' => pht('Creation (Newest First)'),
148 'aliases' => array('created'),
151 'vector' => array('-key'),
152 'name' => pht('Creation (Oldest First)'),
157 public function getOrderableColumns() {
160 'table' => 'notification',
161 'column' => 'chronologicalKey',
168 protected function applyExternalCursorConstraintsToQuery(
169 PhabricatorCursorPagedPolicyAwareQuery
$subquery,
173 ->withKeys(array($cursor))
178 protected function newExternalCursorStringForResult($object) {
179 return $object->getChronologicalKey();
182 protected function newPagingMapFromPartialObject($object) {
184 'key' => $object['chronologicalKey'],
188 protected function getPrimaryTableAlias() {
189 return 'notification';
192 public function getQueryApplicationClass() {
193 return 'PhabricatorNotificationsApplication';