public function testLocaleScopeGuard()
 {
     $original = PhabricatorEnv::getLocaleCode();
     // Set a guard; it should change the locale, then revert it when destroyed.
     $guard = PhabricatorEnv::beginScopedLocale('en_GB');
     $this->assertEqual('en_GB', PhabricatorEnv::getLocaleCode());
     unset($guard);
     $this->assertEqual($original, PhabricatorEnv::getLocaleCode());
     // Nest guards, then destroy them out of order.
     $guard1 = PhabricatorEnv::beginScopedLocale('en_GB');
     $this->assertEqual('en_GB', PhabricatorEnv::getLocaleCode());
     $guard2 = PhabricatorEnv::beginScopedLocale('en_A*');
     $this->assertEqual('en_A*', PhabricatorEnv::getLocaleCode());
     unset($guard1);
     $this->assertEqual('en_A*', PhabricatorEnv::getLocaleCode());
     unset($guard2);
     $this->assertEqual($original, PhabricatorEnv::getLocaleCode());
     // If you push `null`, that should mean "the default locale", not
     // "the current locale".
     $guard3 = PhabricatorEnv::beginScopedLocale('en_GB');
     $this->assertEqual('en_GB', PhabricatorEnv::getLocaleCode());
     $guard4 = PhabricatorEnv::beginScopedLocale(null);
     $this->assertEqual($original, PhabricatorEnv::getLocaleCode());
     unset($guard4);
     $this->assertEqual('en_GB', PhabricatorEnv::getLocaleCode());
     unset($guard3);
     $this->assertEqual($original, PhabricatorEnv::getLocaleCode());
 }
 protected function getSelectOptionGroups()
 {
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
     $locales = PhutilLocale::loadAllLocales();
     $group_labels = array('normal' => pht('Translations'), 'limited' => pht('Limited Translations'), 'silly' => pht('Silly Translations'), 'test' => pht('Developer/Test Translations'));
     $groups = array_fill_keys(array_keys($group_labels), array());
     $translations = array();
     foreach ($locales as $locale) {
         $code = $locale->getLocaleCode();
         // Get the locale's localized name if it's available. For example,
         // "Deutsch" instead of "German". This helps users who do not speak the
         // current language to find the correct setting.
         $raw_scope = PhabricatorEnv::beginScopedLocale($code);
         $name = $locale->getLocaleName();
         unset($raw_scope);
         if ($locale->isSillyLocale()) {
             if ($is_serious) {
                 // Omit silly locales on serious business installs.
                 continue;
             }
             $groups['silly'][$code] = $name;
             continue;
         }
         if ($locale->isTestLocale()) {
             $groups['test'][$code] = $name;
             continue;
         }
         $strings = PhutilTranslation::getTranslationMapForLocale($code);
         $size = count($strings);
         // If a translation is English, assume it can fall back to the default
         // strings and don't caveat its completeness.
         $is_english = substr($code, 0, 3) == 'en_';
         // Arbitrarily pick some number of available strings to promote a
         // translation out of the "limited" group. The major goal is just to
         // keep locales with very few strings out of the main group, so users
         // aren't surprised if a locale has no upstream translations available.
         if ($size > 512 || $is_english) {
             $type = 'normal';
         } else {
             $type = 'limited';
         }
         $groups[$type][$code] = $name;
     }
     $results = array();
     foreach ($groups as $key => $group) {
         $label = $group_labels[$key];
         if (!$group) {
             continue;
         }
         asort($group);
         $results[] = array('label' => $label, 'options' => $group);
     }
     return $results;
 }
 private function sendMail(PhabricatorMailTarget $target, PhabricatorRepository $repository, PhabricatorRepositoryPushEvent $event)
 {
     $task_data = $this->getTaskData();
     $viewer = $target->getViewer();
     $locale = PhabricatorEnv::beginScopedLocale($viewer->getTranslation());
     $logs = $event->getLogs();
     list($ref_lines, $ref_list) = $this->renderRefs($logs);
     list($commit_lines, $subject_line) = $this->renderCommits($repository, $logs, idx($task_data, 'info', array()));
     $ref_count = count($ref_lines);
     $commit_count = count($commit_lines);
     $handles = id(new PhabricatorHandleQuery())->setViewer($viewer)->withPHIDs(array($event->getPusherPHID()))->execute();
     $pusher_name = $handles[$event->getPusherPHID()]->getName();
     $repo_name = $repository->getMonogram();
     if ($commit_count) {
         $overview = pht('%s pushed %d commit(s) to %s.', $pusher_name, $commit_count, $repo_name);
     } else {
         $overview = pht('%s pushed to %s.', $pusher_name, $repo_name);
     }
     $details_uri = PhabricatorEnv::getProductionURI('/diffusion/pushlog/view/' . $event->getID() . '/');
     $body = new PhabricatorMetaMTAMailBody();
     $body->addRawSection($overview);
     $body->addLinkSection(pht('DETAILS'), $details_uri);
     if ($commit_lines) {
         $body->addTextSection(pht('COMMITS'), implode("\n", $commit_lines));
     }
     if ($ref_lines) {
         $body->addTextSection(pht('REFERENCES'), implode("\n", $ref_lines));
     }
     $prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix');
     $parts = array();
     if ($commit_count) {
         $parts[] = pht('%s commit(s)', $commit_count);
     }
     if ($ref_count) {
         $parts[] = implode(', ', $ref_list);
     }
     $parts = implode(', ', $parts);
     if ($subject_line) {
         $subject = pht('(%s) %s', $parts, $subject_line);
     } else {
         $subject = pht('(%s)', $parts);
     }
     $mail = id(new PhabricatorMetaMTAMail())->setRelatedPHID($event->getPHID())->setSubjectPrefix($prefix)->setVarySubjectPrefix(pht('[Push]'))->setSubject($subject)->setFrom($event->getPusherPHID())->setBody($body->render())->setThreadID($event->getPHID(), $is_new = true)->addHeader('Thread-Topic', $subject)->setIsBulk(true);
     $target->sendMail($mail);
 }
 private function sendNotifications()
 {
     $cursor = $this->getCursor();
     $window_min = $cursor - phutil_units('16 hours in seconds');
     $window_max = $cursor + phutil_units('16 hours in seconds');
     $viewer = PhabricatorUser::getOmnipotentUser();
     $events = id(new PhabricatorCalendarEventQuery())->setViewer($viewer)->withDateRange($window_min, $window_max)->withIsCancelled(false)->withIsImported(false)->setGenerateGhosts(true)->execute();
     if (!$events) {
         // No events are starting soon in any timezone, so there is nothing
         // left to be done.
         return;
     }
     $attendee_map = array();
     foreach ($events as $key => $event) {
         $notifiable_phids = array();
         foreach ($event->getInvitees() as $invitee) {
             if (!$invitee->isAttending()) {
                 continue;
             }
             $notifiable_phids[] = $invitee->getInviteePHID();
         }
         if (!$notifiable_phids) {
             unset($events[$key]);
         }
         $attendee_map[$key] = array_fuse($notifiable_phids);
     }
     if (!$attendee_map) {
         // None of the events have any notifiable attendees, so there is no
         // one to notify of anything.
         return;
     }
     $all_attendees = array();
     foreach ($attendee_map as $key => $attendee_phids) {
         foreach ($attendee_phids as $attendee_phid) {
             $all_attendees[$attendee_phid] = $attendee_phid;
         }
     }
     $user_map = id(new PhabricatorPeopleQuery())->setViewer($viewer)->withPHIDs($all_attendees)->withIsDisabled(false)->needUserSettings(true)->execute();
     $user_map = mpull($user_map, null, 'getPHID');
     if (!$user_map) {
         // None of the attendees are valid users: they're all imported users
         // or projects or invalid or some other kind of unnotifiable entity.
         return;
     }
     $all_event_phids = array();
     foreach ($events as $key => $event) {
         foreach ($event->getNotificationPHIDs() as $phid) {
             $all_event_phids[$phid] = $phid;
         }
     }
     $table = new PhabricatorCalendarNotification();
     $conn = $table->establishConnection('w');
     $rows = queryfx_all($conn, 'SELECT * FROM %T WHERE eventPHID IN (%Ls) AND targetPHID IN (%Ls)', $table->getTableName(), $all_event_phids, $all_attendees);
     $sent_map = array();
     foreach ($rows as $row) {
         $event_phid = $row['eventPHID'];
         $target_phid = $row['targetPHID'];
         $initial_epoch = $row['utcInitialEpoch'];
         $sent_map[$event_phid][$target_phid][$initial_epoch] = $row;
     }
     $now = PhabricatorTime::getNow();
     $notify_min = $now;
     $notify_max = $now + $this->getNotifyWindow();
     $notify_map = array();
     foreach ($events as $key => $event) {
         $initial_epoch = $event->getUTCInitialEpoch();
         $event_phids = $event->getNotificationPHIDs();
         // Select attendees who actually exist, and who we have not sent any
         // notifications to yet.
         $attendee_phids = $attendee_map[$key];
         $users = array_select_keys($user_map, $attendee_phids);
         foreach ($users as $user_phid => $user) {
             foreach ($event_phids as $event_phid) {
                 if (isset($sent_map[$event_phid][$user_phid][$initial_epoch])) {
                     unset($users[$user_phid]);
                     continue 2;
                 }
             }
         }
         if (!$users) {
             continue;
         }
         // Discard attendees for whom the event start time isn't soon. Events
         // may start at different times for different users, so we need to
         // check every user's start time.
         foreach ($users as $user_phid => $user) {
             $user_datetime = $event->newStartDateTime()->setViewerTimezone($user->getTimezoneIdentifier());
             $user_epoch = $user_datetime->getEpoch();
             if ($user_epoch < $notify_min || $user_epoch > $notify_max) {
                 unset($users[$user_phid]);
                 continue;
             }
             $view = id(new PhabricatorCalendarEventNotificationView())->setViewer($user)->setEvent($event)->setDateTime($user_datetime)->setEpoch($user_epoch);
             $notify_map[$user_phid][] = $view;
         }
     }
     $mail_list = array();
     $mark_list = array();
     $now = PhabricatorTime::getNow();
     foreach ($notify_map as $user_phid => $events) {
         $user = $user_map[$user_phid];
         $locale = PhabricatorEnv::beginScopedLocale($user->getTranslation());
         $caught = null;
         try {
             $mail_list[] = $this->newMailMessage($user, $events);
         } catch (Exception $ex) {
             $caught = $ex;
         }
         unset($locale);
         if ($caught) {
             throw $ex;
         }
         foreach ($events as $view) {
             $event = $view->getEvent();
             foreach ($event->getNotificationPHIDs() as $phid) {
                 $mark_list[] = qsprintf($conn, '(%s, %s, %d, %d)', $phid, $user_phid, $event->getUTCInitialEpoch(), $now);
             }
         }
     }
     // Mark all the notifications we're about to send as delivered so we
     // do not double-notify.
     foreach (PhabricatorLiskDAO::chunkSQL($mark_list) as $chunk) {
         queryfx($conn, 'INSERT IGNORE INTO %T
       (eventPHID, targetPHID, utcInitialEpoch, didNotifyEpoch)
       VALUES %Q', $table->getTableName(), $chunk);
     }
     foreach ($mail_list as $mail) {
         $mail->saveAndSend();
     }
 }
 /**
  * @task mail
  */
 private function buildMail(PhabricatorLiskDAO $object, array $xactions)
 {
     $email_to = $this->mailToPHIDs;
     $email_cc = $this->mailCCPHIDs;
     $email_cc = array_merge($email_cc, $this->heraldEmailPHIDs);
     $targets = $this->buildReplyHandler($object)->getMailTargets($email_to, $email_cc);
     // Set this explicitly before we start swapping out the effective actor.
     $this->setActingAsPHID($this->getActingAsPHID());
     $messages = array();
     foreach ($targets as $target) {
         $original_actor = $this->getActor();
         $viewer = $target->getViewer();
         $this->setActor($viewer);
         $locale = PhabricatorEnv::beginScopedLocale($viewer->getTranslation());
         $caught = null;
         $mail = null;
         try {
             // Reload handles for the new viewer.
             $this->loadHandles($xactions);
             $mail = $this->buildMailForTarget($object, $xactions, $target);
         } catch (Exception $ex) {
             $caught = $ex;
         }
         $this->setActor($original_actor);
         unset($locale);
         if ($caught) {
             throw $ex;
         }
         if ($mail) {
             $messages[] = $mail;
         }
     }
     $this->runHeraldMailRules($messages);
     return $messages;
 }