public function execute(PhutilArgumentParser $args)
 {
     $console = PhutilConsole::getConsole();
     $viewer = $this->getViewer();
     $since = PhabricatorTime::getNow() - phutil_units('30 days in seconds');
     $until = PhabricatorTime::getNow();
     $mails = id(new PhabricatorMetaMTAMailQuery())->setViewer($viewer)->withDateCreatedBetween($since, $until)->execute();
     $unfiltered = array();
     foreach ($mails as $mail) {
         $unfiltered_actors = mpull($mail->loadAllActors(), 'getPHID');
         foreach ($unfiltered_actors as $phid) {
             if (empty($unfiltered[$phid])) {
                 $unfiltered[$phid] = 0;
             }
             $unfiltered[$phid]++;
         }
     }
     arsort($unfiltered);
     $table = id(new PhutilConsoleTable())->setBorders(true)->addColumn('user', array('title' => pht('User')))->addColumn('unfiltered', array('title' => pht('Unfiltered')));
     $handles = $viewer->loadHandles(array_keys($unfiltered));
     $names = mpull(iterator_to_array($handles), 'getName', 'getPHID');
     foreach ($unfiltered as $phid => $count) {
         $table->addRow(array('user' => idx($names, $phid), 'unfiltered' => $count));
     }
     $table->draw();
     echo "\n";
     echo pht('Mail sent in the last 30 days.') . "\n";
     echo pht('"Unfiltered" is raw volume before preferences were applied.') . "\n";
     echo "\n";
     return 0;
 }
 public function renderModuleStatus(AphrontRequest $request)
 {
     $viewer = $request->getViewer();
     $collectors = PhabricatorGarbageCollector::getAllCollectors();
     $collectors = msort($collectors, 'getCollectorConstant');
     $rows = array();
     $rowc = array();
     foreach ($collectors as $key => $collector) {
         $class = null;
         if ($collector->hasAutomaticPolicy()) {
             $policy_view = phutil_tag('em', array(), pht('Automatic'));
         } else {
             $policy = $collector->getRetentionPolicy();
             if ($policy === null) {
                 $policy_view = pht('Indefinite');
             } else {
                 $days = ceil($policy / phutil_units('1 day in seconds'));
                 $policy_view = pht('%s Day(s)', new PhutilNumber($days));
             }
             $default = $collector->getDefaultRetentionPolicy();
             if ($policy !== $default) {
                 $class = 'highlighted';
                 $policy_view = phutil_tag('strong', array(), $policy_view);
             }
         }
         $rowc[] = $class;
         $rows[] = array($collector->getCollectorConstant(), $collector->getCollectorName(), $policy_view);
     }
     $table = id(new AphrontTableView($rows))->setRowClasses($rowc)->setHeaders(array(pht('Constant'), pht('Name'), pht('Retention Policy')))->setColumnClasses(array(null, 'pri wide', null));
     $header = id(new PHUIHeaderView())->setHeader(pht('Garbage Collectors'))->setSubheader(pht('Collectors with custom policies are highlighted. Use ' . '%s to change retention policies.', phutil_tag('tt', array(), 'bin/garbage set-policy')));
     return id(new PHUIObjectBoxView())->setHeader($header)->setTable($table);
 }
 protected final function executeQuery()
 {
     $future = $this->newQueryFuture();
     $drequest = $this->getRequest();
     $name = basename($drequest->getPath());
     $ttl = PhabricatorTime::getNow() + phutil_units('48 hours in seconds');
     try {
         $threshold = PhabricatorFileStorageEngine::getChunkThreshold();
         $future->setReadBufferSize($threshold);
         $source = id(new PhabricatorExecFutureFileUploadSource())->setName($name)->setTTL($ttl)->setViewPolicy(PhabricatorPolicies::POLICY_NOONE)->setExecFuture($future);
         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         $file = $source->uploadFile();
         unset($unguarded);
     } catch (CommandException $ex) {
         if (!$future->getWasKilledByTimeout()) {
             throw $ex;
         }
         $this->didHitTimeLimit = true;
         $file = null;
     }
     $byte_limit = $this->getByteLimit();
     if ($byte_limit && $file->getByteSize() > $byte_limit) {
         $this->didHitByteLimit = true;
         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         id(new PhabricatorDestructionEngine())->destroyObject($file);
         unset($unguarded);
         $file = null;
     }
     return $file;
 }
 public function collectGarbage()
 {
     $ttl = phutil_units('90 days in seconds');
     $table = new MultimeterEvent();
     $conn_w = $table->establishConnection('w');
     queryfx($conn_w, 'DELETE FROM %T WHERE epoch < %d LIMIT 100', $table->getTableName(), PhabricatorTime::getNow() - $ttl);
     return $conn_w->getAffectedRows() == 100;
 }
 public function collectGarbage()
 {
     $ttl = phutil_units('90 days in seconds');
     $table = new PhabricatorMetaMTAMail();
     $conn_w = $table->establishConnection('w');
     queryfx($conn_w, 'DELETE FROM %T WHERE dateCreated < %d LIMIT 100', $table->getTableName(), time() - $ttl);
     return $conn_w->getAffectedRows() == 100;
 }
 public function collectGarbage()
 {
     $ttl = phutil_units('3 days in seconds');
     $table = new PhabricatorSystemActionLog();
     $conn_w = $table->establishConnection('w');
     queryfx($conn_w, 'DELETE FROM %T WHERE epoch < %d LIMIT 100', $table->getTableName(), time() - $ttl);
     return $conn_w->getAffectedRows() == 100;
 }
 public function collectGarbage()
 {
     $ttl = phutil_units('90 days in seconds');
     $mails = id(new PhabricatorMetaMTAMail())->loadAllWhere('dateCreated < %d LIMIT 100', PhabricatorTime::getNow() - $ttl);
     foreach ($mails as $mail) {
         $mail->delete();
     }
     return count($mails) == 100;
 }
 public function execute(PhutilArgumentParser $args)
 {
     $console = PhutilConsole::getConsole();
     $viewer = $this->getViewer();
     $days = (int) $args->getArg('days');
     if ($days < 1) {
         throw new PhutilArgumentUsageException(pht('Period specified with --days must be at least 1.'));
     }
     $duration = phutil_units("{$days} days in seconds");
     $since = PhabricatorTime::getNow() - $duration;
     $until = PhabricatorTime::getNow();
     $mails = id(new PhabricatorMetaMTAMailQuery())->setViewer($viewer)->withDateCreatedBetween($since, $until)->execute();
     $unfiltered = array();
     $delivered = array();
     foreach ($mails as $mail) {
         // Count messages we attempted to deliver. This includes messages which
         // were voided by preferences or other rules.
         $unfiltered_actors = mpull($mail->loadAllActors(), 'getPHID');
         foreach ($unfiltered_actors as $phid) {
             if (empty($unfiltered[$phid])) {
                 $unfiltered[$phid] = 0;
             }
             $unfiltered[$phid]++;
         }
         // Now, count mail we actually delivered.
         $result = $mail->getDeliveredActors();
         if ($result) {
             foreach ($result as $actor_phid => $actor_info) {
                 if (!$actor_info['deliverable']) {
                     continue;
                 }
                 if (empty($delivered[$actor_phid])) {
                     $delivered[$actor_phid] = 0;
                 }
                 $delivered[$actor_phid]++;
             }
         }
     }
     // Sort users by delivered mail, then unfiltered mail.
     arsort($delivered);
     arsort($unfiltered);
     $delivered = $delivered + array_fill_keys(array_keys($unfiltered), 0);
     $table = id(new PhutilConsoleTable())->setBorders(true)->addColumn('user', array('title' => pht('User')))->addColumn('unfiltered', array('title' => pht('Unfiltered')))->addColumn('delivered', array('title' => pht('Delivered')));
     $handles = $viewer->loadHandles(array_keys($unfiltered));
     $names = mpull(iterator_to_array($handles), 'getName', 'getPHID');
     foreach ($delivered as $phid => $delivered_count) {
         $unfiltered_count = idx($unfiltered, $phid, 0);
         $table->addRow(array('user' => idx($names, $phid), 'unfiltered' => $unfiltered_count, 'delivered' => $delivered_count));
     }
     $table->draw();
     echo "\n";
     echo pht('Mail sent in the last %s day(s).', new PhutilNumber($days)) . "\n";
     echo pht('"Unfiltered" is raw volume before rules applied.') . "\n";
     echo pht('"Delivered" shows email actually sent.') . "\n";
     echo "\n";
     return 0;
 }
 public static function newHTTPAuthorization(PhabricatorRepository $repository, PhabricatorUser $viewer, $operation)
 {
     $lfs_user = self::HTTP_USERNAME;
     $lfs_pass = Filesystem::readRandomCharacters(32);
     $lfs_hash = PhabricatorHash::digest($lfs_pass);
     $ttl = PhabricatorTime::getNow() + phutil_units('1 day in seconds');
     $token = id(new PhabricatorAuthTemporaryToken())->setTokenResource($repository->getPHID())->setTokenType(self::TOKENTYPE)->setTokenCode($lfs_hash)->setUserPHID($viewer->getPHID())->setTemporaryTokenProperty('lfs.operation', $operation)->setTokenExpires($ttl)->save();
     $authorization_header = base64_encode($lfs_user . ':' . $lfs_pass);
     return 'Basic ' . $authorization_header;
 }
 public function execute(PhutilArgumentParser $args)
 {
     $viewer = $this->getViewer();
     $engine = new PhabricatorCalendarNotificationEngine();
     $minutes = $args->getArg('minutes');
     if ($minutes) {
         $engine->setNotifyWindow(phutil_units("{$minutes} minutes in seconds"));
     }
     $engine->publishNotifications();
     return 0;
 }
 public function testICSDuration()
 {
     $event = $this->parseICSSingleEvent('duration.ics');
     // Raw value is "20160719T095722Z".
     $start_epoch = strtotime('2016-07-19 09:57:22 UTC');
     $this->assertEqual(1468922242, $start_epoch);
     // Raw value is "P1DT17H4M23S".
     $duration = phutil_units('1 day in seconds') + phutil_units('17 hours in seconds') + phutil_units('4 minutes in seconds') + phutil_units('23 seconds in seconds');
     $this->assertEqual($start_epoch, $event->getStartDateTime()->getEpoch());
     $this->assertEqual($start_epoch + $duration, $event->getEndDateTime()->getEpoch());
 }
 public static function getSessionTypeTTL($session_type)
 {
     switch ($session_type) {
         case self::TYPE_WEB:
             return phutil_units('30 days in seconds');
         case self::TYPE_CONDUIT:
             return phutil_units('24 hours in seconds');
         default:
             throw new Exception(pht('Unknown session type "%s".', $session_type));
     }
 }
 public function processAddFactorForm(AphrontFormView $form, AphrontRequest $request, PhabricatorUser $user)
 {
     $totp_token_type = PhabricatorAuthTOTPKeyTemporaryTokenType::TOKENTYPE;
     $key = $request->getStr('totpkey');
     if (strlen($key)) {
         // If the user is providing a key, make sure it's a key we generated.
         // This raises the barrier to theoretical attacks where an attacker might
         // provide a known key (such attacks are already prevented by CSRF, but
         // this is a second barrier to overcome).
         // (We store and verify the hash of the key, not the key itself, to limit
         // how useful the data in the table is to an attacker.)
         $temporary_token = id(new PhabricatorAuthTemporaryTokenQuery())->setViewer($user)->withTokenResources(array($user->getPHID()))->withTokenTypes(array($totp_token_type))->withExpired(false)->withTokenCodes(array(PhabricatorHash::digest($key)))->executeOne();
         if (!$temporary_token) {
             // If we don't have a matching token, regenerate the key below.
             $key = null;
         }
     }
     if (!strlen($key)) {
         $key = self::generateNewTOTPKey();
         // Mark this key as one we generated, so the user is allowed to submit
         // a response for it.
         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         id(new PhabricatorAuthTemporaryToken())->setTokenResource($user->getPHID())->setTokenType($totp_token_type)->setTokenExpires(time() + phutil_units('1 hour in seconds'))->setTokenCode(PhabricatorHash::digest($key))->save();
         unset($unguarded);
     }
     $code = $request->getStr('totpcode');
     $e_code = true;
     if ($request->getExists('totp')) {
         $okay = self::verifyTOTPCode($user, new PhutilOpaqueEnvelope($key), $code);
         if ($okay) {
             $config = $this->newConfigForUser($user)->setFactorName(pht('Mobile App (TOTP)'))->setFactorSecret($key);
             return $config;
         } else {
             if (!strlen($code)) {
                 $e_code = pht('Required');
             } else {
                 $e_code = pht('Invalid');
             }
         }
     }
     $form->addHiddenInput('totp', true);
     $form->addHiddenInput('totpkey', $key);
     $form->appendRemarkupInstructions(pht('First, download an authenticator application on your phone. Two ' . 'applications which work well are **Authy** and **Google ' . 'Authenticator**, but any other TOTP application should also work.'));
     $form->appendInstructions(pht('Launch the application on your phone, and add a new entry for ' . 'this Phabricator install. When prompted, scan the QR code or ' . 'manually enter the key shown below into the application.'));
     $prod_uri = new PhutilURI(PhabricatorEnv::getProductionURI('/'));
     $issuer = $prod_uri->getDomain();
     $uri = urisprintf('otpauth://totp/%s:%s?secret=%s&issuer=%s', $issuer, $user->getUsername(), $key, $issuer);
     $qrcode = $this->renderQRCode($uri);
     $form->appendChild($qrcode);
     $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Key'))->setValue(phutil_tag('strong', array(), $key)));
     $form->appendInstructions(pht('(If given an option, select that this key is "Time Based", not ' . '"Counter Based".)'));
     $form->appendInstructions(pht('After entering the key, the application should display a numeric ' . 'code. Enter that code below to confirm that you have configured ' . 'the authenticator correctly:'));
     $form->appendChild(id(new PHUIFormNumberControl())->setLabel(pht('TOTP Code'))->setName('totpcode')->setValue($code)->setError($e_code));
 }
 private function newFile(DiffusionRequest $drequest, $content)
 {
     $path = $drequest->getPath();
     $name = basename($path);
     $repository = $drequest->getRepository();
     $repository_phid = $repository->getPHID();
     $file = PhabricatorFile::buildFromFileDataOrHash($content, array('name' => $name, 'ttl' => time() + phutil_units('48 hours in seconds'), 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE));
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     $file->attachToObject($repository_phid);
     unset($unguarded);
     return $file;
 }
 public function handleRequest(AphrontRequest $request)
 {
     $show_prototypes = PhabricatorEnv::getEnvConfig('phabricator.show-prototypes');
     if (!$show_prototypes) {
         throw new Exception(pht('Show prototypes is disabled.
       Set `phabricator.show-prototypes` to `true` to use the image proxy'));
     }
     $viewer = $request->getViewer();
     $img_uri = $request->getStr('uri');
     // Validate the URI before doing anything
     PhabricatorEnv::requireValidRemoteURIForLink($img_uri);
     $uri = new PhutilURI($img_uri);
     $proto = $uri->getProtocol();
     if (!in_array($proto, array('http', 'https'))) {
         throw new Exception(pht('The provided image URI must be either http or https'));
     }
     // Check if we already have the specified image URI downloaded
     $cached_request = id(new PhabricatorFileExternalRequest())->loadOneWhere('uriIndex = %s', PhabricatorHash::digestForIndex($img_uri));
     if ($cached_request) {
         return $this->getExternalResponse($cached_request);
     }
     $ttl = PhabricatorTime::getNow() + phutil_units('7 days in seconds');
     $external_request = id(new PhabricatorFileExternalRequest())->setURI($img_uri)->setTTL($ttl);
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     // Cache missed so we'll need to validate and download the image
     try {
         // Rate limit outbound fetches to make this mechanism less useful for
         // scanning networks and ports.
         PhabricatorSystemActionEngine::willTakeAction(array($viewer->getPHID()), new PhabricatorFilesOutboundRequestAction(), 1);
         $file = PhabricatorFile::newFromFileDownload($uri, array('viewPolicy' => PhabricatorPolicies::POLICY_NOONE, 'canCDN' => true));
         if (!$file->isViewableImage()) {
             $mime_type = $file->getMimeType();
             $engine = new PhabricatorDestructionEngine();
             $engine->destroyObject($file);
             $file = null;
             throw new Exception(pht('The URI "%s" does not correspond to a valid image file, got ' . 'a file with MIME type "%s". You must specify the URI of a ' . 'valid image file.', $uri, $mime_type));
         } else {
             $file->save();
         }
         $external_request->setIsSuccessful(true)->setFilePHID($file->getPHID())->save();
         unset($unguarded);
         return $this->getExternalResponse($external_request);
     } catch (HTTPFutureHTTPResponseStatus $status) {
         $external_request->setIsSuccessful(false)->setResponseMessage($status->getMessage())->save();
         return $this->getExternalResponse($external_request);
     } catch (Exception $ex) {
         // Not actually saving the request in this case
         $external_request->setResponseMessage($ex->getMessage());
         return $this->getExternalResponse($external_request);
     }
 }
 private function getTokenExpires($token_type)
 {
     $now = PhabricatorTime::getNow();
     switch ($token_type) {
         case self::TYPE_STANDARD:
             return null;
         case self::TYPE_COMMANDLINE:
             return $now + phutil_units('1 hour in seconds');
         case self::TYPE_CLUSTER:
             return $now + phutil_units('30 minutes in seconds');
         default:
             throw new Exception(pht('Unknown Conduit token type "%s"!', $token_type));
     }
 }
 public function execute(PhutilArgumentParser $args)
 {
     $config_key = 'phd.garbage-collection';
     $collector = $this->getCollector($args->getArg('collector'));
     $days = $args->getArg('days');
     $indefinite = $args->getArg('indefinite');
     $default = $args->getArg('default');
     $count = 0;
     if ($days !== null) {
         $count++;
     }
     if ($indefinite) {
         $count++;
     }
     if ($default) {
         $count++;
     }
     if (!$count) {
         throw new PhutilArgumentUsageException(pht('Choose a policy with "%s", "%s" or "%s".', '--days', '--indefinite', '--default'));
     }
     if ($count > 1) {
         throw new PhutilArgumentUsageException(pht('Options "%s", "%s" and "%s" represent mutually exclusive ways ' . 'to choose a policy. Specify only one.', '--days', '--indefinite', '--default'));
     }
     if ($days !== null) {
         $days = (int) $days;
         if ($days < 1) {
             throw new PhutilArgumentUsageException(pht('Specify a positive number of days to retain data for.'));
         }
     }
     $collector_const = $collector->getCollectorConstant();
     $value = PhabricatorEnv::getEnvConfig($config_key);
     if ($days !== null) {
         echo tsprintf("%s\n", pht('Setting retention policy for "%s" to %s day(s).', $collector->getCollectorName(), new PhutilNumber($days)));
         $value[$collector_const] = phutil_units($days . ' days in seconds');
     } else {
         if ($indefinite) {
             echo tsprintf("%s\n", pht('Setting "%s" to be retained indefinitely.', $collector->getCollectorName()));
             $value[$collector_const] = null;
         } else {
             echo tsprintf("%s\n", pht('Restoring "%s" to the default retention policy.', $collector->getCollectorName()));
             unset($value[$collector_const]);
         }
     }
     id(new PhabricatorConfigLocalSource())->setKeys(array($config_key => $value));
     echo tsprintf("%s\n", pht('Wrote new policy to local configuration.'));
     echo tsprintf("%s\n", pht('This change will take effect the next time the daemons are ' . 'restarted.'));
     return 0;
 }
 public function processRequest()
 {
     $out = array();
     // Prevent indexing of '/diffusion/', since the content is not generally
     // useful to index, web spiders get stuck scraping the history of every
     // file, and much of the content is Ajaxed in anyway so spiders won't even
     // see it. These pages are also relatively expensive to generate.
     // Note that this still allows commits (at '/rPxxxxx') to be indexed.
     // They're probably not hugely useful, but suffer fewer of the problems
     // Diffusion suffers and are hard to omit with 'robots.txt'.
     $out[] = 'User-Agent: *';
     $out[] = 'Disallow: /diffusion/';
     // Add a small crawl delay (number of seconds between requests) for spiders
     // which respect it. The intent here is to prevent spiders from affecting
     // performance for users. The possible cost is slower indexing, but that
     // seems like a reasonable tradeoff, since most Phabricator installs are
     // probably not hugely concerned about cutting-edge SEO.
     $out[] = 'Crawl-delay: 1';
     $content = implode("\n", $out) . "\n";
     return id(new AphrontPlainTextResponse())->setContent($content)->setCacheDurationInSeconds(phutil_units('2 hours in seconds'));
 }
Beispiel #19
0
 public function getHeaders()
 {
     $headers = array();
     if (!$this->frameable) {
         $headers[] = array('X-Frame-Options', 'Deny');
     }
     if ($this->getRequest() && $this->getRequest()->isHTTPS()) {
         $hsts_key = 'security.strict-transport-security';
         $use_hsts = PhabricatorEnv::getEnvConfig($hsts_key);
         if ($use_hsts) {
             $duration = phutil_units('365 days in seconds');
         } else {
             // If HSTS has been disabled, tell browsers to turn it off. This may
             // not be effective because we can only disable it over a valid HTTPS
             // connection, but it best represents the configured intent.
             $duration = 0;
         }
         $headers[] = array('Strict-Transport-Security', "max-age={$duration}; includeSubdomains; preload");
     }
     return $headers;
 }
 public function run()
 {
     $is_temporary = $this->getArgument('temporary');
     $conduit = $this->getConduit();
     $results = array();
     $uploader = id(new ArcanistFileUploader())->setConduitClient($conduit);
     foreach ($this->paths as $key => $path) {
         $file = id(new ArcanistFileDataRef())->setName(basename($path))->setPath($path);
         if ($is_temporary) {
             $expires_at = time() + phutil_units('24 hours in seconds');
             $file->setDeleteAfterEpoch($expires_at);
         }
         $uploader->addFile($file);
     }
     $files = $uploader->uploadFiles();
     $results = array();
     foreach ($files as $file) {
         // TODO: This could be handled more gracefully; just preserving behavior
         // until we introduce `file.query` and modernize this.
         if ($file->getErrors()) {
             throw new Exception(implode("\n", $file->getErrors()));
         }
         $phid = $file->getPHID();
         $name = $file->getName();
         $info = $conduit->callMethodSynchronous('file.info', array('phid' => $phid));
         $results[$path] = $info;
         if (!$this->json) {
             $id = $info['id'];
             echo "  F{$id} {$name}: " . $info['uri'] . "\n\n";
         }
     }
     if ($this->json) {
         echo json_encode($results) . "\n";
     } else {
         $this->writeStatus(pht('Done.'));
     }
     return 0;
 }
 /**
  * Awaken tasks that have yielded.
  *
  * Reschedules the specified tasks if they are currently queued in a yielded,
  * unleased, unretried state so they'll execute sooner. This can let the
  * queue avoid unnecessary waits.
  *
  * This method does not provide any assurances about when these tasks will
  * execute, or even guarantee that it will have any effect at all.
  *
  * @param list<id> List of task IDs to try to awaken.
  * @return void
  */
 public static final function awakenTaskIDs(array $ids)
 {
     if (!$ids) {
         return;
     }
     $table = new PhabricatorWorkerActiveTask();
     $conn_w = $table->establishConnection('w');
     // NOTE: At least for now, we're keeping these tasks yielded, just
     // pretending that they threw a shorter yield than they really did.
     // Overlap the windows here to handle minor client/server time differences
     // and because it's likely correct to push these tasks to the head of their
     // respective priorities. There is a good chance they are ready to execute.
     $window = phutil_units('1 hour in seconds');
     $epoch_ago = PhabricatorTime::getNow() - $window;
     queryfx($conn_w, 'UPDATE %T SET leaseExpires = %d
     WHERE id IN (%Ld)
       AND leaseOwner = %s
       AND leaseExpires > %d
       AND failureCount = 0', $table->getTableName(), $epoch_ago, $ids, self::YIELD_OWNER, $epoch_ago);
 }
 private function loadOtherRevisions(array $changesets, DifferentialDiff $target, PhabricatorRepository $repository)
 {
     assert_instances_of($changesets, 'DifferentialChangeset');
     $paths = array();
     foreach ($changesets as $changeset) {
         $paths[] = $changeset->getAbsoluteRepositoryPath($repository, $target);
     }
     if (!$paths) {
         return array();
     }
     $path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs();
     if (!$path_map) {
         return array();
     }
     $recent = PhabricatorTime::getNow() - phutil_units('30 days in seconds');
     $query = id(new DifferentialRevisionQuery())->setViewer($this->getRequest()->getUser())->withStatus(DifferentialRevisionQuery::STATUS_OPEN)->withUpdatedEpochBetween($recent, null)->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED)->setLimit(10)->needFlags(true)->needDrafts(true)->needRelationships(true);
     foreach ($path_map as $path => $path_id) {
         $query->withPath($repository->getID(), $path_id);
     }
     $results = $query->execute();
     // Strip out *this* revision.
     foreach ($results as $key => $result) {
         if ($result->getID() == $this->revisionID) {
             unset($results[$key]);
         }
     }
     return $results;
 }
 protected function buildOpenRevisions()
 {
     $user = $this->getRequest()->getUser();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
     $path = $drequest->getPath();
     $path_map = id(new DiffusionPathIDQuery(array($path)))->loadPathIDs();
     $path_id = idx($path_map, $path);
     if (!$path_id) {
         return null;
     }
     $recent = PhabricatorTime::getNow() - phutil_units('30 days in seconds');
     $revisions = id(new DifferentialRevisionQuery())->setViewer($user)->withPath($repository->getID(), $path_id)->withStatus(DifferentialRevisionQuery::STATUS_OPEN)->withUpdatedEpochBetween($recent, null)->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED)->setLimit(10)->needRelationships(true)->needFlags(true)->needDrafts(true)->execute();
     if (!$revisions) {
         return null;
     }
     $header = id(new PHUIHeaderView())->setHeader(pht('Open Revisions'))->setSubheader(pht('Recently updated open revisions affecting this file.'));
     $view = id(new DifferentialRevisionListView())->setHeader($header)->setRevisions($revisions)->setUser($user);
     $phids = $view->getRequiredHandlePHIDs();
     $handles = $this->loadViewerHandles($phids);
     $view->setHandles($handles);
     return $view;
 }
 public function testPhutilUnits()
 {
     $cases = array('0 seconds in seconds' => 0, '1 second in seconds' => 1, '2 seconds in seconds' => 2, '100 seconds in seconds' => 100, '2 minutes in seconds' => 120, '1 hour in seconds' => 3600, '1 day in seconds' => 86400, '3 days in seconds' => 259200);
     foreach ($cases as $input => $expect) {
         $this->assertEqual($expect, phutil_units($input), 'phutil_units("' . $input . '")');
     }
     $bad_cases = array('quack', '3 years in seconds', '1 minute in milliseconds', '1 day in days', '-1 minutes in seconds', '1.5 minutes in seconds');
     foreach ($bad_cases as $input) {
         $caught = null;
         try {
             phutil_units($input);
         } catch (InvalidArgumentException $ex) {
             $caught = $ex;
         }
         $this->assertTrue($caught instanceof InvalidArgumentException, 'phutil_units("' . $input . '")');
     }
 }
 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();
     }
 }
 /**
  * Retrieve a temporary, one-time URI which can log in to an account.
  *
  * These URIs are used for password recovery and to regain access to accounts
  * which users have been locked out of.
  *
  * @param PhabricatorUser User to generate a URI for.
  * @param PhabricatorUserEmail Optionally, email to verify when
  *  link is used.
  * @param string Optional context string for the URI. This is purely cosmetic
  *  and used only to customize workflow and error messages.
  * @return string Login URI.
  * @task onetime
  */
 public function getOneTimeLoginURI(PhabricatorUser $user, PhabricatorUserEmail $email = null, $type = self::ONETIME_RESET)
 {
     $key = Filesystem::readRandomCharacters(32);
     $key_hash = $this->getOneTimeLoginKeyHash($user, $email, $key);
     $onetime_type = PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE;
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     id(new PhabricatorAuthTemporaryToken())->setTokenResource($user->getPHID())->setTokenType($onetime_type)->setTokenExpires(time() + phutil_units('1 day in seconds'))->setTokenCode($key_hash)->save();
     unset($unguarded);
     $uri = '/login/once/' . $type . '/' . $user->getID() . '/' . $key . '/';
     if ($email) {
         $uri = $uri . $email->getID() . '/';
     }
     try {
         $uri = PhabricatorEnv::getProductionURI($uri);
     } catch (Exception $ex) {
         // If a user runs `bin/auth recover` before configuring the base URI,
         // just show the path. We don't have any way to figure out the domain.
         // See T4132.
     }
     return $uri;
 }
 public function getDefaultRetentionPolicy()
 {
     return phutil_units('90 days in seconds');
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     $group_map = $this->getColumnMap();
     $group = explode('.', $request->getStr('group'));
     $group = array_intersect($group, array_keys($group_map));
     $group = array_fuse($group);
     if (empty($group['type'])) {
         $group['type'] = 'type';
     }
     $now = PhabricatorTime::getNow();
     $ago = $now - phutil_units('24 hours in seconds');
     $table = new MultimeterEvent();
     $conn = $table->establishConnection('r');
     $where = array();
     $where[] = qsprintf($conn, 'epoch >= %d AND epoch <= %d', $ago, $now);
     $with = array();
     foreach ($group_map as $key => $column) {
         // Don't let non-admins filter by viewers, this feels a little too
         // invasive of privacy.
         if ($key == 'viewer') {
             if (!$viewer->getIsAdmin()) {
                 continue;
             }
         }
         $with[$key] = $request->getStrList($key);
         if ($with[$key]) {
             $where[] = qsprintf($conn, '%T IN (%Ls)', $column, $with[$key]);
         }
     }
     $where = '(' . implode(') AND (', $where) . ')';
     $data = queryfx_all($conn, 'SELECT *,
       count(*) AS N,
       SUM(sampleRate * resourceCost) AS totalCost,
       SUM(sampleRate * resourceCost) / SUM(sampleRate) AS averageCost
     FROM %T
     WHERE %Q
     GROUP BY %Q
     ORDER BY totalCost DESC, MAX(id) DESC
     LIMIT 100', $table->getTableName(), $where, implode(', ', array_select_keys($group_map, $group)));
     $this->loadDimensions($data);
     $phids = array();
     foreach ($data as $row) {
         $viewer_name = $this->getViewerDimension($row['eventViewerID'])->getName();
         $viewer_phid = $this->getEventViewerPHID($viewer_name);
         if ($viewer_phid) {
             $phids[] = $viewer_phid;
         }
     }
     $handles = $viewer->loadHandles($phids);
     $rows = array();
     foreach ($data as $row) {
         if ($row['N'] == 1) {
             $events_col = $row['id'];
         } else {
             $events_col = $this->renderGroupingLink($group, 'id', pht('%s Event(s)', new PhutilNumber($row['N'])));
         }
         if (isset($group['request'])) {
             $request_col = $row['requestKey'];
             if (!$with['request']) {
                 $request_col = $this->renderSelectionLink('request', $row['requestKey'], $request_col);
             }
         } else {
             $request_col = $this->renderGroupingLink($group, 'request');
         }
         if (isset($group['viewer'])) {
             if ($viewer->getIsAdmin()) {
                 $viewer_col = $this->getViewerDimension($row['eventViewerID'])->getName();
                 $viewer_phid = $this->getEventViewerPHID($viewer_col);
                 if ($viewer_phid) {
                     $viewer_col = $handles[$viewer_phid]->getName();
                 }
                 if (!$with['viewer']) {
                     $viewer_col = $this->renderSelectionLink('viewer', $row['eventViewerID'], $viewer_col);
                 }
             } else {
                 $viewer_col = phutil_tag('em', array(), pht('(Masked)'));
             }
         } else {
             $viewer_col = $this->renderGroupingLink($group, 'viewer');
         }
         if (isset($group['context'])) {
             $context_col = $this->getContextDimension($row['eventContextID'])->getName();
             if (!$with['context']) {
                 $context_col = $this->renderSelectionLink('context', $row['eventContextID'], $context_col);
             }
         } else {
             $context_col = $this->renderGroupingLink($group, 'context');
         }
         if (isset($group['host'])) {
             $host_col = $this->getHostDimension($row['eventHostID'])->getName();
             if (!$with['host']) {
                 $host_col = $this->renderSelectionLink('host', $row['eventHostID'], $host_col);
             }
         } else {
             $host_col = $this->renderGroupingLink($group, 'host');
         }
         if (isset($group['label'])) {
             $label_col = $this->getLabelDimension($row['eventLabelID'])->getName();
             if (!$with['label']) {
                 $label_col = $this->renderSelectionLink('label', $row['eventLabelID'], $label_col);
             }
         } else {
             $label_col = $this->renderGroupingLink($group, 'label');
         }
         if ($with['type']) {
             $type_col = MultimeterEvent::getEventTypeName($row['eventType']);
         } else {
             $type_col = $this->renderSelectionLink('type', $row['eventType'], MultimeterEvent::getEventTypeName($row['eventType']));
         }
         $rows[] = array($events_col, $request_col, $viewer_col, $context_col, $host_col, $type_col, $label_col, MultimeterEvent::formatResourceCost($viewer, $row['eventType'], $row['averageCost']), MultimeterEvent::formatResourceCost($viewer, $row['eventType'], $row['totalCost']), $row['N'] == 1 ? $row['sampleRate'] : '-', phabricator_datetime($row['epoch'], $viewer));
     }
     $table = id(new AphrontTableView($rows))->setHeaders(array(pht('ID'), pht('Request'), pht('Viewer'), pht('Context'), pht('Host'), pht('Type'), pht('Label'), pht('Avg'), pht('Cost'), pht('Rate'), pht('Epoch')))->setColumnClasses(array(null, null, null, null, null, null, 'wide', 'n', 'n', 'n', null));
     $box = id(new PHUIObjectBoxView())->setHeaderText(pht('Samples'))->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)->setTable($table);
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Samples'), $this->getGroupURI(array(), true));
     $crumbs->setBorder(true);
     $crumb_map = array('host' => pht('By Host'), 'context' => pht('By Context'), 'viewer' => pht('By Viewer'), 'request' => pht('By Request'), 'label' => pht('By Label'), 'id' => pht('By ID'));
     $parts = array();
     foreach ($group as $item) {
         if ($item == 'type') {
             continue;
         }
         $parts[$item] = $item;
         $crumbs->addTextCrumb(idx($crumb_map, $item, $item), $this->getGroupURI($parts, true));
     }
     $header = id(new PHUIHeaderView())->setHeader(pht('Samples (%s - %s)', phabricator_datetime($ago, $viewer), phabricator_datetime($now, $viewer)))->setHeaderIcon('fa-motorcycle');
     $view = id(new PHUITwoColumnView())->setHeader($header)->setFooter($box);
     return $this->newPage()->setTitle(pht('Samples'))->setCrumbs($crumbs)->appendChild($view);
 }
 /**
  * Load the pull frequency for this repository, based on the time since the
  * last activity.
  *
  * We pull rarely used repositories less frequently. This finds the most
  * recent commit which is older than the current time (which prevents us from
  * spinning on repositories with a silly commit post-dated to some time in
  * 2037). We adjust the pull frequency based on when the most recent commit
  * occurred.
  *
  * @param   int   The minimum update interval to use, in seconds.
  * @return  int   Repository update interval, in seconds.
  */
 public function loadUpdateInterval($minimum = 15)
 {
     // If a repository is still importing, always pull it as frequently as
     // possible. This prevents us from hanging for a long time at 99.9% when
     // importing an inactive repository.
     if ($this->isImporting()) {
         return $minimum;
     }
     $window_start = PhabricatorTime::getNow() + $minimum;
     $table = id(new PhabricatorRepositoryCommit());
     $last_commit = queryfx_one($table->establishConnection('r'), 'SELECT epoch FROM %T
     WHERE repositoryID = %d AND epoch <= %d
     ORDER BY epoch DESC LIMIT 1', $table->getTableName(), $this->getID(), $window_start);
     if ($last_commit) {
         $time_since_commit = $window_start - $last_commit['epoch'];
         $last_few_days = phutil_units('3 days in seconds');
         if ($time_since_commit <= $last_few_days) {
             // For repositories with activity in the recent past, we wait one
             // extra second for every 10 minutes since the last commit. This
             // shorter backoff is intended to handle weekends and other short
             // breaks from development.
             $smart_wait = $time_since_commit / 600;
         } else {
             // For repositories without recent activity, we wait one extra second
             // for every 4 minutes since the last commit. This longer backoff
             // handles rarely used repositories, up to the maximum.
             $smart_wait = $time_since_commit / 240;
         }
         // We'll never wait more than 6 hours to pull a repository.
         $longest_wait = phutil_units('6 hours in seconds');
         $smart_wait = min($smart_wait, $longest_wait);
         $smart_wait = max($minimum, $smart_wait);
     } else {
         $smart_wait = $minimum;
     }
     return $smart_wait;
 }
 /**
  * Update garbage collection, possibly collecting a small amount of garbage.
  *
  * @return bool True if there is more garbage to collect.
  * @task garbage
  */
 private function updateGarbageCollection()
 {
     // If we're ready to start the next collection cycle, load all the
     // collectors.
     $next = $this->nextCollection;
     if ($next && PhabricatorTime::getNow() >= $next) {
         $this->nextCollection = null;
         $all_collectors = PhabricatorGarbageCollector::getAllCollectors();
         $this->garbageCollectors = $all_collectors;
     }
     // If we're in a collection cycle, continue collection.
     if ($this->garbageCollectors) {
         foreach ($this->garbageCollectors as $key => $collector) {
             $more_garbage = $collector->runCollector();
             if (!$more_garbage) {
                 unset($this->garbageCollectors[$key]);
             }
             // We only run one collection per call, to prevent triggers from being
             // thrown too far off schedule if there's a lot of garbage to collect.
             break;
         }
         if ($this->garbageCollectors) {
             // If we have more work to do, return true.
             return true;
         }
         // Otherwise, reschedule another cycle in 4 hours.
         $now = PhabricatorTime::getNow();
         $wait = phutil_units('4 hours in seconds');
         $this->nextCollection = $now + $wait;
     }
     return false;
 }