diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3640,6 +3640,8 @@ 'PhabricatorUserIconField' => 'applications/people/customfield/PhabricatorUserIconField.php', 'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php', 'PhabricatorUserLogView' => 'applications/people/view/PhabricatorUserLogView.php', + 'PhabricatorUserMessageCountCacheType' => 'applications/people/cache/PhabricatorUserMessageCountCacheType.php', + 'PhabricatorUserNotificationCountCacheType' => 'applications/people/cache/PhabricatorUserNotificationCountCacheType.php', 'PhabricatorUserPHIDResolver' => 'applications/phid/resolver/PhabricatorUserPHIDResolver.php', 'PhabricatorUserPreferences' => 'applications/settings/storage/PhabricatorUserPreferences.php', 'PhabricatorUserPreferencesCacheType' => 'applications/people/cache/PhabricatorUserPreferencesCacheType.php', @@ -8451,6 +8453,8 @@ 'PhabricatorPolicyInterface', ), 'PhabricatorUserLogView' => 'AphrontView', + 'PhabricatorUserMessageCountCacheType' => 'PhabricatorUserCacheType', + 'PhabricatorUserNotificationCountCacheType' => 'PhabricatorUserCacheType', 'PhabricatorUserPHIDResolver' => 'PhabricatorPHIDResolver', 'PhabricatorUserPreferences' => array( 'PhabricatorUserDAO', diff --git a/src/applications/aphlict/query/AphlictDropdownDataQuery.php b/src/applications/aphlict/query/AphlictDropdownDataQuery.php --- a/src/applications/aphlict/query/AphlictDropdownDataQuery.php +++ b/src/applications/aphlict/query/AphlictDropdownDataQuery.php @@ -46,17 +46,15 @@ $is_c_installed = PhabricatorApplication::isClassInstalledForViewer( $conpherence_app, $viewer); - $raw_message_count_number = null; - $message_count_number = null; if ($is_c_installed) { - $unread_status = ConpherenceParticipationStatus::BEHIND; - $unread = id(new ConpherenceParticipantCountQuery()) - ->withParticipantPHIDs(array($viewer->getPHID())) - ->withParticipationStatus($unread_status) - ->execute(); - $raw_message_count_number = idx($unread, $viewer->getPHID(), 0); + $raw_message_count_number = $viewer->getUnreadMessageCount(); $message_count_number = $this->formatNumber($raw_message_count_number); + } else { + $raw_message_count_number = null; + $message_count_number = null; } + + $conpherence_data = array( 'isInstalled' => $is_c_installed, 'countType' => 'messages', @@ -69,15 +67,15 @@ $is_n_installed = PhabricatorApplication::isClassInstalledForViewer( $notification_app, $viewer); - $notification_count_number = null; - $raw_notification_count_number = null; if ($is_n_installed) { - $raw_notification_count_number = - id(new PhabricatorFeedStoryNotification()) - ->countUnread($viewer); + $raw_notification_count_number = $viewer->getUnreadNotificationCount(); $notification_count_number = $this->formatNumber( $raw_notification_count_number); + } else { + $notification_count_number = null; + $raw_notification_count_number = null; } + $notification_data = array( 'isInstalled' => $is_n_installed, 'countType' => 'notifications', diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -68,9 +68,12 @@ $latest_transaction = head($transactions); $participant = $conpherence->getParticipantIfExists($user->getPHID()); if ($participant) { - $write_guard = AphrontWriteGuard::beginScopedUnguardedWrites(); - $participant->markUpToDate($conpherence, $latest_transaction); - unset($write_guard); + if (!$participant->isUpToDate($conpherence)) { + $write_guard = AphrontWriteGuard::beginScopedUnguardedWrites(); + $participant->markUpToDate($conpherence, $latest_transaction); + $user->clearCacheData(PhabricatorUserMessageCountCacheType::KEY_COUNT); + unset($write_guard); + } } $data = ConpherenceTransactionRenderer::renderTransactions( diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -422,6 +422,10 @@ $participant->save(); } + PhabricatorUserCache::clearCaches( + PhabricatorUserMessageCountCacheType::KEY_COUNT, + array_keys($participants)); + if ($xactions) { $data = array( 'type' => 'message', diff --git a/src/applications/conpherence/storage/ConpherenceParticipant.php b/src/applications/conpherence/storage/ConpherenceParticipant.php --- a/src/applications/conpherence/storage/ConpherenceParticipant.php +++ b/src/applications/conpherence/storage/ConpherenceParticipant.php @@ -47,11 +47,16 @@ $this->setBehindTransactionPHID($xaction->getPHID()); $this->setSeenMessageCount($conpherence->getMessageCount()); $this->save(); + + PhabricatorUserCache::clearCache( + PhabricatorUserMessageCountCacheType::KEY_COUNT, + $this->getParticipantPHID()); } + return $this; } - private function isUpToDate(ConpherenceThread $conpherence) { + public function isUpToDate(ConpherenceThread $conpherence) { return ($this->getSeenMessageCount() == $conpherence->getMessageCount()) && diff --git a/src/applications/feed/PhabricatorFeedStoryPublisher.php b/src/applications/feed/PhabricatorFeedStoryPublisher.php --- a/src/applications/feed/PhabricatorFeedStoryPublisher.php +++ b/src/applications/feed/PhabricatorFeedStoryPublisher.php @@ -159,7 +159,8 @@ $will_receive_mail = array_fill_keys($this->mailRecipientPHIDs, true); - foreach (array_unique($subscribed_phids) as $user_phid) { + $user_phids = array_unique($subscribed_phids); + foreach ($user_phids as $user_phid) { if (isset($will_receive_mail[$user_phid])) { $mark_read = 1; } else { @@ -184,6 +185,10 @@ $notif->getTableName(), implode(', ', $sql)); } + + PhabricatorUserCache::clearCaches( + PhabricatorUserNotificationCountCacheType::KEY_COUNT, + $user_phids); } private function sendNotification($chrono_key, array $subscribed_phids) { diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -134,6 +134,9 @@ return $files; } + $viewer = $this->getViewer(); + $is_omnipotent = $viewer->isOmnipotent(); + // We need to load attached objects to perform policy checks for files. // First, load the edges. @@ -156,6 +159,13 @@ continue; } + if ($is_omnipotent) { + // If the viewer is omnipotent, we don't need to load the associated + // objects either since they can certainly see the object. Skipping + // this can improve performance and prevent cycles. + continue; + } + foreach ($phids as $phid) { $object_phids[$phid] = true; } diff --git a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php --- a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php +++ b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php @@ -337,9 +337,12 @@ $all_phids = array_merge($to, $cc); if ($all_phids) { + // NOTE: We need the user settings to pull their translations later + // so we can send mail in the correct language. $users = id(new PhabricatorPeopleQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs($all_phids) + ->needUserSettings(true) ->execute(); $users = mpull($users, null, 'getPHID'); diff --git a/src/applications/notification/controller/PhabricatorNotificationClearController.php b/src/applications/notification/controller/PhabricatorNotificationClearController.php --- a/src/applications/notification/controller/PhabricatorNotificationClearController.php +++ b/src/applications/notification/controller/PhabricatorNotificationClearController.php @@ -18,6 +18,10 @@ $viewer->getPHID(), $chrono_key); + PhabricatorUserCache::clearCache( + PhabricatorUserNotificationCountCacheType::KEY_COUNT, + $viewer->getPHID()); + return id(new AphrontReloadResponse()) ->setURI('/notification/'); } diff --git a/src/applications/notification/controller/PhabricatorNotificationPanelController.php b/src/applications/notification/controller/PhabricatorNotificationPanelController.php --- a/src/applications/notification/controller/PhabricatorNotificationPanelController.php +++ b/src/applications/notification/controller/PhabricatorNotificationPanelController.php @@ -71,8 +71,7 @@ $content, $connection_ui); - $unread_count = id(new PhabricatorFeedStoryNotification()) - ->countUnread($viewer); + $unread_count = $viewer->getUnreadNotificationCount(); $json = array( 'content' => $content, diff --git a/src/applications/notification/storage/PhabricatorFeedStoryNotification.php b/src/applications/notification/storage/PhabricatorFeedStoryNotification.php --- a/src/applications/notification/storage/PhabricatorFeedStoryNotification.php +++ b/src/applications/notification/storage/PhabricatorFeedStoryNotification.php @@ -60,20 +60,10 @@ $object_phid); unset($unguarded); - } - - public function countUnread(PhabricatorUser $user) { - $conn = $this->establishConnection('r'); - - $data = queryfx_one( - $conn, - 'SELECT COUNT(*) as count - FROM %T - WHERE userPHID = %s AND hasViewed = 0', - $this->getTableName(), - $user->getPHID()); - return $data['count']; + $count_key = PhabricatorUserNotificationCountCacheType::KEY_COUNT; + PhabricatorUserCache::clearCache($count_key, $user->getPHID()); + $user->clearCacheData($count_key); } } diff --git a/src/applications/people/cache/PhabricatorUserMessageCountCacheType.php b/src/applications/people/cache/PhabricatorUserMessageCountCacheType.php new file mode 100644 --- /dev/null +++ b/src/applications/people/cache/PhabricatorUserMessageCountCacheType.php @@ -0,0 +1,45 @@ +withParticipantPHIDs($user_phids) + ->withParticipationStatus($unread_status) + ->execute(); + + $empty = array_fill_keys($user_phids, 0); + return $unread + $empty; + } + +} diff --git a/src/applications/people/cache/PhabricatorUserNotificationCountCacheType.php b/src/applications/people/cache/PhabricatorUserNotificationCountCacheType.php new file mode 100644 --- /dev/null +++ b/src/applications/people/cache/PhabricatorUserNotificationCountCacheType.php @@ -0,0 +1,50 @@ +establishConnection('r'); + + $rows = queryfx_all( + $conn_r, + 'SELECT userPHID, COUNT(*) N FROM %T + WHERE userPHID IN (%Ls) AND hasViewed = 0 + GROUP BY userPHID', + $table->getTableName(), + $user_phids); + + $empty = array_fill_keys($user_phids, 0); + return ipull($rows, 'N', 'userPHID') + $empty; + } + +} diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -788,6 +788,16 @@ return $this->requireCacheData($uri_key); } + public function getUnreadNotificationCount() { + $notification_key = PhabricatorUserNotificationCountCacheType::KEY_COUNT; + return $this->requireCacheData($notification_key); + } + + public function getUnreadMessageCount() { + $message_key = PhabricatorUserMessageCountCacheType::KEY_COUNT; + return $this->requireCacheData($message_key); + } + public function getFullName() { if (strlen($this->getRealName())) { return $this->getUsername().' ('.$this->getRealName().')'; diff --git a/src/applications/people/storage/PhabricatorUserCache.php b/src/applications/people/storage/PhabricatorUserCache.php --- a/src/applications/people/storage/PhabricatorUserCache.php +++ b/src/applications/people/storage/PhabricatorUserCache.php @@ -97,10 +97,18 @@ } public static function clearCache($key, $user_phid) { + return self::clearCaches($key, array($user_phid)); + } + + public static function clearCaches($key, array $user_phids) { if (PhabricatorEnv::isReadOnly()) { return; } + if (!$user_phids) { + return; + } + $table = new self(); $conn_w = $table->establishConnection('w'); @@ -108,15 +116,14 @@ queryfx( $conn_w, - 'DELETE FROM %T WHERE cacheIndex = %s AND userPHID = %s', + 'DELETE FROM %T WHERE cacheIndex = %s AND userPHID IN (%Ls)', $table->getTableName(), PhabricatorHash::digestForIndex($key), - $user_phid); + $user_phids); unset($unguarded); } - public static function clearCacheForAllUsers($key) { if (PhabricatorEnv::isReadOnly()) { return; diff --git a/src/applications/settings/editor/PhabricatorUserPreferencesEditor.php b/src/applications/settings/editor/PhabricatorUserPreferencesEditor.php --- a/src/applications/settings/editor/PhabricatorUserPreferencesEditor.php +++ b/src/applications/settings/editor/PhabricatorUserPreferencesEditor.php @@ -162,7 +162,6 @@ PhabricatorUserPreferencesCacheType::KEY_PREFERENCES); } - return $xactions; } diff --git a/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php b/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php --- a/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php +++ b/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php @@ -84,8 +84,10 @@ $xaction_phids = idx($data, 'xactionPHIDs'); if (!$xaction_phids) { - throw new PhabricatorWorkerPermanentFailureException( - pht('Task has no transaction PHIDs!')); + // It's okay if we don't have any transactions. This can happen when + // creating objects or performing no-op updates. We will still apply + // meaningful side effects like updating search engine indexes. + return array(); } $viewer = PhabricatorUser::getOmnipotentUser();