From 3a6ee79190b709d84ff79865d1c1d6234e5c102a Mon Sep 17 00:00:00 2001 From: John-Ashton Allen Date: Fri, 8 Jun 2012 06:31:30 -0700 Subject: [PATCH] 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 --- conf/default.conf.php | 2 + resources/sql/patches/temp.notifications.sql | 8 ++ src/__phutil_library_map__.php | 24 ++++ ...AphrontDefaultApplicationConfiguration.php | 3 +- .../feed/PhabricatorFeedStoryPublisher.php | 47 +++++++- .../feed/story/PhabricatorFeedStory.php | 11 +- .../story/PhabricatorFeedStoryManiphest.php | 106 ++++++++++-------- .../ManiphestTaskDetailController.php | 3 + .../editor/ManiphestTransactionEditor.php | 17 ++- .../PhabricatorNotificationQuery.php | 81 +++++++++++++ .../PhabricatorNotificationBuilder.php | 54 +++++++++ .../PhabricatorNotificationController.php | 37 ++++++ .../PhabricatorNotificationTestController.php | 53 +++++++++ .../PhabricatorFeedStoryNotification.php | 57 ++++++++++ .../view/base/PhabricatorNotificationView.php | 21 ++++ .../PhabricatorNotificationStoryView.php | 65 +++++++++++ 16 files changed, 540 insertions(+), 49 deletions(-) create mode 100644 resources/sql/patches/temp.notifications.sql create mode 100644 src/applications/notification/PhabricatorNotificationQuery.php create mode 100644 src/applications/notification/builder/notification/PhabricatorNotificationBuilder.php create mode 100644 src/applications/notification/controller/base/PhabricatorNotificationController.php create mode 100644 src/applications/notification/controller/test/PhabricatorNotificationTestController.php create mode 100644 src/applications/notification/storage/PhabricatorFeedStoryNotification.php create mode 100644 src/applications/notification/view/base/PhabricatorNotificationView.php create mode 100644 src/applications/notification/view/story/PhabricatorNotificationStoryView.php diff --git a/conf/default.conf.php b/conf/default.conf.php index fe70ae2d32..4c8b67e5cf 100644 --- a/conf/default.conf.php +++ b/conf/default.conf.php @@ -179,6 +179,8 @@ return array( // extend AphrontMySQLDatabaseConnectionBase. 'mysql.implementation' => 'AphrontMySQLDatabaseConnection', +// -- Notifications ----// + 'notification.enabled' => false, // -- Email ----------------------------------------------------------------- // diff --git a/resources/sql/patches/temp.notifications.sql b/resources/sql/patches/temp.notifications.sql new file mode 100644 index 0000000000..f10664d539 --- /dev/null +++ b/resources/sql/patches/temp.notifications.sql @@ -0,0 +1,8 @@ +CREATE TABLE if not exists phabricator_feed.feed_storynotification ( + userPHID varchar(64) not null collate utf8_bin, + primaryObjectPHID varchar(64) not null collate utf8_bin, + chronologicalKey BIGINT UNSIGNED NOT NULL, + hasViewed boolean not null, + UNIQUE KEY (userPHID, chronologicalKey), + KEY (userPHID, hasViewed, primaryObjectPHID) +); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index bb522ae3e1..7ef53f83cd 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -525,6 +525,9 @@ phutil_register_library_map(array( 'ManiphestView' => 'applications/maniphest/view/ManiphestView.php', 'MetaMTAConstants' => 'applications/metamta/constants/MetaMTAConstants.php', 'MetaMTANotificationType' => 'applications/metamta/constants/MetaMTANotificationType.php', + 'NotificationMessage' => 'applications/notification/constants/message/NotificationMessage.php', + 'NotificationPathname' => 'applications/notification/constants/pathname/NotificationPathname.php', + 'NotificationType' => 'applications/notification/constants/type/NotificationType.php', 'OwnersPackageReplyHandler' => 'applications/owners/OwnersPackageReplyHandler.php', 'PackageCreateMail' => 'applications/owners/mail/PackageCreateMail.php', 'PackageDeleteMail' => 'applications/owners/mail/PackageDeleteMail.php', @@ -631,6 +634,7 @@ phutil_register_library_map(array( 'PhabricatorFeedStoryData' => 'applications/feed/storage/PhabricatorFeedStoryData.php', 'PhabricatorFeedStoryDifferential' => 'applications/feed/story/PhabricatorFeedStoryDifferential.php', 'PhabricatorFeedStoryManiphest' => 'applications/feed/story/PhabricatorFeedStoryManiphest.php', + 'PhabricatorFeedStoryNotification' => 'applications/notification/storage/PhabricatorFeedStoryNotification.php', 'PhabricatorFeedStoryPhriction' => 'applications/feed/story/PhabricatorFeedStoryPhriction.php', 'PhabricatorFeedStoryProject' => 'applications/feed/story/PhabricatorFeedStoryProject.php', 'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php', @@ -733,6 +737,16 @@ phutil_register_library_map(array( 'PhabricatorMetaMTAWorker' => 'applications/metamta/PhabricatorMetaMTAWorker.php', 'PhabricatorMustVerifyEmailController' => 'applications/auth/controller/PhabricatorMustVerifyEmailController.php', 'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php', + 'PhabricatorNotificationBuilder' => 'applications/notification/builder/notification/PhabricatorNotificationBuilder.php', + 'PhabricatorNotificationConstants' => 'applications/notification/constants/base/PhabricatorNotificationConstants.php', + 'PhabricatorNotificationController' => 'applications/notification/controller/base/PhabricatorNotificationController.php', + 'PhabricatorNotificationPanelController' => 'applications/notification/controller/panel/PhabricatorNotificationPanelController.php', + 'PhabricatorNotificationQuery' => 'applications/notification/PhabricatorNotificationQuery.php', + 'PhabricatorNotificationStory' => 'applications/notification/base/PhabricatorNotificationStory.php', + 'PhabricatorNotificationStoryTypeConstants' => 'applications/notification/constants/story/PhabricatorNotificationStoryTypeConstants.php', + 'PhabricatorNotificationStoryView' => 'applications/notification/view/story/PhabricatorNotificationStoryView.php', + 'PhabricatorNotificationTestController' => 'applications/notification/controller/test/PhabricatorNotificationTestController.php', + 'PhabricatorNotificationView' => 'applications/notification/view/base/PhabricatorNotificationView.php', 'PhabricatorNotificationsController' => 'applications/notifications/controller/PhabricatorNotificationsController.php', 'PhabricatorOAuthClientAuthorization' => 'applications/oauthserver/storage/PhabricatorOAuthClientAuthorization.php', 'PhabricatorOAuthClientAuthorizationBaseController' => 'applications/oauthserver/controller/clientauthorization/PhabricatorOAuthClientAuthorizationBaseController.php', @@ -1511,6 +1525,9 @@ phutil_register_library_map(array( 'ManiphestTransactionType' => 'ManiphestConstants', 'ManiphestView' => 'AphrontView', 'MetaMTANotificationType' => 'MetaMTAConstants', + 'NotificationMessage' => 'PhabricatorNotificationConstants', + 'NotificationPathname' => 'PhabricatorNotificationConstants', + 'NotificationType' => 'PhabricatorNotificationConstants', 'OwnersPackageReplyHandler' => 'PhabricatorMailReplyHandler', 'PackageCreateMail' => 'PackageMail', 'PackageDeleteMail' => 'PackageMail', @@ -1604,6 +1621,7 @@ phutil_register_library_map(array( 'PhabricatorFeedStoryData' => 'PhabricatorFeedDAO', 'PhabricatorFeedStoryDifferential' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryManiphest' => 'PhabricatorFeedStory', + 'PhabricatorFeedStoryNotification' => 'PhabricatorFeedDAO', 'PhabricatorFeedStoryPhriction' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryProject' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO', @@ -1688,6 +1706,12 @@ phutil_register_library_map(array( 'PhabricatorMetaMTAWorker' => 'PhabricatorWorker', 'PhabricatorMustVerifyEmailController' => 'PhabricatorAuthController', 'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine', + 'PhabricatorNotificationController' => 'PhabricatorController', + 'PhabricatorNotificationPanelController' => 'PhabricatorNotificationController', + 'PhabricatorNotificationStoryTypeConstants' => 'PhabricatorNotificationConstants', + 'PhabricatorNotificationStoryView' => 'PhabricatorNotificationView', + 'PhabricatorNotificationTestController' => 'PhabricatorNotificationController', + 'PhabricatorNotificationView' => 'AphrontView', 'PhabricatorNotificationsController' => 'PhabricatorController', 'PhabricatorOAuthClientAuthorization' => 'PhabricatorOAuthServerDAO', 'PhabricatorOAuthClientAuthorizationBaseController' => 'PhabricatorOAuthServerController', diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php index 34556cbd5a..2608e0b2df 100644 --- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -421,8 +421,7 @@ class AphrontDefaultApplicationConfiguration 'PhabricatorChatLogChannelLogController', ), - '/aphlict/' => 'PhabricatorAphlictTestPageController', - + '/notification/test/' => 'PhabricatorNotificationTestController', '/flag/' => array( '' => 'PhabricatorFlagListController', 'view/(?P[^/]+)/' => 'PhabricatorFlagListController', diff --git a/src/applications/feed/PhabricatorFeedStoryPublisher.php b/src/applications/feed/PhabricatorFeedStoryPublisher.php index ae64f0f20f..2d54e4a567 100644 --- a/src/applications/feed/PhabricatorFeedStoryPublisher.php +++ b/src/applications/feed/PhabricatorFeedStoryPublisher.php @@ -1,7 +1,7 @@ relatedPHIDs = $phids; return $this; } + public function setSubscribedPHIDs(array $phids) { + $this->subscribedPHIDs = $phids; + return $this; + } + public function setPrimaryObjectPHID($phid) { + $this->primaryObjectPHID = $phid; + return $this; + } + public function setStoryType($story_type) { $this->storyType = $story_type; return $this; @@ -85,9 +96,43 @@ final class PhabricatorFeedStoryPublisher { $ref->getTableName(), implode(', ', $sql)); + if (PhabricatorEnv::getEnvConfig('notification.enabled')) { + $this->insertNotifications($chrono_key); + } return $story; } + + private function insertNotifications($chrono_key) { + if (!$this->primaryObjectPHID) { + throw + new Exception("Call setPrimaryObjectPHID() before Publishing!"); + } + if ($this->subscribedPHIDs) { + $notif = new PhabricatorFeedStoryNotification(); + $sql = array(); + $conn = $notif->establishConnection('w'); + + foreach (array_unique($this->subscribedPHIDs) as $user_phid) { + $sql[] = qsprintf( + $conn, + '(%s, %s, %s, %d)', + $this->primaryObjectPHID, + $user_phid, + $chrono_key, + 0); + } + + queryfx( + $conn, + 'INSERT INTO %T + (primaryObjectPHID, userPHID, chronologicalKey, hasViewed) + VALUES %Q', + $notif->getTableName(), + implode(', ', $sql)); + } + + } /** * We generate a unique chronological key for each story type because we want * to be able to page through the stream with a cursor (i.e., select stories diff --git a/src/applications/feed/story/PhabricatorFeedStory.php b/src/applications/feed/story/PhabricatorFeedStory.php index fb466b9439..c1d696dc53 100644 --- a/src/applications/feed/story/PhabricatorFeedStory.php +++ b/src/applications/feed/story/PhabricatorFeedStory.php @@ -19,7 +19,7 @@ abstract class PhabricatorFeedStory { private $data; - + private $hasViewed; private $handles; private $framed; @@ -33,6 +33,15 @@ abstract class PhabricatorFeedStory { return array(); } + public function setHasViewed($has_viewed) { + $this->hasViewed = $has_viewed; + return $this; + } + + public function getHasViewed() { + return $this->hasViewed; + } + public function getRequiredObjectPHIDs() { return array(); } diff --git a/src/applications/feed/story/PhabricatorFeedStoryManiphest.php b/src/applications/feed/story/PhabricatorFeedStoryManiphest.php index 6defa7c10b..aedeaf9bad 100644 --- a/src/applications/feed/story/PhabricatorFeedStoryManiphest.php +++ b/src/applications/feed/story/PhabricatorFeedStoryManiphest.php @@ -16,72 +16,48 @@ * limitations under the License. */ -final class PhabricatorFeedStoryManiphest extends PhabricatorFeedStory { +final class PhabricatorFeedStoryManiphest + extends PhabricatorFeedStory { public function getRequiredHandlePHIDs() { $data = $this->getStoryData(); return array_filter( - array( + array( $this->getStoryData()->getAuthorPHID(), $data->getValue('taskPHID'), $data->getValue('ownerPHID'), - )); + )); } public function getRequiredObjectPHIDs() { return array( $this->getStoryData()->getAuthorPHID(), - ); + ); } public function renderView() { $data = $this->getStoryData(); - $author_phid = $data->getAuthorPHID(); - $owner_phid = $data->getValue('ownerPHID'); - $task_phid = $data->getValue('taskPHID'); - - $action = $data->getValue('action'); - $view = new PhabricatorFeedStoryView(); - $verb = ManiphestAction::getActionPastTenseVerb($action); - $extra = null; - switch ($action) { - case ManiphestAction::ACTION_ASSIGN: - if ($owner_phid) { - $extra = - ' to '. - $this->linkTo($owner_phid); - } else { - $verb = 'placed'; - $extra = ' up for grabs'; - } - break; - } - - $title = - $this->linkTo($author_phid). - " {$verb} task ". - $this->linkTo($task_phid); - $title .= $extra; - $title .= '.'; - - $view->setTitle($title); - - switch ($action) { - case ManiphestAction::ACTION_CREATE: - $full_size = true; - break; - default: - $full_size = false; - break; - } - $view->setEpoch($data->getEpoch()); + $action = $this->getLineForData($data); + $view->setTitle($action); + $view->setEpoch($data->getEpoch()); + + + switch ($action) { + case ManiphestAction::ACTION_CREATE: + $full_size = true; + break; + default: + $full_size = false; + break; + } + if ($full_size) { - $view->setImage($this->getHandle($author_phid)->getImageURI()); + $view->setImage($this->getHandle($this->getAuthorPHID())->getImageURI()); $content = $this->renderSummary($data->getValue('description')); $view->appendChild($content); } else { @@ -91,4 +67,46 @@ final class PhabricatorFeedStoryManiphest extends PhabricatorFeedStory { return $view; } + + public function renderNotificationView() { + $data = $this->getStoryData(); + + $view = new PhabricatorNotificationStoryView(); + + $view->setEpoch($data->getEpoch()); + $view->setTitle($this->getLineForData($data)); + $view->setEpoch($data->getEpoch()); + $view->setViewed($this->getHasViewed()); + + return $view; + } + + private function getLineForData($data) { + $actor_phid = $data->getAuthorPHID(); + $owner_phid = $data->getValue('ownerPHID'); + $task_phid = $data->getValue('taskPHID'); + $action = $data->getValue('action'); + $description = $data->getValue('description'); + $comments = phutil_escape_html( + phutil_utf8_shorten( + $data->getValue('comments'), + 140)); + + $actor_link = $this->linkTo($actor_phid); + $task_link = $this->linkTo($task_phid); + $owner_link = $this->linkTo($owner_phid); + $verb = ManiphestAction::getActionPastTenseVerb($action); + + $one_line = "{$actor_link} {$verb} {$task_link}"; + + switch ($action) { + case ManiphestAction::ACTION_ASSIGN: + $one_line .= " to {$owner_link}"; + break; + default: + break; + } + + return $one_line; + } } diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 8dcc699c32..b94429e7fa 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -510,6 +510,9 @@ final class ManiphestTaskDetailController extends ManiphestController { $transaction_view->setAuxiliaryFields($aux_fields); $transaction_view->setMarkupEngine($engine); + PhabricatorFeedStoryNotification::updateObjectNotificationViews( + $user, $task->getPHID()); + return $this->buildStandardPageResponse( array( $context_bar, diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index 94dfa2eee3..751fa00859 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -314,7 +314,8 @@ final class ManiphestTransactionEditor { if ($transaction->hasComments()) { $comments = $transaction->getComments(); } - switch ($transaction->getTransactionType()) { + $type = $transaction->getTransactionType(); + switch ($type) { case ManiphestTransactionType::TYPE_OWNER: $actions[] = ManiphestAction::ACTION_ASSIGN; break; @@ -323,6 +324,8 @@ final class ManiphestTransactionEditor { $actions[] = ManiphestAction::ACTION_CLOSE; } else if ($this->isCreate($transactions)) { $actions[] = ManiphestAction::ACTION_CREATE; + } else { + $actions[] = ManiphestAction::ACTION_REOPEN; } break; default: @@ -335,6 +338,7 @@ final class ManiphestTransactionEditor { $actor_phid = head($transactions)->getAuthorPHID(); $author_phid = $task->getAuthorPHID(); + id(new PhabricatorFeedStoryPublisher()) ->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_MANIPHEST) ->setStoryData(array( @@ -357,6 +361,17 @@ final class ManiphestTransactionEditor { $owner_phid, )), $task->getProjectPHIDs())) + ->setPrimaryObjectPHID($task->getPHID()) + ->setSubscribedPHIDs( + array_merge( + array_filter( + array( + $author_phid, + $owner_phid, + $actor_phid + ) + ), + $task->getCCPHIDs())) ->publish(); } diff --git a/src/applications/notification/PhabricatorNotificationQuery.php b/src/applications/notification/PhabricatorNotificationQuery.php new file mode 100644 index 0000000000..4b1970d4e7 --- /dev/null +++ b/src/applications/notification/PhabricatorNotificationQuery.php @@ -0,0 +1,81 @@ +limit = $limit; + return $this; + } + + public function setUserPHID($user_phid) { + $this->userPHID = $user_phid; + return $this; + } + + + public function execute() { + if (!$this->userPHID) { + throw new Exception("Call setUser() before executing the query"); + } + + //TODO throw an exception if no user + $story_table = new PhabricatorFeedStoryData(); + $notification_table = new PhabricatorFeedStoryNotification(); + + $conn = $story_table->establishConnection('r'); + + $data = queryfx_all( + $conn, + "SELECT story.*, notif.hasViewed FROM %T notif + JOIN %T story ON notif.chronologicalKey = story.chronologicalKey + WHERE notif.userPHID = %s + ORDER BY notif.chronologicalKey desc + LIMIT %d", + $notification_table->getTableName(), + $story_table->getTableName(), + $this->userPHID, + $this->limit); + + $viewed_map = ipull($data, 'hasViewed', 'chronologicalKey'); + $data = $story_table->loadAllFromArray($data); + + $stories = array(); + + foreach ($data as $story_data) { + $class = $story_data->getStoryType(); + try { + if (!class_exists($class) || + !is_subclass_of($class, 'PhabricatorFeedStory')) { + $class = 'PhabricatorFeedStoryUnknown'; + } + } catch (PhutilMissingSymbolException $ex) { + $class = 'PhabricatorFeedStoryUnknown'; + } + $story = newv($class, array($story_data)); + $story->setHasViewed($viewed_map[$story->getChronologicalKey()]); + $stories[] = $story; + } + + return $stories; + } +} diff --git a/src/applications/notification/builder/notification/PhabricatorNotificationBuilder.php b/src/applications/notification/builder/notification/PhabricatorNotificationBuilder.php new file mode 100644 index 0000000000..1e123f88ad --- /dev/null +++ b/src/applications/notification/builder/notification/PhabricatorNotificationBuilder.php @@ -0,0 +1,54 @@ +stories = $stories; + } + + public function buildView() { + + $stories = $this->stories; + + $handles = array(); + $objects = array(); + + if ($stories) { + $handle_phids = array_mergev(mpull($stories, 'getRequiredHandlePHIDs')); + $handles = id(new PhabricatorObjectHandleData($handle_phids)) + ->loadHandles(); + } + + $null_view = new AphrontNullView(); + + foreach ($stories as $story) { + $story->setHandles($handles); + $view = $story->renderNotificationView(); + $null_view->appendChild($view); + } + + return id(new AphrontNullView())->appendChild( + '
'. + $null_view->render(). + '
'); + + } +} diff --git a/src/applications/notification/controller/base/PhabricatorNotificationController.php b/src/applications/notification/controller/base/PhabricatorNotificationController.php new file mode 100644 index 0000000000..ae82ac8b10 --- /dev/null +++ b/src/applications/notification/controller/base/PhabricatorNotificationController.php @@ -0,0 +1,37 @@ +buildStandardPageView(); + + $page->setApplicationName('Notification'); + $page->setBaseURI('/notification/'); + $page->setTitle(idx($data, 'title')); + $page->setGlyph('!'); + $page->appendChild($view); + + $response = new AphrontWebpageResponse(); + return $response->setContent($page->render()); + + } + +} diff --git a/src/applications/notification/controller/test/PhabricatorNotificationTestController.php b/src/applications/notification/controller/test/PhabricatorNotificationTestController.php new file mode 100644 index 0000000000..fb7ba42300 --- /dev/null +++ b/src/applications/notification/controller/test/PhabricatorNotificationTestController.php @@ -0,0 +1,53 @@ +getRequest(); + $user = $request->getUser(); + + $query = new PhabricatorNotificationQuery(); + $query->setUserPHID($user->getPHID()); + + $stories = $query->execute(); + + $builder = new PhabricatorNotificationBuilder($stories); + $notifications_view = $builder->buildView(); + + $num_unconsumed = 0; + + foreach ($stories as $story) { + if (!$story->getHasViewed()) { + $num_unconsumed++; + } + + } + + $json = array( + $notifications_view->render() + ); + + + return $this->buildStandardPageResponse( + $json, + array('title' => 'Notification Test Page')); + } +} diff --git a/src/applications/notification/storage/PhabricatorFeedStoryNotification.php b/src/applications/notification/storage/PhabricatorFeedStoryNotification.php new file mode 100644 index 0000000000..ca2416b3aa --- /dev/null +++ b/src/applications/notification/storage/PhabricatorFeedStoryNotification.php @@ -0,0 +1,57 @@ + self::IDS_MANUAL, + self::CONFIG_TIMESTAMPS => false, + ) + parent::getConfiguration(); + } + + static public function updateObjectNotificationViews(PhabricatorUser $user, + $object_phid) { + + if (PhabricatorEnv::getEnvConfig('notification.enabled')) { + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + + $notification_table = new PhabricatorFeedStoryNotification(); + $conn = $notification_table->establishConnection('w'); + + queryfx( + $conn, + "UPDATE %T + SET hasViewed = 1 + WHERE userPHID = %s + AND primaryObjectPHID = %s + AND hasViewed = 0", + $notification_table->getTableName(), + $user->getPHID(), + $object_phid); + + unset($unguarded); + } + } + +} diff --git a/src/applications/notification/view/base/PhabricatorNotificationView.php b/src/applications/notification/view/base/PhabricatorNotificationView.php new file mode 100644 index 0000000000..5f815f7004 --- /dev/null +++ b/src/applications/notification/view/base/PhabricatorNotificationView.php @@ -0,0 +1,21 @@ +title = $title; + return $this; + } + + public function setEpoch($epoch) { + $this->epoch = $epoch; + return $this; + } + + public function setViewed($viewed) { + $this->viewed = $viewed; + } + + public function render() { + + $title = $this->title; + if (!$this->viewed) { + $title = ''.$title.''; + } + + $head = phutil_render_tag( + 'div', + array( + 'class' => 'phabricator-notification-story-head', + ), + nonempty($title, 'Untitled Story')); + + + return phutil_render_tag( + 'div', + array( + 'class' => + 'phabricator-notification '. + 'phabricator-notification-story-one-line'), + $head); + } + +}