@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 335 lines 9.0 kB view raw
1<?php 2 3/** 4 * @extends PhabricatorCursorPagedPolicyAwareQuery<PhrequentUserTime> 5 */ 6final class PhrequentUserTimeQuery 7 extends PhabricatorCursorPagedPolicyAwareQuery { 8 9 const ORDER_ID_ASC = 0; 10 const ORDER_ID_DESC = 1; 11 const ORDER_STARTED_ASC = 2; 12 const ORDER_STARTED_DESC = 3; 13 const ORDER_ENDED_ASC = 4; 14 const ORDER_ENDED_DESC = 5; 15 16 const ENDED_YES = 0; 17 const ENDED_NO = 1; 18 const ENDED_ALL = 2; 19 20 private $ids; 21 private $userPHIDs; 22 private $objectPHIDs; 23 private $ended = self::ENDED_ALL; 24 25 private $needPreemptingEvents; 26 27 public function withIDs(array $ids) { 28 $this->ids = $ids; 29 return $this; 30 } 31 32 public function withUserPHIDs(array $user_phids) { 33 $this->userPHIDs = $user_phids; 34 return $this; 35 } 36 37 public function withObjectPHIDs(array $object_phids) { 38 $this->objectPHIDs = $object_phids; 39 return $this; 40 } 41 42 public function withEnded($ended) { 43 $this->ended = $ended; 44 return $this; 45 } 46 47 public function setOrder($order) { 48 switch ($order) { 49 case self::ORDER_ID_ASC: 50 $this->setOrderVector(array('-id')); 51 break; 52 case self::ORDER_ID_DESC: 53 $this->setOrderVector(array('id')); 54 break; 55 case self::ORDER_STARTED_ASC: 56 $this->setOrderVector(array('-start', '-id')); 57 break; 58 case self::ORDER_STARTED_DESC: 59 $this->setOrderVector(array('start', 'id')); 60 break; 61 case self::ORDER_ENDED_ASC: 62 $this->setOrderVector(array('-end', '-id')); 63 break; 64 case self::ORDER_ENDED_DESC: 65 $this->setOrderVector(array('end', 'id')); 66 break; 67 default: 68 throw new Exception(pht('Unknown order "%s".', $order)); 69 } 70 71 return $this; 72 } 73 74 public function needPreemptingEvents($need_events) { 75 $this->needPreemptingEvents = $need_events; 76 return $this; 77 } 78 79 protected function buildWhereClause(AphrontDatabaseConnection $conn) { 80 $where = array(); 81 82 if ($this->ids !== null) { 83 $where[] = qsprintf( 84 $conn, 85 'id IN (%Ld)', 86 $this->ids); 87 } 88 89 if ($this->userPHIDs !== null) { 90 $where[] = qsprintf( 91 $conn, 92 'userPHID IN (%Ls)', 93 $this->userPHIDs); 94 } 95 96 if ($this->objectPHIDs !== null) { 97 $where[] = qsprintf( 98 $conn, 99 'objectPHID IN (%Ls)', 100 $this->objectPHIDs); 101 } 102 103 switch ($this->ended) { 104 case self::ENDED_ALL: 105 break; 106 case self::ENDED_YES: 107 $where[] = qsprintf( 108 $conn, 109 'dateEnded IS NOT NULL'); 110 break; 111 case self::ENDED_NO: 112 $where[] = qsprintf( 113 $conn, 114 'dateEnded IS NULL'); 115 break; 116 default: 117 throw new Exception(pht("Unknown ended '%s'!", $this->ended)); 118 } 119 120 $where[] = $this->buildPagingClause($conn); 121 122 return $this->formatWhereClause($conn, $where); 123 } 124 125 public function getOrderableColumns() { 126 return parent::getOrderableColumns() + array( 127 'start' => array( 128 'column' => 'dateStarted', 129 'type' => 'int', 130 ), 131 'end' => array( 132 'column' => 'dateEnded', 133 'type' => 'int', 134 'null' => 'head', 135 ), 136 ); 137 } 138 139 protected function newPagingMapFromPartialObject($object) { 140 return array( 141 'id' => (int)$object->getID(), 142 'start' => (int)$object->getDateStarted(), 143 'end' => (int)$object->getDateEnded(), 144 ); 145 } 146 147 protected function loadPage() { 148 $usertime = new PhrequentUserTime(); 149 $conn = $usertime->establishConnection('r'); 150 151 $data = queryfx_all( 152 $conn, 153 'SELECT usertime.* FROM %T usertime %Q %Q %Q', 154 $usertime->getTableName(), 155 $this->buildWhereClause($conn), 156 $this->buildOrderClause($conn), 157 $this->buildLimitClause($conn)); 158 159 return $usertime->loadAllFromArray($data); 160 } 161 162 protected function didFilterPage(array $page) { 163 if ($this->needPreemptingEvents) { 164 $usertime = new PhrequentUserTime(); 165 $conn_r = $usertime->establishConnection('r'); 166 167 $preempt = array(); 168 foreach ($page as $event) { 169 $preempt[] = qsprintf( 170 $conn_r, 171 '(userPHID = %s AND 172 (dateStarted BETWEEN %d AND %d) AND 173 (dateEnded IS NULL OR dateEnded > %d))', 174 $event->getUserPHID(), 175 $event->getDateStarted(), 176 nonempty($event->getDateEnded(), PhabricatorTime::getNow()), 177 $event->getDateStarted()); 178 } 179 180 $preempting_events = queryfx_all( 181 $conn_r, 182 'SELECT * FROM %T WHERE %LO ORDER BY dateStarted ASC, id ASC', 183 $usertime->getTableName(), 184 $preempt); 185 $preempting_events = $usertime->loadAllFromArray($preempting_events); 186 187 $preempting_events = mgroup($preempting_events, 'getUserPHID'); 188 189 foreach ($page as $event) { 190 $e_start = $event->getDateStarted(); 191 $e_end = $event->getDateEnded(); 192 193 $select = array(); 194 $user_events = idx($preempting_events, $event->getUserPHID(), array()); 195 foreach ($user_events as $u_event) { 196 if ($u_event->getID() == $event->getID()) { 197 // Don't allow an event to preempt itself. 198 continue; 199 } 200 201 $u_start = $u_event->getDateStarted(); 202 $u_end = $u_event->getDateEnded(); 203 204 if ($u_start < $e_start) { 205 // This event started before our event started, so it's not 206 // preempting us. 207 continue; 208 } 209 210 if ($u_start == $e_start) { 211 if ($u_event->getID() < $event->getID()) { 212 // This event started at the same time as our event started, 213 // but has a lower ID, so it's not preempting us. 214 continue; 215 } 216 } 217 218 if (($e_end !== null) && ($u_start > $e_end)) { 219 // Our event has ended, and this event started after it ended. 220 continue; 221 } 222 223 if (($u_end !== null) && ($u_end < $e_start)) { 224 // This event ended before our event began. 225 continue; 226 } 227 228 $select[] = $u_event; 229 } 230 231 $event->attachPreemptingEvents($select); 232 } 233 } 234 235 return $page; 236 } 237 238/* -( Helper Functions ) --------------------------------------------------- */ 239 240 public static function getEndedSearchOptions() { 241 return array( 242 self::ENDED_ALL => pht('All'), 243 self::ENDED_NO => pht('No'), 244 self::ENDED_YES => pht('Yes'), 245 ); 246 } 247 248 public static function getOrderSearchOptions() { 249 return array( 250 self::ORDER_STARTED_ASC => pht('by furthest start date'), 251 self::ORDER_STARTED_DESC => pht('by nearest start date'), 252 self::ORDER_ENDED_ASC => pht('by furthest end date'), 253 self::ORDER_ENDED_DESC => pht('by nearest end date'), 254 ); 255 } 256 257 public static function getUserTotalObjectsTracked( 258 PhabricatorUser $user, 259 $limit = PHP_INT_MAX) { 260 261 $usertime_dao = new PhrequentUserTime(); 262 $conn = $usertime_dao->establishConnection('r'); 263 264 $count = queryfx_one( 265 $conn, 266 'SELECT COUNT(usertime.id) N FROM %T usertime '. 267 'WHERE usertime.userPHID = %s '. 268 'AND usertime.dateEnded IS NULL '. 269 'LIMIT %d', 270 $usertime_dao->getTableName(), 271 $user->getPHID(), 272 $limit); 273 return $count['N']; 274 } 275 276 public static function isUserTrackingObject( 277 PhabricatorUser $user, 278 $phid) { 279 280 $usertime_dao = new PhrequentUserTime(); 281 $conn = $usertime_dao->establishConnection('r'); 282 283 $count = queryfx_one( 284 $conn, 285 'SELECT COUNT(usertime.id) N FROM %T usertime '. 286 'WHERE usertime.userPHID = %s '. 287 'AND usertime.objectPHID = %s '. 288 'AND usertime.dateEnded IS NULL', 289 $usertime_dao->getTableName(), 290 $user->getPHID(), 291 $phid); 292 return $count['N'] > 0; 293 } 294 295 public static function getUserTimeSpentOnObject( 296 PhabricatorUser $user, 297 $phid) { 298 299 $usertime_dao = new PhrequentUserTime(); 300 $conn = $usertime_dao->establishConnection('r'); 301 302 // First calculate all the time spent where the 303 // usertime blocks have ended. 304 $sum_ended = queryfx_one( 305 $conn, 306 'SELECT SUM(usertime.dateEnded - usertime.dateStarted) N '. 307 'FROM %T usertime '. 308 'WHERE usertime.userPHID = %s '. 309 'AND usertime.objectPHID = %s '. 310 'AND usertime.dateEnded IS NOT NULL', 311 $usertime_dao->getTableName(), 312 $user->getPHID(), 313 $phid); 314 315 // Now calculate the time spent where the usertime 316 // blocks have not yet ended. 317 $sum_not_ended = queryfx_one( 318 $conn, 319 'SELECT SUM(UNIX_TIMESTAMP() - usertime.dateStarted) N '. 320 'FROM %T usertime '. 321 'WHERE usertime.userPHID = %s '. 322 'AND usertime.objectPHID = %s '. 323 'AND usertime.dateEnded IS NULL', 324 $usertime_dao->getTableName(), 325 $user->getPHID(), 326 $phid); 327 328 return $sum_ended['N'] + $sum_not_ended['N']; 329 } 330 331 public function getQueryApplicationClass() { 332 return PhabricatorPhrequentApplication::class; 333 } 334 335}