@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Adds base notification application

Summary: First diff in a series of diffs to add notifications to Phabricator. This is the notification application ONLY. This commit does not include the changes to other applications that makes them add notifications. As such, no notifications will be generated beyond the initial database import.

Test Plan: This is part of the notifications architecture which has been running on http://theoryphabricator.com for the past several months.

Reviewers: epriestley, btrahan, ddfisher

Reviewed By: epriestley

CC: allenjohnashton, keebuhm, aran, Korvin, jungejason, nh

Maniphest Tasks: T974

Differential Revision: https://secure.phabricator.com/D2571

authored by

John-Ashton Allen and committed by
epriestley
3a6ee791 692296a4

+536 -45
+2
conf/default.conf.php
··· 179 179 // extend AphrontMySQLDatabaseConnectionBase. 180 180 'mysql.implementation' => 'AphrontMySQLDatabaseConnection', 181 181 182 + // -- Notifications ----// 183 + 'notification.enabled' => false, 182 184 183 185 // -- Email ----------------------------------------------------------------- // 184 186
+8
resources/sql/patches/temp.notifications.sql
··· 1 + CREATE TABLE if not exists phabricator_feed.feed_storynotification ( 2 + userPHID varchar(64) not null collate utf8_bin, 3 + primaryObjectPHID varchar(64) not null collate utf8_bin, 4 + chronologicalKey BIGINT UNSIGNED NOT NULL, 5 + hasViewed boolean not null, 6 + UNIQUE KEY (userPHID, chronologicalKey), 7 + KEY (userPHID, hasViewed, primaryObjectPHID) 8 + );
+24
src/__phutil_library_map__.php
··· 525 525 'ManiphestView' => 'applications/maniphest/view/ManiphestView.php', 526 526 'MetaMTAConstants' => 'applications/metamta/constants/MetaMTAConstants.php', 527 527 'MetaMTANotificationType' => 'applications/metamta/constants/MetaMTANotificationType.php', 528 + 'NotificationMessage' => 'applications/notification/constants/message/NotificationMessage.php', 529 + 'NotificationPathname' => 'applications/notification/constants/pathname/NotificationPathname.php', 530 + 'NotificationType' => 'applications/notification/constants/type/NotificationType.php', 528 531 'OwnersPackageReplyHandler' => 'applications/owners/OwnersPackageReplyHandler.php', 529 532 'PackageCreateMail' => 'applications/owners/mail/PackageCreateMail.php', 530 533 'PackageDeleteMail' => 'applications/owners/mail/PackageDeleteMail.php', ··· 631 634 'PhabricatorFeedStoryData' => 'applications/feed/storage/PhabricatorFeedStoryData.php', 632 635 'PhabricatorFeedStoryDifferential' => 'applications/feed/story/PhabricatorFeedStoryDifferential.php', 633 636 'PhabricatorFeedStoryManiphest' => 'applications/feed/story/PhabricatorFeedStoryManiphest.php', 637 + 'PhabricatorFeedStoryNotification' => 'applications/notification/storage/PhabricatorFeedStoryNotification.php', 634 638 'PhabricatorFeedStoryPhriction' => 'applications/feed/story/PhabricatorFeedStoryPhriction.php', 635 639 'PhabricatorFeedStoryProject' => 'applications/feed/story/PhabricatorFeedStoryProject.php', 636 640 'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php', ··· 733 737 'PhabricatorMetaMTAWorker' => 'applications/metamta/PhabricatorMetaMTAWorker.php', 734 738 'PhabricatorMustVerifyEmailController' => 'applications/auth/controller/PhabricatorMustVerifyEmailController.php', 735 739 'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php', 740 + 'PhabricatorNotificationBuilder' => 'applications/notification/builder/notification/PhabricatorNotificationBuilder.php', 741 + 'PhabricatorNotificationConstants' => 'applications/notification/constants/base/PhabricatorNotificationConstants.php', 742 + 'PhabricatorNotificationController' => 'applications/notification/controller/base/PhabricatorNotificationController.php', 743 + 'PhabricatorNotificationPanelController' => 'applications/notification/controller/panel/PhabricatorNotificationPanelController.php', 744 + 'PhabricatorNotificationQuery' => 'applications/notification/PhabricatorNotificationQuery.php', 745 + 'PhabricatorNotificationStory' => 'applications/notification/base/PhabricatorNotificationStory.php', 746 + 'PhabricatorNotificationStoryTypeConstants' => 'applications/notification/constants/story/PhabricatorNotificationStoryTypeConstants.php', 747 + 'PhabricatorNotificationStoryView' => 'applications/notification/view/story/PhabricatorNotificationStoryView.php', 748 + 'PhabricatorNotificationTestController' => 'applications/notification/controller/test/PhabricatorNotificationTestController.php', 749 + 'PhabricatorNotificationView' => 'applications/notification/view/base/PhabricatorNotificationView.php', 736 750 'PhabricatorNotificationsController' => 'applications/notifications/controller/PhabricatorNotificationsController.php', 737 751 'PhabricatorOAuthClientAuthorization' => 'applications/oauthserver/storage/PhabricatorOAuthClientAuthorization.php', 738 752 'PhabricatorOAuthClientAuthorizationBaseController' => 'applications/oauthserver/controller/clientauthorization/PhabricatorOAuthClientAuthorizationBaseController.php', ··· 1511 1525 'ManiphestTransactionType' => 'ManiphestConstants', 1512 1526 'ManiphestView' => 'AphrontView', 1513 1527 'MetaMTANotificationType' => 'MetaMTAConstants', 1528 + 'NotificationMessage' => 'PhabricatorNotificationConstants', 1529 + 'NotificationPathname' => 'PhabricatorNotificationConstants', 1530 + 'NotificationType' => 'PhabricatorNotificationConstants', 1514 1531 'OwnersPackageReplyHandler' => 'PhabricatorMailReplyHandler', 1515 1532 'PackageCreateMail' => 'PackageMail', 1516 1533 'PackageDeleteMail' => 'PackageMail', ··· 1604 1621 'PhabricatorFeedStoryData' => 'PhabricatorFeedDAO', 1605 1622 'PhabricatorFeedStoryDifferential' => 'PhabricatorFeedStory', 1606 1623 'PhabricatorFeedStoryManiphest' => 'PhabricatorFeedStory', 1624 + 'PhabricatorFeedStoryNotification' => 'PhabricatorFeedDAO', 1607 1625 'PhabricatorFeedStoryPhriction' => 'PhabricatorFeedStory', 1608 1626 'PhabricatorFeedStoryProject' => 'PhabricatorFeedStory', 1609 1627 'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO', ··· 1688 1706 'PhabricatorMetaMTAWorker' => 'PhabricatorWorker', 1689 1707 'PhabricatorMustVerifyEmailController' => 'PhabricatorAuthController', 1690 1708 'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine', 1709 + 'PhabricatorNotificationController' => 'PhabricatorController', 1710 + 'PhabricatorNotificationPanelController' => 'PhabricatorNotificationController', 1711 + 'PhabricatorNotificationStoryTypeConstants' => 'PhabricatorNotificationConstants', 1712 + 'PhabricatorNotificationStoryView' => 'PhabricatorNotificationView', 1713 + 'PhabricatorNotificationTestController' => 'PhabricatorNotificationController', 1714 + 'PhabricatorNotificationView' => 'AphrontView', 1691 1715 'PhabricatorNotificationsController' => 'PhabricatorController', 1692 1716 'PhabricatorOAuthClientAuthorization' => 'PhabricatorOAuthServerDAO', 1693 1717 'PhabricatorOAuthClientAuthorizationBaseController' => 'PhabricatorOAuthServerController',
+1 -2
src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
··· 421 421 'PhabricatorChatLogChannelLogController', 422 422 ), 423 423 424 - '/aphlict/' => 'PhabricatorAphlictTestPageController', 425 - 424 + '/notification/test/' => 'PhabricatorNotificationTestController', 426 425 '/flag/' => array( 427 426 '' => 'PhabricatorFlagListController', 428 427 'view/(?P<view>[^/]+)/' => 'PhabricatorFlagListController',
+46 -1
src/applications/feed/PhabricatorFeedStoryPublisher.php
··· 1 1 <?php 2 2 3 3 /* 4 - * Copyright 2011 Facebook, Inc. 4 + * Copyright 2012 Facebook, Inc. 5 5 * 6 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 7 * you may not use this file except in compliance with the License. ··· 23 23 private $storyData; 24 24 private $storyTime; 25 25 private $storyAuthorPHID; 26 + private $primaryObjectPHID; 27 + private $subscribedPHIDs; 26 28 27 29 public function setRelatedPHIDs(array $phids) { 28 30 $this->relatedPHIDs = $phids; 31 + return $this; 32 + } 33 + 34 + public function setSubscribedPHIDs(array $phids) { 35 + $this->subscribedPHIDs = $phids; 36 + return $this; 37 + } 38 + public function setPrimaryObjectPHID($phid) { 39 + $this->primaryObjectPHID = $phid; 29 40 return $this; 30 41 } 31 42 ··· 85 96 $ref->getTableName(), 86 97 implode(', ', $sql)); 87 98 99 + if (PhabricatorEnv::getEnvConfig('notification.enabled')) { 100 + $this->insertNotifications($chrono_key); 101 + } 88 102 return $story; 89 103 } 90 104 105 + 106 + private function insertNotifications($chrono_key) { 107 + if (!$this->primaryObjectPHID) { 108 + throw 109 + new Exception("Call setPrimaryObjectPHID() before Publishing!"); 110 + } 111 + if ($this->subscribedPHIDs) { 112 + $notif = new PhabricatorFeedStoryNotification(); 113 + $sql = array(); 114 + $conn = $notif->establishConnection('w'); 115 + 116 + foreach (array_unique($this->subscribedPHIDs) as $user_phid) { 117 + $sql[] = qsprintf( 118 + $conn, 119 + '(%s, %s, %s, %d)', 120 + $this->primaryObjectPHID, 121 + $user_phid, 122 + $chrono_key, 123 + 0); 124 + } 125 + 126 + queryfx( 127 + $conn, 128 + 'INSERT INTO %T 129 + (primaryObjectPHID, userPHID, chronologicalKey, hasViewed) 130 + VALUES %Q', 131 + $notif->getTableName(), 132 + implode(', ', $sql)); 133 + } 134 + 135 + } 91 136 /** 92 137 * We generate a unique chronological key for each story type because we want 93 138 * to be able to page through the stream with a cursor (i.e., select stories
+10 -1
src/applications/feed/story/PhabricatorFeedStory.php
··· 19 19 abstract class PhabricatorFeedStory { 20 20 21 21 private $data; 22 - 22 + private $hasViewed; 23 23 private $handles; 24 24 private $framed; 25 25 ··· 31 31 32 32 public function getRequiredHandlePHIDs() { 33 33 return array(); 34 + } 35 + 36 + public function setHasViewed($has_viewed) { 37 + $this->hasViewed = $has_viewed; 38 + return $this; 39 + } 40 + 41 + public function getHasViewed() { 42 + return $this->hasViewed; 34 43 } 35 44 36 45 public function getRequiredObjectPHIDs() {
+58 -40
src/applications/feed/story/PhabricatorFeedStoryManiphest.php
··· 16 16 * limitations under the License. 17 17 */ 18 18 19 - final class PhabricatorFeedStoryManiphest extends PhabricatorFeedStory { 19 + final class PhabricatorFeedStoryManiphest 20 + extends PhabricatorFeedStory { 20 21 21 22 public function getRequiredHandlePHIDs() { 22 23 $data = $this->getStoryData(); 23 24 return array_filter( 24 - array( 25 + array( 25 26 $this->getStoryData()->getAuthorPHID(), 26 27 $data->getValue('taskPHID'), 27 28 $data->getValue('ownerPHID'), 28 - )); 29 + )); 29 30 } 30 31 31 32 public function getRequiredObjectPHIDs() { 32 33 return array( 33 34 $this->getStoryData()->getAuthorPHID(), 34 - ); 35 + ); 35 36 } 36 37 37 38 public function renderView() { 38 39 $data = $this->getStoryData(); 39 40 40 - $author_phid = $data->getAuthorPHID(); 41 - $owner_phid = $data->getValue('ownerPHID'); 42 - $task_phid = $data->getValue('taskPHID'); 43 - 44 - $action = $data->getValue('action'); 45 - 46 41 $view = new PhabricatorFeedStoryView(); 47 42 48 - $verb = ManiphestAction::getActionPastTenseVerb($action); 49 - $extra = null; 50 - switch ($action) { 51 - case ManiphestAction::ACTION_ASSIGN: 52 - if ($owner_phid) { 53 - $extra = 54 - ' to '. 55 - $this->linkTo($owner_phid); 56 - } else { 57 - $verb = 'placed'; 58 - $extra = ' up for grabs'; 59 - } 60 - break; 61 - } 43 + $view->setEpoch($data->getEpoch()); 62 44 63 - $title = 64 - $this->linkTo($author_phid). 65 - " {$verb} task ". 66 - $this->linkTo($task_phid); 67 - $title .= $extra; 68 - $title .= '.'; 45 + $action = $this->getLineForData($data); 46 + $view->setTitle($action); 47 + $view->setEpoch($data->getEpoch()); 69 48 70 - $view->setTitle($title); 71 49 72 50 switch ($action) { 73 - case ManiphestAction::ACTION_CREATE: 74 - $full_size = true; 75 - break; 76 - default: 77 - $full_size = false; 78 - break; 51 + case ManiphestAction::ACTION_CREATE: 52 + $full_size = true; 53 + break; 54 + default: 55 + $full_size = false; 56 + break; 79 57 } 80 58 81 - $view->setEpoch($data->getEpoch()); 82 - 83 59 if ($full_size) { 84 - $view->setImage($this->getHandle($author_phid)->getImageURI()); 60 + $view->setImage($this->getHandle($this->getAuthorPHID())->getImageURI()); 85 61 $content = $this->renderSummary($data->getValue('description')); 86 62 $view->appendChild($content); 87 63 } else { ··· 91 67 return $view; 92 68 } 93 69 70 + 71 + public function renderNotificationView() { 72 + $data = $this->getStoryData(); 73 + 74 + $view = new PhabricatorNotificationStoryView(); 75 + 76 + $view->setEpoch($data->getEpoch()); 77 + $view->setTitle($this->getLineForData($data)); 78 + $view->setEpoch($data->getEpoch()); 79 + $view->setViewed($this->getHasViewed()); 80 + 81 + return $view; 82 + } 83 + 84 + private function getLineForData($data) { 85 + $actor_phid = $data->getAuthorPHID(); 86 + $owner_phid = $data->getValue('ownerPHID'); 87 + $task_phid = $data->getValue('taskPHID'); 88 + $action = $data->getValue('action'); 89 + $description = $data->getValue('description'); 90 + $comments = phutil_escape_html( 91 + phutil_utf8_shorten( 92 + $data->getValue('comments'), 93 + 140)); 94 + 95 + $actor_link = $this->linkTo($actor_phid); 96 + $task_link = $this->linkTo($task_phid); 97 + $owner_link = $this->linkTo($owner_phid); 98 + $verb = ManiphestAction::getActionPastTenseVerb($action); 99 + 100 + $one_line = "{$actor_link} {$verb} {$task_link}"; 101 + 102 + switch ($action) { 103 + case ManiphestAction::ACTION_ASSIGN: 104 + $one_line .= " to {$owner_link}"; 105 + break; 106 + default: 107 + break; 108 + } 109 + 110 + return $one_line; 111 + } 94 112 }
+3
src/applications/maniphest/controller/ManiphestTaskDetailController.php
··· 510 510 $transaction_view->setAuxiliaryFields($aux_fields); 511 511 $transaction_view->setMarkupEngine($engine); 512 512 513 + PhabricatorFeedStoryNotification::updateObjectNotificationViews( 514 + $user, $task->getPHID()); 515 + 513 516 return $this->buildStandardPageResponse( 514 517 array( 515 518 $context_bar,
+16 -1
src/applications/maniphest/editor/ManiphestTransactionEditor.php
··· 314 314 if ($transaction->hasComments()) { 315 315 $comments = $transaction->getComments(); 316 316 } 317 - switch ($transaction->getTransactionType()) { 317 + $type = $transaction->getTransactionType(); 318 + switch ($type) { 318 319 case ManiphestTransactionType::TYPE_OWNER: 319 320 $actions[] = ManiphestAction::ACTION_ASSIGN; 320 321 break; ··· 323 324 $actions[] = ManiphestAction::ACTION_CLOSE; 324 325 } else if ($this->isCreate($transactions)) { 325 326 $actions[] = ManiphestAction::ACTION_CREATE; 327 + } else { 328 + $actions[] = ManiphestAction::ACTION_REOPEN; 326 329 } 327 330 break; 328 331 default: ··· 335 338 $actor_phid = head($transactions)->getAuthorPHID(); 336 339 $author_phid = $task->getAuthorPHID(); 337 340 341 + 338 342 id(new PhabricatorFeedStoryPublisher()) 339 343 ->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_MANIPHEST) 340 344 ->setStoryData(array( ··· 357 361 $owner_phid, 358 362 )), 359 363 $task->getProjectPHIDs())) 364 + ->setPrimaryObjectPHID($task->getPHID()) 365 + ->setSubscribedPHIDs( 366 + array_merge( 367 + array_filter( 368 + array( 369 + $author_phid, 370 + $owner_phid, 371 + $actor_phid 372 + ) 373 + ), 374 + $task->getCCPHIDs())) 360 375 ->publish(); 361 376 } 362 377
+81
src/applications/notification/PhabricatorNotificationQuery.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorNotificationQuery { 20 + 21 + private $limit = 100; 22 + private $userPHID; 23 + 24 + 25 + public function setLimit($limit) { 26 + $this->limit = $limit; 27 + return $this; 28 + } 29 + 30 + public function setUserPHID($user_phid) { 31 + $this->userPHID = $user_phid; 32 + return $this; 33 + } 34 + 35 + 36 + public function execute() { 37 + if (!$this->userPHID) { 38 + throw new Exception("Call setUser() before executing the query"); 39 + } 40 + 41 + //TODO throw an exception if no user 42 + $story_table = new PhabricatorFeedStoryData(); 43 + $notification_table = new PhabricatorFeedStoryNotification(); 44 + 45 + $conn = $story_table->establishConnection('r'); 46 + 47 + $data = queryfx_all( 48 + $conn, 49 + "SELECT story.*, notif.hasViewed FROM %T notif 50 + JOIN %T story ON notif.chronologicalKey = story.chronologicalKey 51 + WHERE notif.userPHID = %s 52 + ORDER BY notif.chronologicalKey desc 53 + LIMIT %d", 54 + $notification_table->getTableName(), 55 + $story_table->getTableName(), 56 + $this->userPHID, 57 + $this->limit); 58 + 59 + $viewed_map = ipull($data, 'hasViewed', 'chronologicalKey'); 60 + $data = $story_table->loadAllFromArray($data); 61 + 62 + $stories = array(); 63 + 64 + foreach ($data as $story_data) { 65 + $class = $story_data->getStoryType(); 66 + try { 67 + if (!class_exists($class) || 68 + !is_subclass_of($class, 'PhabricatorFeedStory')) { 69 + $class = 'PhabricatorFeedStoryUnknown'; 70 + } 71 + } catch (PhutilMissingSymbolException $ex) { 72 + $class = 'PhabricatorFeedStoryUnknown'; 73 + } 74 + $story = newv($class, array($story_data)); 75 + $story->setHasViewed($viewed_map[$story->getChronologicalKey()]); 76 + $stories[] = $story; 77 + } 78 + 79 + return $stories; 80 + } 81 + }
+54
src/applications/notification/builder/notification/PhabricatorNotificationBuilder.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorNotificationBuilder { 20 + 21 + private $stories; 22 + 23 + public function __construct(array $stories) { 24 + $this->stories = $stories; 25 + } 26 + 27 + public function buildView() { 28 + 29 + $stories = $this->stories; 30 + 31 + $handles = array(); 32 + $objects = array(); 33 + 34 + if ($stories) { 35 + $handle_phids = array_mergev(mpull($stories, 'getRequiredHandlePHIDs')); 36 + $handles = id(new PhabricatorObjectHandleData($handle_phids)) 37 + ->loadHandles(); 38 + } 39 + 40 + $null_view = new AphrontNullView(); 41 + 42 + foreach ($stories as $story) { 43 + $story->setHandles($handles); 44 + $view = $story->renderNotificationView(); 45 + $null_view->appendChild($view); 46 + } 47 + 48 + return id(new AphrontNullView())->appendChild( 49 + '<div class="phabricator-notification-frame">'. 50 + $null_view->render(). 51 + '</div>'); 52 + 53 + } 54 + }
+37
src/applications/notification/controller/base/PhabricatorNotificationController.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + abstract class PhabricatorNotificationController 20 + extends PhabricatorController { 21 + 22 + public function buildStandardPageResponse($view, array $data) { 23 + 24 + $page = $this->buildStandardPageView(); 25 + 26 + $page->setApplicationName('Notification'); 27 + $page->setBaseURI('/notification/'); 28 + $page->setTitle(idx($data, 'title')); 29 + $page->setGlyph('!'); 30 + $page->appendChild($view); 31 + 32 + $response = new AphrontWebpageResponse(); 33 + return $response->setContent($page->render()); 34 + 35 + } 36 + 37 + }
+53
src/applications/notification/controller/test/PhabricatorNotificationTestController.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorNotificationTestController 20 + extends PhabricatorNotificationController { 21 + 22 + public function processRequest() { 23 + 24 + $request = $this->getRequest(); 25 + $user = $request->getUser(); 26 + 27 + $query = new PhabricatorNotificationQuery(); 28 + $query->setUserPHID($user->getPHID()); 29 + 30 + $stories = $query->execute(); 31 + 32 + $builder = new PhabricatorNotificationBuilder($stories); 33 + $notifications_view = $builder->buildView(); 34 + 35 + $num_unconsumed = 0; 36 + 37 + foreach ($stories as $story) { 38 + if (!$story->getHasViewed()) { 39 + $num_unconsumed++; 40 + } 41 + 42 + } 43 + 44 + $json = array( 45 + $notifications_view->render() 46 + ); 47 + 48 + 49 + return $this->buildStandardPageResponse( 50 + $json, 51 + array('title' => 'Notification Test Page')); 52 + } 53 + }
+57
src/applications/notification/storage/PhabricatorFeedStoryNotification.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorFeedStoryNotification extends PhabricatorFeedDAO { 20 + 21 + protected $userPHID; 22 + protected $primaryObjectPHID; 23 + protected $chronologicalKey; 24 + protected $hasViewed; 25 + 26 + public function getConfiguration() { 27 + return array( 28 + self::CONFIG_IDS => self::IDS_MANUAL, 29 + self::CONFIG_TIMESTAMPS => false, 30 + ) + parent::getConfiguration(); 31 + } 32 + 33 + static public function updateObjectNotificationViews(PhabricatorUser $user, 34 + $object_phid) { 35 + 36 + if (PhabricatorEnv::getEnvConfig('notification.enabled')) { 37 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 38 + 39 + $notification_table = new PhabricatorFeedStoryNotification(); 40 + $conn = $notification_table->establishConnection('w'); 41 + 42 + queryfx( 43 + $conn, 44 + "UPDATE %T 45 + SET hasViewed = 1 46 + WHERE userPHID = %s 47 + AND primaryObjectPHID = %s 48 + AND hasViewed = 0", 49 + $notification_table->getTableName(), 50 + $user->getPHID(), 51 + $object_phid); 52 + 53 + unset($unguarded); 54 + } 55 + } 56 + 57 + }
+21
src/applications/notification/view/base/PhabricatorNotificationView.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + abstract class PhabricatorNotificationView extends AphrontView { 20 + 21 + }
+65
src/applications/notification/view/story/PhabricatorNotificationStoryView.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorNotificationStoryView 20 + extends PhabricatorNotificationView { 21 + 22 + private $title; 23 + private $phid; 24 + private $epoch; 25 + private $viewed; 26 + 27 + public function setTitle($title) { 28 + $this->title = $title; 29 + return $this; 30 + } 31 + 32 + public function setEpoch($epoch) { 33 + $this->epoch = $epoch; 34 + return $this; 35 + } 36 + 37 + public function setViewed($viewed) { 38 + $this->viewed = $viewed; 39 + } 40 + 41 + public function render() { 42 + 43 + $title = $this->title; 44 + if (!$this->viewed) { 45 + $title = '<b>'.$title.'</b>'; 46 + } 47 + 48 + $head = phutil_render_tag( 49 + 'div', 50 + array( 51 + 'class' => 'phabricator-notification-story-head', 52 + ), 53 + nonempty($title, 'Untitled Story')); 54 + 55 + 56 + return phutil_render_tag( 57 + 'div', 58 + array( 59 + 'class' => 60 + 'phabricator-notification '. 61 + 'phabricator-notification-story-one-line'), 62 + $head); 63 + } 64 + 65 + }