@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
at upstream/main 198 lines 5.1 kB view raw
1<?php 2 3/** 4 * @task config Configuring the Query 5 * @task exec Query Execution 6 * 7 * @extends PhabricatorCursorPagedPolicyAwareQuery<PhabricatorFeedStory> 8 */ 9final class PhabricatorNotificationQuery 10 extends PhabricatorCursorPagedPolicyAwareQuery { 11 12 private $userPHIDs; 13 private $keys; 14 private $unread; 15 16 17/* -( Configuring the Query )---------------------------------------------- */ 18 19 20 public function withUserPHIDs(array $user_phids) { 21 $this->userPHIDs = $user_phids; 22 return $this; 23 } 24 25 public function withKeys(array $keys) { 26 $this->keys = $keys; 27 return $this; 28 } 29 30 31 /** 32 * Filter results by read/unread status. Note that `true` means to return 33 * only unread notifications, while `false` means to return only //read// 34 * notifications. The default is `null`, which returns both. 35 * 36 * @param mixed $unread True or false to filter results by read status. Null 37 * to remove the filter. 38 * @return $this 39 * @task config 40 */ 41 public function withUnread($unread) { 42 $this->unread = $unread; 43 return $this; 44 } 45 46 47/* -( Query Execution )---------------------------------------------------- */ 48 49 50 protected function loadPage() { 51 $story_table = new PhabricatorFeedStoryData(); 52 $notification_table = new PhabricatorFeedStoryNotification(); 53 54 $conn = $story_table->establishConnection('r'); 55 56 $data = queryfx_all( 57 $conn, 58 'SELECT story.*, notification.hasViewed FROM %R notification 59 JOIN %R story ON notification.chronologicalKey = story.chronologicalKey 60 %Q 61 ORDER BY notification.chronologicalKey DESC 62 %Q', 63 $notification_table, 64 $story_table, 65 $this->buildWhereClause($conn), 66 $this->buildLimitClause($conn)); 67 68 return $data; 69 } 70 71 protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 72 $where = parent::buildWhereClauseParts($conn); 73 74 if ($this->userPHIDs !== null) { 75 $where[] = qsprintf( 76 $conn, 77 'notification.userPHID IN (%Ls)', 78 $this->userPHIDs); 79 } 80 81 if ($this->unread !== null) { 82 $where[] = qsprintf( 83 $conn, 84 'notification.hasViewed = %d', 85 (int)!$this->unread); 86 } 87 88 if ($this->keys !== null) { 89 $where[] = qsprintf( 90 $conn, 91 'notification.chronologicalKey IN (%Ls)', 92 $this->keys); 93 } 94 95 return $where; 96 } 97 98 protected function willFilterPage(array $rows) { 99 // See T13623. The policy model here is outdated and awkward. 100 101 // Users may have notifications about objects they can no longer see. 102 // Two ways this can arise: destroy an object; or change an object's 103 // view policy to exclude a user. 104 105 // "PhabricatorFeedStory::loadAllFromRows()" does its own policy filtering. 106 // This doesn't align well with modern query sequencing, but we should be 107 // able to get away with it by loading here. 108 109 // See T13623. Although most queries for notifications return unique 110 // stories, this isn't a guarantee. 111 $story_map = ipull($rows, null, 'chronologicalKey'); 112 113 $viewer = $this->getViewer(); 114 $stories = PhabricatorFeedStory::loadAllFromRows($story_map, $viewer); 115 $stories = mpull($stories, null, 'getChronologicalKey'); 116 117 $results = array(); 118 foreach ($rows as $row) { 119 $story_key = $row['chronologicalKey']; 120 $has_viewed = $row['hasViewed']; 121 122 if (!isset($stories[$story_key])) { 123 // NOTE: We can't call "didRejectResult()" here because we don't have 124 // a policy object to pass. 125 continue; 126 } 127 128 $story = id(clone $stories[$story_key]) 129 ->setHasViewed($has_viewed); 130 131 if (!$story->isVisibleInNotifications()) { 132 continue; 133 } 134 135 $results[] = $story; 136 } 137 138 return $results; 139 } 140 141 protected function getDefaultOrderVector() { 142 return array('key'); 143 } 144 145 public function getBuiltinOrders() { 146 return array( 147 'newest' => array( 148 'vector' => array('key'), 149 'name' => pht('Creation (Newest First)'), 150 'aliases' => array('created'), 151 ), 152 'oldest' => array( 153 'vector' => array('-key'), 154 'name' => pht('Creation (Oldest First)'), 155 ), 156 ); 157 } 158 159 public function getOrderableColumns() { 160 return array( 161 'key' => array( 162 'table' => 'notification', 163 'column' => 'chronologicalKey', 164 'type' => 'string', 165 'unique' => true, 166 ), 167 ); 168 } 169 170 protected function applyExternalCursorConstraintsToQuery( 171 PhabricatorCursorPagedPolicyAwareQuery $subquery, 172 $cursor) { 173 174 $subquery 175 ->withKeys(array($cursor)) 176 ->setLimit(1); 177 178 } 179 180 protected function newExternalCursorStringForResult($object) { 181 return $object->getChronologicalKey(); 182 } 183 184 protected function newPagingMapFromPartialObject($object) { 185 return array( 186 'key' => $object['chronologicalKey'], 187 ); 188 } 189 190 protected function getPrimaryTableAlias() { 191 return 'notification'; 192 } 193 194 public function getQueryApplicationClass() { 195 return PhabricatorNotificationsApplication::class; 196 } 197 198}