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')); }
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; }