diff --git a/scripts/__init_script__.php b/scripts/__init_script__.php index ea51f78e03..560e8d934a 100644 --- a/scripts/__init_script__.php +++ b/scripts/__init_script__.php @@ -1,77 +1,75 @@ array( 'javelin-util', 'javelin-install', 'javelin-event', 'javelin-stratcom', 'javelin-behavior', 'javelin-request', 'javelin-vector', 'javelin-dom', 'javelin-json', 'javelin-uri', ), 'typeahead.pkg.js' => array( 'javelin-typeahead', 'javelin-typeahead-normalizer', 'javelin-typeahead-source', 'javelin-typeahead-preloaded-source', 'javelin-typeahead-ondemand-source', 'javelin-tokenizer', 'javelin-behavior-aphront-basic-tokenizer', ), 'core.pkg.js' => array( 'javelin-mask', 'javelin-workflow', 'javelin-behavior-workflow', 'javelin-behavior-aphront-form-disable-on-submit', 'phabricator-keyboard-shortcut-manager', 'phabricator-keyboard-shortcut', 'javelin-behavior-phabricator-keyboard-shortcuts', 'javelin-behavior-refresh-csrf', 'javelin-behavior-phabricator-watch-anchor', 'javelin-behavior-phabricator-autofocus', 'phabricator-paste-file-upload', 'phabricator-menu-item', 'phabricator-dropdown-menu', 'javelin-behavior-phabricator-oncopy', 'phabricator-tooltip', 'javelin-behavior-phabricator-tooltips', 'phabricator-prefab', ), 'core.pkg.css' => array( 'phabricator-core-css', 'phabricator-core-buttons-css', 'phabricator-standard-page-view', 'aphront-dialog-view-css', 'aphront-form-view-css', 'aphront-panel-view-css', 'aphront-side-nav-view-css', 'aphront-table-view-css', 'aphront-crumbs-view-css', 'aphront-tokenizer-control-css', 'aphront-typeahead-control-css', 'aphront-list-filter-view-css', 'phabricator-directory-css', 'phabricator-jump-nav', 'phabricator-app-buttons-css', 'phabricator-remarkup-css', 'syntax-highlighting-css', 'aphront-pager-view-css', 'phabricator-transaction-view-css', 'aphront-tooltip-css', 'aphront-headsup-view-css', 'phabricator-flag-css', 'aphront-error-view-css', ), 'differential.pkg.css' => array( 'differential-core-view-css', 'differential-changeset-view-css', 'differential-results-table-css', 'differential-revision-history-css', 'differential-table-of-contents-css', 'differential-revision-comment-css', 'differential-revision-add-comment-css', 'differential-revision-comment-list-css', 'phabricator-object-selector-css', 'aphront-headsup-action-list-view-css', 'phabricator-content-source-view-css', 'differential-local-commits-view-css', 'inline-comment-summary-css', ), 'differential.pkg.js' => array( 'phabricator-drag-and-drop-file-upload', 'phabricator-shaped-request', 'javelin-behavior-differential-feedback-preview', 'javelin-behavior-differential-edit-inline-comments', 'javelin-behavior-differential-populate', 'javelin-behavior-differential-show-more', 'javelin-behavior-differential-diff-radios', 'javelin-behavior-differential-accept-with-errors', 'javelin-behavior-differential-comment-jump', 'javelin-behavior-differential-add-reviewers-and-ccs', 'javelin-behavior-differential-keyboard-navigation', 'javelin-behavior-aphront-drag-and-drop', 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', 'differential-inline-comment-editor', 'javelin-behavior-differential-dropdown-menus', 'javelin-behavior-buoyant', ), 'diffusion.pkg.css' => array( 'diffusion-commit-view-css', 'diffusion-icons-css', ), 'diffusion.pkg.js' => array( 'javelin-behavior-diffusion-pull-lastmodified', 'javelin-behavior-diffusion-commit-graph', 'javelin-behavior-audit-preview', ), 'maniphest.pkg.css' => array( 'maniphest-task-summary-css', 'maniphest-transaction-detail-css', 'aphront-attached-file-view-css', 'phabricator-project-tag-css', ), 'maniphest.pkg.js' => array( 'javelin-behavior-maniphest-batch-selector', 'javelin-behavior-maniphest-transaction-controls', 'javelin-behavior-maniphest-transaction-preview', 'javelin-behavior-maniphest-transaction-expand', 'javelin-behavior-maniphest-subpriority-editor', ), ); require_once dirname(__FILE__).'/__init_script__.php'; if ($argc != 2) { $self = basename($argv[0]); echo "usage: {$self} \n"; exit(1); } -phutil_require_module('phutil', 'filesystem'); -phutil_require_module('phutil', 'filesystem/filefinder'); -phutil_require_module('phutil', 'future/exec'); -phutil_require_module('phutil', 'parser/docblock'); - $root = Filesystem::resolvePath($argv[1]); $resource_hash = PhabricatorEnv::getEnvConfig('celerity.resource-hash'); $runtime_map = array(); echo "Finding raw static resources...\n"; $raw_files = id(new FileFinder($root)) ->withType('f') ->withSuffix('png') ->withSuffix('jpg') ->withSuffix('gif') ->withSuffix('swf') ->withFollowSymlinks(true) ->setGenerateChecksums(true) ->find(); echo "Processing ".count($raw_files)." files"; foreach ($raw_files as $path => $hash) { echo "."; $path = '/'.Filesystem::readablePath($path, $root); $type = CelerityResourceTransformer::getResourceType($path); $hash = md5($hash.$path.$resource_hash); $uri = '/res/'.substr($hash, 0, 8).$path; $runtime_map[$path] = array( 'hash' => $hash, 'uri' => $uri, 'disk' => $path, 'type' => $type, ); } echo "\n"; $xformer = id(new CelerityResourceTransformer()) ->setMinify(false) ->setRawResourceMap($runtime_map); echo "Finding transformable static resources...\n"; $files = id(new FileFinder($root)) ->withType('f') ->withSuffix('js') ->withSuffix('css') ->withFollowSymlinks(true) ->setGenerateChecksums(true) ->find(); echo "Processing ".count($files)." files"; $file_map = array(); foreach ($files as $path => $raw_hash) { echo "."; $path = '/'.Filesystem::readablePath($path, $root); $data = Filesystem::readFile($root.$path); $data = $xformer->transformResource($path, $data); $hash = md5($data); $hash = md5($hash.$path.$resource_hash); $file_map[$path] = array( 'hash' => $hash, 'disk' => $path, ); } echo "\n"; $resource_graph = array(); $hash_map = array(); $parser = new PhutilDocblockParser(); foreach ($file_map as $path => $info) { $type = CelerityResourceTransformer::getResourceType($path); $data = Filesystem::readFile($root.$info['disk']); $matches = array(); $ok = preg_match('@/[*][*].*?[*]/@s', $data, $matches); if (!$ok) { throw new Exception( "File {$path} does not have a header doc comment. Encode dependency ". "data in a header docblock."); } list($description, $metadata) = $parser->parse($matches[0]); $provides = preg_split('/\s+/', trim(idx($metadata, 'provides'))); $requires = preg_split('/\s+/', trim(idx($metadata, 'requires'))); $provides = array_filter($provides); $requires = array_filter($requires); if (!$provides) { // Tests and documentation-only JS is permitted to @provide no targets. continue; } if (count($provides) > 1) { throw new Exception( "File {$path} must @provide at most one Celerity target."); } $provides = reset($provides); $uri = '/res/'.substr($info['hash'], 0, 8).$path; $hash_map[$provides] = $info['hash']; $resource_graph[$provides] = $requires; $runtime_map[$provides] = array( 'uri' => $uri, 'type' => $type, 'requires' => $requires, 'disk' => $path, ); } $celerity_resource_graph = new CelerityResourceGraph(); $celerity_resource_graph->addNodes($resource_graph); $celerity_resource_graph->setResourceGraph($resource_graph); $celerity_resource_graph->loadGraph(); foreach ($resource_graph as $provides => $requires) { $cycle = $celerity_resource_graph->detectCycles($provides); if ($cycle) { throw new Exception( "Cycle detected in resource graph: ". implode($cycle, " => ") ); } } $package_map = array(); foreach ($package_spec as $name => $package) { $hashes = array(); $type = null; foreach ($package as $symbol) { if (empty($hash_map[$symbol])) { throw new Exception( "Package specification for '{$name}' includes '{$symbol}', but that ". "symbol is not defined anywhere."); } if ($type === null) { $type = $runtime_map[$symbol]['type']; } else { $ntype = $runtime_map[$symbol]['type']; if ($type !== $ntype) { throw new Exception( "Package specification for '{$name}' mixes resources of type ". "'{$type}' with resources of type '{$ntype}'. Each package may only ". "contain one type of resource."); } } $hashes[] = $symbol.':'.$hash_map[$symbol]; } $key = substr(md5(implode("\n", $hashes)), 0, 8); $package_map['packages'][$key] = array( 'name' => $name, 'symbols' => $package, 'uri' => '/res/pkg/'.$key.'/'.$name, 'type' => $type, ); foreach ($package as $symbol) { $package_map['reverse'][$symbol] = $key; } } ksort($runtime_map); $runtime_map = var_export($runtime_map, true); $runtime_map = preg_replace('/\s+$/m', '', $runtime_map); $runtime_map = preg_replace('/array \(/', 'array(', $runtime_map); $package_map['packages'] = isort($package_map['packages'], 'name'); ksort($package_map['reverse']); $package_map = var_export($package_map, true); $package_map = preg_replace('/\s+$/m', '', $package_map); $package_map = preg_replace('/array \(/', 'array(', $package_map); $generated = '@'.'generated'; $resource_map = << \n"; exit(1); } $user = null; $user_str = $argv[1]; try { $user = id(new PhabricatorUser()) ->loadOneWhere('phid = %s', $user_str); } catch (Exception $e) { // no op; we'll error in a line or two } if (empty($user)) { echo "usage: api.php \n" . "user {$user_str} does not exist or failed to load\n"; exit(1); } $method = $argv[2]; $method_class_str = ConduitAPIMethod::getClassNameFromAPIMethodName($method); try { $method_class = newv($method_class_str, array()); } catch (Exception $e) { echo "usage: api.php \n" . "method {$method_class_str} does not exist\n"; exit(1); } $log = new PhabricatorConduitMethodCallLog(); $log->setMethod($method); $params = @file_get_contents('php://stdin'); $params = json_decode($params, true); if (!is_array($params)) { echo "provide method parameters on stdin as a JSON blob"; exit(1); } // build a quick ConduitAPIRequest from stdin PLUS the authenticated user $conduit_request = new ConduitAPIRequest($params); $conduit_request->setUser($user); try { $result = $method_class->executeMethod($conduit_request); $error_code = null; $error_info = null; } catch (ConduitException $ex) { $result = null; $error_code = $ex->getMessage(); if ($ex->getErrorDescription()) { $error_info = $ex->getErrorDescription(); } else { $error_info = $method_handler->getErrorDescription($error_code); } } $time_end = microtime(true); $response = id(new ConduitAPIResponse()) ->setResult($result) ->setErrorCode($error_code) ->setErrorInfo($error_info); echo json_encode($response->toDictionary()), "\n"; // TODO -- how get $connection_id from SSH? $connection_id = null; $log->setConnectionID($connection_id); $log->setError((string)$error_code); $log->setDuration(1000000 * ($time_end - $time_start)); $log->save(); exit(); diff --git a/scripts/daemon/phabricator_daemon_launcher.php b/scripts/daemon/phabricator_daemon_launcher.php index 72728ff8fe..84a76bf316 100755 --- a/scripts/daemon/phabricator_daemon_launcher.php +++ b/scripts/daemon/phabricator_daemon_launcher.php @@ -1,233 +1,228 @@ #!/usr/bin/env php executeListCommand(); exit($err); case 'status': $err = $control->executeStatusCommand(); exit($err); case 'stop': $pass_argv = array_slice($argv, 2); $err = $control->executeStopCommand($pass_argv); exit($err); case 'restart': $err = $control->executeStopCommand(array()); if ($err) { exit($err); } /* Fall Through */ case 'start': $running = $control->loadRunningDaemons(); if ($running) { echo phutil_console_wrap( "phd start: Unable to start daemons because daemons are already ". "running.\n". "You can view running daemons with 'phd status'.\n". "You can stop running daemons with 'phd stop'.\n". "You can use 'phd restart' to stop all daemons before starting new ". "daemons.\n"); exit(1); } $daemons = array( array('PhabricatorRepositoryPullLocalDaemon', array()), array('PhabricatorGarbageCollectorDaemon', array()), ); $taskmasters = PhabricatorEnv::getEnvConfig('phd.start-taskmasters'); for ($ii = 0; $ii < $taskmasters; $ii++) { $daemons[] = array('PhabricatorTaskmasterDaemon', array()); } will_launch($control); foreach ($daemons as $spec) { list($name, $argv) = $spec; echo "Launching '{$name}'...\n"; $control->launchDaemon($name, $argv); } echo "Done.\n"; break; case 'repository-launch-readonly': case 'repository-launch-master': if ($command == 'repository-launch-readonly') { $daemon_args = array( '--', '--no-discovery', ); } else { $daemon_args = array(); } $need_launch = phd_load_tracked_repositories(); if (!$need_launch) { echo "There are no repositories with tracking enabled.\n"; exit(1); } will_launch($control); echo "Launching PullLocal daemon...\n"; $control->launchDaemon( 'PhabricatorRepositoryPullLocalDaemon', $daemon_args); echo "NOTE: '{$command}' is deprecated. Consult the documentation.\n"; echo "Done.\n"; break; case 'launch': case 'debug': $is_debug = ($argv[1] == 'debug'); $daemon = idx($argv, 2); if (!$daemon) { throw new Exception("Daemon name required!"); } $pass_argv = array_slice($argv, 3); $n = 1; if (!$is_debug) { if (is_numeric($daemon)) { $n = $daemon; if ($n < 1) { throw new Exception("Count must be at least 1!"); } $daemon = idx($argv, 3); if (!$daemon) { throw new Exception("Daemon name required!"); } $pass_argv = array_slice($argv, 4); } } $loader = new PhutilSymbolLoader(); $symbols = $loader ->setAncestorClass('PhutilDaemon') ->selectSymbolsWithoutLoading(); $symbols = ipull($symbols, 'name'); $match = array(); foreach ($symbols as $symbol) { if (stripos($symbol, $daemon) !== false) { if (strtolower($symbol) == strtolower($daemon)) { $match = array($symbol); break; } else { $match[] = $symbol; } } } if (count($match) == 0) { throw new Exception( "No daemons match! Use 'phd list' for a list of daemons."); } else if (count($match) > 1) { throw new Exception( "Which of these daemons did you mean?\n". " ".implode("\n ", $match)); } else { $daemon = reset($match); } $with_logs = true; if ($is_debug) { // In debug mode, we emit errors straight to stdout, so nothing useful // will show up in the logs. Don't echo the message about stuff showing // up in them, since it would be confusing. $with_logs = false; } will_launch($control, $with_logs); if ($is_debug) { echo "Launching {$daemon} in debug mode (nondaemonized)...\n"; } else { echo "Launching {$n} x {$daemon}"; } for ($ii = 0; $ii < $n; $ii++) { $control->launchDaemon($daemon, $pass_argv, $is_debug); if (!$is_debug) { echo "."; } } echo "\n"; echo "Done.\n"; break; case '--help': case 'help': default: $err = $control->executeHelpCommand(); exit($err); } function phd_load_tracked_repositories() { - phutil_require_module( - 'phabricator', - 'applications/repository/storage/repository'); - $repositories = id(new PhabricatorRepository())->loadAll(); foreach ($repositories as $key => $repository) { if (!$repository->isTracked()) { unset($repositories[$key]); } } return $repositories; } function will_launch($control, $with_logs = true) { echo "Staging launch...\n"; $control->pingConduit(); if ($with_logs) { $log_dir = $control->getControlDirectory('log').'/daemons.log'; echo "NOTE: Logs will appear in '{$log_dir}'.\n\n"; } } diff --git a/scripts/differential/remove_empty_revisions.php b/scripts/differential/remove_empty_revisions.php index ef1148ba91..1aa72b10fa 100755 --- a/scripts/differential/remove_empty_revisions.php +++ b/scripts/differential/remove_empty_revisions.php @@ -1,53 +1,51 @@ #!/usr/bin/env php establishConnection('r'), 'select distinct r.id from differential_revision r left join '. 'differential_diff d on r.id=d.revisionID where d.revisionID is NULL'); $empty_revisions = ipull($empty_revisions, 'id'); if (!$empty_revisions) { echo "No empty revisions found.\n"; exit(0); } echo phutil_console_wrap( "The following revision don't contain any diff:\n". implode(', ', $empty_revisions)); if (!phutil_console_confirm('Do you want to delete them?')) { echo "Cancelled.\n"; exit(1); } foreach ($empty_revisions as $revision_id) { $revision = id(new DifferentialRevision())->load($revision_id); $revision->delete(); } echo "Done.\n"; diff --git a/scripts/drydock/drydock_control.php b/scripts/drydock/drydock_control.php index 0b819fe464..e3a7612e8d 100755 --- a/scripts/drydock/drydock_control.php +++ b/scripts/drydock/drydock_control.php @@ -1,53 +1,50 @@ #!/usr/bin/env php makeSynchronous(); $allocator->setResourceType('webroot'); $lease = $allocator->allocate(); $lease->waitUntilActive(); $cmd = $lease->getInterface('webroot'); echo "URI: ".$cmd->getURI()."\n"; $lease->release(); die("Done.\n"); $i_file = $lease->getInterface('command'); list($stdout) = $i_file->execx('ls / ; echo -- ; uptime ; echo -- ; uname -n'); echo $stdout; $lease->release(); //$i_http = $lease->getInterface('httpd'); //echo $i_http->getURI('/index.html')."\n"; diff --git a/scripts/mail/mail_handler.php b/scripts/mail/mail_handler.php index da28d8c367..d60fcec11d 100755 --- a/scripts/mail/mail_handler.php +++ b/scripts/mail/mail_handler.php @@ -1,89 +1,82 @@ #!/usr/bin/env php 1) { $_SERVER['PHABRICATOR_ENV'] = $argv[1]; } $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; require_once $root.'/externals/mimemailparser/MimeMailParser.class.php'; -phutil_require_module( - 'phabricator', - 'applications/metamta/storage/receivedmail'); -phutil_require_module( - 'phabricator', - 'applications/files/storage/file'); - $parser = new MimeMailParser(); $parser->setText(file_get_contents('php://stdin')); $text_body = $parser->getMessageBody('text'); $text_body_headers = $parser->getMessageBodyHeaders('text'); $content_type = idx($text_body_headers, 'content-type'); if ( !phutil_is_utf8($text_body) && preg_match('/charset="(.*?)"/', $content_type, $matches) ) { $text_body = mb_convert_encoding($text_body, "UTF-8", $matches[1]); } $headers = $parser->getHeaders(); $headers['subject'] = iconv_mime_decode($headers['subject'], 0, "UTF-8"); $headers['from'] = iconv_mime_decode($headers['from'], 0, "UTF-8"); $received = new PhabricatorMetaMTAReceivedMail(); $received->setHeaders($headers); $received->setBodies(array( 'text' => $text_body, 'html' => $parser->getMessageBody('html'), )); $attachments = array(); foreach ($parser->getAttachments() as $attachment) { if (preg_match('@text/(plain|html)@', $attachment->getContentType()) && $attachment->getContentDisposition() == 'inline') { // If this is an "inline" attachment with some sort of text content-type, // do not treat it as a file for attachment. MimeMailParser already picked // it up in the getMessageBody() call above. We still want to treat 'inline' // attachments with other content types (e.g., images) as attachments. continue; } $file = PhabricatorFile::newFromFileData( $attachment->getContent(), array( 'name' => $attachment->getFilename(), )); $attachments[] = $file->getPHID(); } try { $received->setAttachments($attachments); $received->save(); $received->processReceivedMail(); } catch (Exception $e) { $received ->setMessage('EXCEPTION: '.$e->getMessage()) ->save(); } diff --git a/scripts/repository/rebuild_summaries.php b/scripts/repository/rebuild_summaries.php index a248d370ba..d06f146c4d 100755 --- a/scripts/repository/rebuild_summaries.php +++ b/scripts/repository/rebuild_summaries.php @@ -1,73 +1,69 @@ #!/usr/bin/env php establishConnection('w'); $sizes = queryfx_all( $conn_w, 'SELECT repositoryID, count(*) N FROM %T GROUP BY repositoryID', $commit->getTableName()); $sizes = ipull($sizes, 'N', 'repositoryID'); $maxes = queryfx_all( $conn_w, 'SELECT repositoryID, max(epoch) maxEpoch FROM %T GROUP BY repositoryID', $commit->getTableName()); $maxes = ipull($maxes, 'maxEpoch', 'repositoryID'); $repository_ids = array_keys($sizes + $maxes); echo "Updating ".count($repository_ids)." repositories"; foreach ($repository_ids as $repository_id) { $last_commit = queryfx_one( $conn_w, 'SELECT id FROM %T WHERE repositoryID = %d AND epoch = %d LIMIT 1', $commit->getTableName(), $repository_id, idx($maxes, $repository_id, 0)); if ($last_commit) { $last_commit = $last_commit['id']; } else { $last_commit = 0; } queryfx( $conn_w, 'INSERT INTO %T (repositoryID, lastCommitID, size, epoch) VALUES (%d, %d, %d, %d) ON DUPLICATE KEY UPDATE lastCommitID = VALUES(lastCommitID), size = VALUES(size), epoch = VALUES(epoch)', PhabricatorRepository::TABLE_SUMMARY, $repository_id, $last_commit, idx($sizes, $repository_id, 0), idx($maxes, $repository_id, 0)); echo "."; } echo "\ndone.\n"; diff --git a/scripts/repository/reparse.php b/scripts/repository/reparse.php index 4f21b03f3c..15039bae05 100755 --- a/scripts/repository/reparse.php +++ b/scripts/repository/reparse.php @@ -1,265 +1,263 @@ #!/usr/bin/env php loadOneWhere( 'callsign = %s OR phid = %s', $reparse_what, $reparse_what); if (!$repository) { throw new Exception("Unknown repository '{$reparse_what}'!"); } $commits = id(new PhabricatorRepositoryCommit())->loadAllWhere( 'repositoryID = %d', $repository->getID()); if (!$commits) { throw new Exception("No commits have been discovered in that repository!"); } $callsign = $repository->getCallsign(); } else { $matches = null; if (!preg_match('/r([A-Z]+)([a-z0-9]+)/', $reparse_what, $matches)) { throw new Exception("Can't parse commit identifier!"); } $callsign = $matches[1]; $commit_identifier = $matches[2]; $repository = id(new PhabricatorRepository())->loadOneWhere( 'callsign = %s', $callsign); if (!$repository) { throw new Exception("No repository with callsign '{$callsign}'!"); } $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere( 'repositoryID = %d AND commitIdentifier = %s', $repository->getID(), $commit_identifier); if (!$commit) { throw new Exception( "No matching commit '{$commit_identifier}' in repository '{$callsign}'. ". "(For git and mercurial repositories, you must specify the entire ". "commit hash.)"); } $commits = array($commit); } if ($is_all) { echo phutil_console_format( '**NOTE**: This script will queue tasks to reparse the data. Once the '. 'tasks have been queued, you need to run Taskmaster daemons to execute '. 'them.'); echo "\n\n"; echo "QUEUEING TASKS (".number_format(count($commits))." Commits):\n"; } $tasks = array(); foreach ($commits as $commit) { $classes = array(); switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: if ($reparse_message) { $classes[] = 'PhabricatorRepositoryGitCommitMessageParserWorker'; } if ($reparse_change) { $classes[] = 'PhabricatorRepositoryGitCommitChangeParserWorker'; } break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: if ($reparse_message) { $classes[] = 'PhabricatorRepositoryMercurialCommitMessageParserWorker'; } if ($reparse_change) { $classes[] = 'PhabricatorRepositoryMercurialCommitChangeParserWorker'; } break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: if ($reparse_message) { $classes[] = 'PhabricatorRepositorySvnCommitMessageParserWorker'; } if ($reparse_change) { $classes[] = 'PhabricatorRepositorySvnCommitChangeParserWorker'; } break; } if ($reparse_herald) { $classes[] = 'PhabricatorRepositoryCommitHeraldWorker'; } if ($reparse_owners) { $classes[] = 'PhabricatorRepositoryCommitOwnersWorker'; } $spec = array( 'commitID' => $commit->getID(), 'only' => true, ); if ($is_all) { foreach ($classes as $class) { $task = new PhabricatorWorkerTask(); $task->setTaskClass($class); $task->setData($spec); $task->save(); $commit_name = 'r'.$callsign.$commit->getCommitIdentifier(); echo " Queued '{$class}' for commit '{$commit_name}'.\n"; } } else { foreach ($classes as $class) { $worker = newv($class, array($spec)); echo "Running '{$class}'...\n"; $worker->doWork(); } } } echo "\nDone.\n"; function usage($message) { echo "Usage Error: {$message}"; echo "\n\n"; echo "Run 'reparse.php --help' for detailed help.\n"; exit(1); } function help() { $help = <<\n"; exit(1); } echo phutil_console_wrap( phutil_console_format( 'This script will test that you have configured valid credentials for '. 'access to a repository, so the Phabricator daemons can pull from it. '. 'You should run this as the **same user you will run the daemons as**, '. 'from the **same machine they will run from**. Doing this will help '. 'detect various problems with your configuration, such as SSH issues.')); list($whoami) = execx('whoami'); $whoami = trim($whoami); $ok = phutil_console_confirm("Do you want to continue as '{$whoami}'?"); if (!$ok) { die(1); } $callsign = $argv[1]; echo "Loading '{$callsign}' repository...\n"; $repository = id(new PhabricatorRepository())->loadOneWhere( 'callsign = %s', $argv[1]); if (!$repository) { throw new Exception("No such repository exists!"); } $vcs = $repository->getVersionControlSystem(); PhutilServiceProfiler::installEchoListener(); echo phutil_console_format( "\n". "**NOTE:** If you are prompted for an SSH password in the next step, the\n". "daemon won't work because it doesn't have the password and can't respond\n". "to an interactive prompt. Instead of typing the password, it will hang\n". "forever when prompted. There are several ways to resolve this:\n\n". " - Run the daemon inside an ssh-agent session where you have unlocked\n". " the key (most secure, but most complicated).\n". " - Generate a new, passwordless certificate for the daemon to use\n". " (usually quite easy).\n". " - Remove the passphrase from the key with `ssh-keygen -p`\n". " (easy, but questionable)."); phutil_console_confirm('Did you read all that?', $default_no = false); echo "Trying to connect to the remote...\n"; switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $err = $repository->passthruRemoteCommand( '--limit 1 log %s', $repository->getRemoteURI()); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: // Do an ls-remote on a nonexistent ref, which we expect to just return // nothing. $err = $repository->passthruRemoteCommand( 'ls-remote %s %s', $repository->getRemoteURI(), 'just-testing'); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: // TODO: 'hg id' doesn't support --insecure so we can't tell it not to // spew. If 'hg id' eventually supports --insecure, consider using it. echo "(It is safe to ignore any 'certificate with fingerprint ... not ". "verified' warnings, although you may want to configure Mercurial ". "to recognize the server's fingerprint/certificate.)\n"; $err = $repository->passthruRemoteCommand( 'id --rev tip %s', $repository->getRemoteURI()); break; default: throw new Exception("Unsupported repository type."); } if ($err) { echo phutil_console_format( "** FAIL ** Connection failed. The credentials for this ". "repository appear to be incorrectly configured.\n"); exit(1); } else { echo phutil_console_format( "** OKAY ** Connection successful. The credentials for ". "this repository appear to be correctly configured.\n"); } diff --git a/scripts/symbols/generate_ctags_symbols.php b/scripts/symbols/generate_ctags_symbols.php index 4133c13fc8..f9a65d00ea 100755 --- a/scripts/symbols/generate_ctags_symbols.php +++ b/scripts/symbols/generate_ctags_symbols.php @@ -1,132 +1,130 @@ #!/usr/bin/env php limit(8) as $file => $future) { $tags = $future->resolve(); $tags = explode("\n", $tags[1]); foreach ($tags as $tag) { $parts = explode(";", $tag); // skip lines that we can not parse if (count($parts) < 2) { continue; } // split ctags information $tag_info = explode("\t", $parts[0]); // split exuberant ctags "extension fields" (additional information) $parts[1] = trim($parts[1], "\t \""); $extension_fields = explode("\t", $parts[1]); // skip lines that we can not parse if (count($tag_info) < 3 || count($extension_fields) < 2) { continue; } list($token, $file_path, $line_num) = $tag_info; list($type, $language) = $extension_fields; // strip "language:" $language = substr($language, 9); // To keep consistent with "Separate with commas, for example: php, py" // in Arcanist Project edit form. $language = str_ireplace("python", "py", $language); // also, "normalize" c++ and c# $language = str_ireplace("c++", "cpp", $language); $language = str_ireplace("c#", "csharp", $language); switch ($type) { case 'class': print_symbol($file_path, $line_num, 'class', $token, $language); break; case 'function': print_symbol($file_path, $line_num, 'function', $token, $language); break; default: } } } function ctags_get_parser_future($file_path) { $future = new ExecFuture('ctags -n --fields=Kl -o - %s', $file_path); return $future; } function ctags_check_executable() { $future = new ExecFuture('ctags --version'); $result = $future->resolve(); if (empty($result[1])) { return false; } return true; } function print_symbol($file, $line_num, $type, $token, $language) { // get rid of relative path $file = explode('/', $file); if ($file[0] == '.' || $file[0] == "..") { array_shift($file); } $file = '/' . implode('/', $file); $parts = array( $token, $type, strtolower($language), $line_num, $file, ); echo implode(' ', $parts)."\n"; } diff --git a/scripts/symbols/generate_php_symbols.php b/scripts/symbols/generate_php_symbols.php index d648d0f010..39125bea71 100755 --- a/scripts/symbols/generate_php_symbols.php +++ b/scripts/symbols/generate_php_symbols.php @@ -1,80 +1,77 @@ #!/usr/bin/env php limit(8) as $file => $future) { $tree = XHPASTTree::newFromDataAndResolvedExecFuture( $data[$file], $future->resolve()); $root = $tree->getRootNode(); $functions = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION'); foreach ($functions as $function) { $name = $function->getChildByIndex(2); print_symbol($file, 'function', $name); } $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); foreach ($classes as $class) { $class_name = $class->getChildByIndex(1); print_symbol($file, 'class', $class_name); } $interfaces = $root->selectDescendantsOfType('n_INTERFACE_DECLARATION'); foreach ($interfaces as $interface) { $interface_name = $interface->getChildByIndex(1); print_symbol($file, 'interface', $interface_name); } } function print_symbol($file, $type, $token) { $parts = array( $token->getConcreteString(), $type, 'php', $token->getLineNumber(), '/'.ltrim($file, './'), ); echo implode(' ', $parts)."\n"; } diff --git a/scripts/user/account_admin.php b/scripts/user/account_admin.php index 2a7d663b71..7e4f587c6a 100755 --- a/scripts/user/account_admin.php +++ b/scripts/user/account_admin.php @@ -1,177 +1,174 @@ #!/usr/bin/env php loadOneWhere( 'username = %s', $username); if (!$user) { $original = new PhabricatorUser(); echo "There is no existing user account '{$username}'.\n"; $ok = phutil_console_confirm( "Do you want to create a new '{$username}' account?", $default_no = false); if (!$ok) { echo "Cancelled.\n"; exit(1); } $user = new PhabricatorUser(); $user->setUsername($username); $is_new = true; } else { $original = clone $user; echo "There is an existing user account '{$username}'.\n"; $ok = phutil_console_confirm( "Do you want to edit the existing '{$username}' account?", $default_no = false); if (!$ok) { echo "Cancelled.\n"; exit(1); } $is_new = false; } $user_realname = $user->getRealName(); if (strlen($user_realname)) { $realname_prompt = ' ['.$user_realname.']'; } else { $realname_prompt = ''; } $realname = nonempty( phutil_console_prompt("Enter user real name{$realname_prompt}:"), $user_realname); $user->setRealName($realname); // When creating a new user we prompt for an email address; when editing an // existing user we just skip this because it would be quite involved to provide // a reasonable CLI interface for editing multiple addresses and managing email // verification and primary addresses. $new_email = null; if ($is_new) { do { $email = phutil_console_prompt("Enter user email address:"); $duplicate = id(new PhabricatorUserEmail())->loadOneWhere( 'address = %s', $email); if ($duplicate) { echo "ERROR: There is already a user with that email address. ". "Each user must have a unique email address.\n"; } else { break; } } while (true); $new_email = $email; } $changed_pass = false; // This disables local echo, so the user's password is not shown as they type // it. phutil_passthru('stty -echo'); $password = phutil_console_prompt( "Enter a password for this user [blank to leave unchanged]:"); phutil_passthru('stty echo'); if (strlen($password)) { $changed_pass = $password; } $is_admin = $user->getIsAdmin(); $set_admin = phutil_console_confirm( 'Should this user be an administrator?', $default_no = !$is_admin); echo "\n\nACCOUNT SUMMARY\n\n"; $tpl = "%12s %-30s %-30s\n"; printf($tpl, null, 'OLD VALUE', 'NEW VALUE'); printf($tpl, 'Username', $original->getUsername(), $user->getUsername()); printf($tpl, 'Real Name', $original->getRealName(), $user->getRealName()); if ($new_email) { printf($tpl, 'Email', '', $new_email); } printf($tpl, 'Password', null, ($changed_pass !== false) ? 'Updated' : 'Unchanged'); printf( $tpl, 'Admin', $original->getIsAdmin() ? 'Y' : 'N', $user->getIsAdmin() ? 'Y' : 'N'); echo "\n"; if (!phutil_console_confirm("Save these changes?", $default_no = false)) { echo "Cancelled.\n"; exit(1); } $user->openTransaction(); $editor = new PhabricatorUserEditor(); // TODO: This is wrong, but we have a chicken-and-egg problem when you use // this script to create the first user. $editor->setActor($user); if ($new_email) { $email = id(new PhabricatorUserEmail()) ->setAddress($new_email) ->setIsVerified(1); $editor->createNewUser($user, $email); } else { $editor->updateUser($user); } $editor->makeAdminUser($user, $set_admin); if ($changed_pass !== false) { $editor->changePassword($user, $changed_pass); } $user->saveTransaction(); echo "Saved changes.\n"; diff --git a/scripts/user/add_user.php b/scripts/user/add_user.php index 9b18a673a9..a8b22f6fe8 100755 --- a/scripts/user/add_user.php +++ b/scripts/user/add_user.php @@ -1,75 +1,72 @@ #!/usr/bin/env php \n"; exit(1); } $username = $argv[1]; $email = $argv[2]; $realname = $argv[3]; $admin = $argv[4]; $admin = id(new PhabricatorUser())->loadOneWhere( 'username = %s', $argv[4]); if (!$admin) { throw new Exception( "Admin user must be the username of a valid Phabricator account, used ". "to send the new user a welcome email."); } $existing_user = id(new PhabricatorUser())->loadOneWhere( 'username = %s', $username); if ($existing_user) { throw new Exception( "There is already a user with the username '{$username}'!"); } $existing_email = id(new PhabricatorUserEmail())->loadOneWhere( 'address = %s', $email); if ($existing_email) { throw new Exception( "There is already a user with the email '{$email}'!"); } $user = new PhabricatorUser(); $user->setUsername($username); $user->setRealname($realname); $email_object = id(new PhabricatorUserEmail()) ->setAddress($email) ->setIsVerified(1); id(new PhabricatorUserEditor()) ->setActor($admin) ->createNewUser($user, $email_object); $user->sendWelcomeEmail($admin); echo "Created user '{$username}' (realname='{$realname}', email='{$email}').\n"; diff --git a/scripts/util/purge_cache.php b/scripts/util/purge_cache.php index 4e225595ee..45c91040c1 100755 --- a/scripts/util/purge_cache.php +++ b/scripts/util/purge_cache.php @@ -1,166 +1,163 @@ #!/usr/bin/env php establishConnection('w'), 'DELETE FROM %T WHERE id IN (%Ld)', DifferentialChangeset::TABLE_CACHE, $changesets); } else { echo "Purging changeset cache...\n"; queryfx( $table->establishConnection('w'), 'TRUNCATE TABLE %T', DifferentialChangeset::TABLE_CACHE); } echo "Done.\n"; } if ($purge_differential) { echo "Purging Differential comment cache...\n"; $table = new DifferentialComment(); queryfx( $table->establishConnection('w'), 'UPDATE %T SET cache = NULL', $table->getTableName()); echo "Purging Differential inline comment cache...\n"; $table = new DifferentialInlineComment(); queryfx( $table->establishConnection('w'), 'UPDATE %T SET cache = NULL', $table->getTableName()); echo "Done.\n"; } if ($purge_maniphest) { echo "Purging Maniphest comment cache...\n"; $table = new ManiphestTransaction(); queryfx( $table->establishConnection('w'), 'UPDATE %T SET cache = NULL', $table->getTableName()); echo "Done.\n"; } echo "Ok, caches purged.\n"; function usage($message) { echo "Usage Error: {$message}"; echo "\n\n"; echo "Run 'purge_cache.php --help' for detailed help.\n"; exit(1); } function help() { $help = <<request = $request; return $this; } final public function getRequest() { return $this->request; } final public function getConsole() { return $this->console; } final public function setConsole($console) { $this->console = $console; return $this; } final public function buildController() { $map = $this->getURIMap(); $mapper = new AphrontURIMapper($map); $request = $this->getRequest(); $path = $request->getPath(); list($controller_class, $uri_data) = $mapper->mapPath($path); if (!$controller_class) { if (!preg_match('@/$@', $path)) { // If we failed to match anything but don't have a trailing slash, try // to add a trailing slash and issue a redirect if that resolves. list($controller_class, $uri_data) = $mapper->mapPath($path.'/'); // NOTE: For POST, just 404 instead of redirecting, since the redirect // will be a GET without parameters. if ($controller_class && !$request->isHTTPPost()) { $uri = $request->getRequestURI()->setPath($path.'/'); return $this->buildRedirectController($uri); } } return $this->build404Controller(); } - PhutilSymbolLoader::loadClass($controller_class); $controller = newv($controller_class, array($request)); return array($controller, $uri_data); } final public function setHost($host) { $this->host = $host; return $this; } final public function getHost() { return $this->host; } final public function setPath($path) { $this->path = $path; return $this; } final public function getPath() { return $this->path; } final public function willBuildRequest() { } /** * Hook for synchronizing account information from OAuth workflows. * * @task hook */ public function willAuthenticateUserWithOAuth( PhabricatorUser $user, PhabricatorUserOAuthInfo $oauth_info, PhabricatorOAuthProvider $provider) { return; } } diff --git a/src/aphront/console/plugin/xhprof/api/DarkConsoleXHProfPluginAPI.php b/src/aphront/console/plugin/xhprof/api/DarkConsoleXHProfPluginAPI.php index 020f122de7..653f5baffa 100644 --- a/src/aphront/console/plugin/xhprof/api/DarkConsoleXHProfPluginAPI.php +++ b/src/aphront/console/plugin/xhprof/api/DarkConsoleXHProfPluginAPI.php @@ -1,91 +1,90 @@ 'application/xhprof', 'name' => 'profile.xhprof', )); return $file->getPHID(); } else { return null; } } } diff --git a/src/applications/auth/oauth/provider/base/PhabricatorOAuthProvider.php b/src/applications/auth/oauth/provider/base/PhabricatorOAuthProvider.php index c9f87464aa..33b3a8539c 100644 --- a/src/applications/auth/oauth/provider/base/PhabricatorOAuthProvider.php +++ b/src/applications/auth/oauth/provider/base/PhabricatorOAuthProvider.php @@ -1,177 +1,176 @@ getTokenExpiryKey(); if ($key) { $expiry_value = idx($data, $key, 0); if ($expiry_value) { return time() + $expiry_value; } } return 0; } /** * If the provider needs extra stuff in the token request, return it here. * For example, Google needs a grant_type parameter. */ public function getExtraTokenParameters() { return array(); } abstract public function getUserInfoURI(); abstract public function getMinimumScope(); abstract public function setUserData($data); abstract public function retrieveUserID(); abstract public function retrieveUserEmail(); abstract public function retrieveUserAccountName(); abstract public function retrieveUserProfileImage(); abstract public function retrieveUserAccountURI(); abstract public function retrieveUserRealName(); /** * Override this if the provider returns the token response as, e.g., JSON * or XML. */ public function decodeTokenResponse($response) { $data = null; parse_str($response, $data); return $data; } public function __construct() { } /** * This is where the OAuth provider will redirect the user after the user * grants Phabricator access. */ final public function getRedirectURI() { $key = $this->getProviderKey(); return PhabricatorEnv::getURI('/oauth/'.$key.'/login/'); } final public function setAccessToken($access_token) { $this->accessToken = $access_token; return $this; } final public function getAccessToken() { return $this->accessToken; } /** * Often used within setUserData to make sure $data is not completely * junk. More granular validations of data might be necessary depending on * the provider and are generally encouraged. */ final protected function validateUserData($data) { if (empty($data) || !is_array($data)) { throw new PhabricatorOAuthProviderException(); } return true; } public static function newProvider($which) { switch ($which) { case self::PROVIDER_FACEBOOK: $class = 'PhabricatorOAuthProviderFacebook'; break; case self::PROVIDER_GITHUB: $class = 'PhabricatorOAuthProviderGitHub'; break; case self::PROVIDER_GOOGLE: $class = 'PhabricatorOAuthProviderGoogle'; break; case self::PROVIDER_PHABRICATOR: $class = 'PhabricatorOAuthProviderPhabricator'; break; case self::PROVIDER_DISQUS: $class = 'PhabricatorOAuthProviderDisqus'; break; default: throw new Exception('Unknown OAuth provider.'); } - PhutilSymbolLoader::loadClass($class); return newv($class, array()); } public static function getAllProviders() { $all = array( self::PROVIDER_FACEBOOK, self::PROVIDER_GITHUB, self::PROVIDER_GOOGLE, self::PROVIDER_PHABRICATOR, self::PROVIDER_DISQUS, ); $providers = array(); foreach ($all as $provider) { $providers[$provider] = self::newProvider($provider); } return $providers; } } diff --git a/src/applications/base/controller/base/PhabricatorController.php b/src/applications/base/controller/base/PhabricatorController.php index 94a8ed6734..11f195bf2b 100644 --- a/src/applications/base/controller/base/PhabricatorController.php +++ b/src/applications/base/controller/base/PhabricatorController.php @@ -1,124 +1,121 @@ shouldRequireLogin(); return ($need_login && $need_verify); } final public function willBeginExecution() { $request = $this->getRequest(); $user = new PhabricatorUser(); $phusr = $request->getCookie('phusr'); $phsid = $request->getCookie('phsid'); if ($phusr && $phsid) { $info = queryfx_one( $user->establishConnection('r'), 'SELECT u.* FROM %T u JOIN %T s ON u.phid = s.userPHID AND s.type LIKE %> AND s.sessionKey = %s', $user->getTableName(), 'phabricator_session', 'web-', $phsid); if ($info) { $user->loadFromArray($info); } } $request->setUser($user); if ($user->getIsDisabled() && $this->shouldRequireEnabledUser()) { - $disabled_user_controller = newv( - 'PhabricatorDisabledUserController', - array($request)); + $disabled_user_controller = new PhabricatorDisabledUserController( + $request); return $this->delegateToController($disabled_user_controller); } if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) { if ($user->getConsoleEnabled() || PhabricatorEnv::getEnvConfig('darkconsole.always-on')) { $console = new DarkConsoleCore(); $request->getApplicationConfiguration()->setConsole($console); } } if ($this->shouldRequireLogin() && !$user->getPHID()) { - $login_controller = newv('PhabricatorLoginController', array($request)); + $login_controller = new PhabricatorLoginController($request); return $this->delegateToController($login_controller); } if ($this->shouldRequireEmailVerification()) { $email = $user->loadPrimaryEmail(); if (!$email) { throw new Exception( "No primary email address associated with this account!"); } if (!$email->getIsVerified()) { - $verify_controller = newv( - 'PhabricatorMustVerifyEmailController', - array($request)); + $verify_controller = new PhabricatorMustVerifyEmailController($request); return $this->delegateToController($verify_controller); } } if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) { return new Aphront403Response(); } } public function buildStandardPageView() { $view = new PhabricatorStandardPageView(); $view->setRequest($this->getRequest()); if ($this->shouldRequireAdmin()) { $view->setIsAdminInterface(true); } return $view; } public function buildStandardPageResponse($view, array $data) { $page = $this->buildStandardPageView(); $page->appendChild($view); $response = new AphrontWebpageResponse(); $response->setContent($page->render()); return $response; } } diff --git a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php index 138b7c8fcd..c450f9c29b 100644 --- a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php @@ -1,828 +1,827 @@ allowsAnonymousAccess(); } public function willProcessRequest(array $data) { $this->revisionID = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $viewer_is_anonymous = !$user->isLoggedIn(); $revision = id(new DifferentialRevision())->load($this->revisionID); if (!$revision) { return new Aphront404Response(); } $revision->loadRelationships(); $diffs = $revision->loadDiffs(); if (!$diffs) { throw new Exception( "This revision has no diffs. Something has gone quite wrong."); } $diff_vs = $request->getInt('vs'); $target_id = $request->getInt('id'); $target = idx($diffs, $target_id, end($diffs)); $target_manual = $target; if (!$target_id) { foreach ($diffs as $diff) { if ($diff->getCreationMethod() != 'commit') { $target_manual = $diff; } } } if (empty($diffs[$diff_vs])) { $diff_vs = null; } list($aux_fields, $props) = $this->loadAuxiliaryFieldsAndProperties( $revision, $target_manual, array( 'local:commits', 'arc:lint', 'arc:unit', )); $arc_project = $target->loadArcanistProject(); $repository = ($arc_project ? $arc_project->loadRepository() : null); list($changesets, $vs_map, $rendering_references) = $this->loadChangesetsAndVsMap( $target, idx($diffs, $diff_vs), $repository); $comments = $revision->loadComments(); $comments = array_merge( $this->getImplicitComments($revision, reset($diffs)), $comments); $all_changesets = $changesets; $inlines = $this->loadInlineComments($comments, $all_changesets); $object_phids = array_merge( $revision->getReviewers(), $revision->getCCPHIDs(), $revision->loadCommitPHIDs(), array( $revision->getAuthorPHID(), $user->getPHID(), ), mpull($comments, 'getAuthorPHID')); foreach ($comments as $comment) { $metadata = $comment->getMetadata(); $added_reviewers = idx( $metadata, DifferentialComment::METADATA_ADDED_REVIEWERS); if ($added_reviewers) { foreach ($added_reviewers as $phid) { $object_phids[] = $phid; } } $added_ccs = idx( $metadata, DifferentialComment::METADATA_ADDED_CCS); if ($added_ccs) { foreach ($added_ccs as $phid) { $object_phids[] = $phid; } } } foreach ($revision->getAttached() as $type => $phids) { foreach ($phids as $phid => $info) { $object_phids[] = $phid; } } $aux_phids = array(); foreach ($aux_fields as $key => $aux_field) { $aux_phids[$key] = $aux_field->getRequiredHandlePHIDsForRevisionView(); } $object_phids = array_merge($object_phids, array_mergev($aux_phids)); $object_phids = array_unique($object_phids); $handles = id(new PhabricatorObjectHandleData($object_phids)) ->loadHandles(); foreach ($aux_fields as $key => $aux_field) { // Make sure each field only has access to handles it specifically // requested, not all handles. Otherwise you can get a field which works // only in the presence of other fields. $aux_field->setHandles(array_select_keys($handles, $aux_phids[$key])); } $reviewer_warning = null; $has_live_reviewer = false; foreach ($revision->getReviewers() as $reviewer) { if (!$handles[$reviewer]->isDisabled()) { $has_live_reviewer = true; } } if (!$has_live_reviewer) { $reviewer_warning = new AphrontErrorView(); $reviewer_warning->setSeverity(AphrontErrorView::SEVERITY_WARNING); $reviewer_warning->setTitle('No Active Reviewers'); if ($revision->getReviewers()) { $reviewer_warning->appendChild( '

All specified reviewers are disabled. You may want to add '. 'some new reviewers.

'); } else { $reviewer_warning->appendChild( '

This revision has no specified reviewers. You may want to '. 'add some.

'); } } $request_uri = $request->getRequestURI(); $limit = 100; $large = $request->getStr('large'); if (count($changesets) > $limit && !$large) { $count = number_format(count($changesets)); $warning = new AphrontErrorView(); $warning->setTitle('Very Large Diff'); $warning->setSeverity(AphrontErrorView::SEVERITY_WARNING); $warning->setWidth(AphrontErrorView::WIDTH_WIDE); $warning->appendChild( "

This diff is very large and affects {$count} files. Load ". "each file individually. ". "". phutil_render_tag( 'a', array( 'href' => $request_uri ->alter('large', 'true') ->setFragment('differential-review-toc'), ), 'Show All Files Inline'). ""); $warning = $warning->render(); $visible_changesets = array(); foreach ($inlines as $inline) { $changeset_id = $inline->getChangesetID(); if (isset($changesets[$changeset_id])) { $visible_changesets[$changeset_id] = $changesets[$changeset_id]; } } if (!empty($props['arc:lint'])) { $changeset_paths = mpull($changesets, null, 'getFilename'); foreach ($props['arc:lint'] as $lint) { $changeset = idx($changeset_paths, $lint['path']); if ($changeset) { $visible_changesets[$changeset->getID()] = $changeset; } } } } else { $warning = null; $visible_changesets = $changesets; } $revision_detail = new DifferentialRevisionDetailView(); $revision_detail->setRevision($revision); $revision_detail->setAuxiliaryFields($aux_fields); $actions = $this->getRevisionActions($revision); $custom_renderer_class = PhabricatorEnv::getEnvConfig( 'differential.revision-custom-detail-renderer'); if ($custom_renderer_class) { // TODO: build a better version of the action links and deprecate the // whole DifferentialRevisionDetailRenderer class. - PhutilSymbolLoader::loadClass($custom_renderer_class); $custom_renderer = newv($custom_renderer_class, array()); $actions = array_merge( $actions, $custom_renderer->generateActionLinks($revision, $target_manual)); } $whitespace = $request->getStr( 'whitespace', DifferentialChangesetParser::WHITESPACE_IGNORE_ALL); if ($arc_project) { $symbol_indexes = $this->buildSymbolIndexes( $arc_project, $visible_changesets); } else { $symbol_indexes = array(); } $revision_detail->setActions($actions); $revision_detail->setUser($user); $comment_view = new DifferentialRevisionCommentListView(); $comment_view->setComments($comments); $comment_view->setHandles($handles); $comment_view->setInlineComments($inlines); $comment_view->setChangesets($all_changesets); $comment_view->setUser($user); $comment_view->setTargetDiff($target); $comment_view->setVersusDiffID($diff_vs); $changeset_view = new DifferentialChangesetListView(); $changeset_view->setChangesets($changesets); $changeset_view->setVisibleChangesets($visible_changesets); if (!$viewer_is_anonymous) { $changeset_view->setInlineCommentControllerURI( '/differential/comment/inline/edit/'.$revision->getID().'/'); } $changeset_view->setStandaloneURI('/differential/changeset/'); $changeset_view->setRawFileURIs( '/differential/changeset/?view=old', '/differential/changeset/?view=new'); $changeset_view->setUser($user); $changeset_view->setDiff($target); $changeset_view->setRenderingReferences($rendering_references); $changeset_view->setVsMap($vs_map); $changeset_view->setWhitespace($whitespace); if ($repository) { $changeset_view->setRepository($repository, $target); } $changeset_view->setSymbolIndexes($symbol_indexes); $diff_history = new DifferentialRevisionUpdateHistoryView(); $diff_history->setDiffs($diffs); $diff_history->setSelectedVersusDiffID($diff_vs); $diff_history->setSelectedDiffID($target->getID()); $diff_history->setSelectedWhitespace($whitespace); $diff_history->setUser($user); $local_view = new DifferentialLocalCommitsView(); $local_view->setUser($user); $local_view->setLocalCommits(idx($props, 'local:commits')); if ($repository) { $other_revisions = $this->loadOtherRevisions( $changesets, $target, $repository); } else { $other_revisions = array(); } $other_view = null; if ($other_revisions) { $other_view = $this->renderOtherRevisions($other_revisions); } $toc_view = new DifferentialDiffTableOfContentsView(); $toc_view->setChangesets($changesets); $toc_view->setVisibleChangesets($visible_changesets); $toc_view->setRenderingReferences($rendering_references); $toc_view->setUnitTestData(idx($props, 'arc:unit', array())); if ($repository) { $toc_view->setRepository($repository); } $toc_view->setDiff($target); $toc_view->setUser($user); $toc_view->setRevisionID($revision->getID()); $toc_view->setWhitespace($whitespace); $comment_form = null; if (!$viewer_is_anonymous) { $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $user->getPHID(), 'differential-comment-'.$revision->getID()); if ($draft) { $draft = $draft->getDraft(); } else { $draft = null; } $comment_form = new DifferentialAddCommentView(); $comment_form->setRevision($revision); $comment_form->setAuxFields($aux_fields); $comment_form->setActions($this->getRevisionCommentActions($revision)); $comment_form->setActionURI('/differential/comment/save/'); $comment_form->setUser($user); $comment_form->setDraft($draft); } $pane_id = celerity_generate_unique_node_id(); Javelin::initBehavior( 'differential-keyboard-navigation', array( 'haunt' => $pane_id, )); Javelin::initBehavior('differential-user-select'); $page_pane = id(new DifferentialPrimaryPaneView()) ->setLineWidthFromChangesets($changesets) ->setID($pane_id) ->appendChild( $comment_view->render(). $diff_history->render(). $warning. $local_view->render(). $toc_view->render(). $other_view. $changeset_view->render()); if ($comment_form) { $page_pane->appendChild($comment_form->render()); } return $this->buildStandardPageResponse( array( $reviewer_warning, $revision_detail, $page_pane, ), array( 'title' => 'D'.$revision->getID().' '.$revision->getTitle(), )); } private function getImplicitComments( DifferentialRevision $revision, DifferentialDiff $diff) { $template = new DifferentialComment(); $template->setAuthorPHID($diff->getAuthorPHID()); $template->setRevisionID($revision->getID()); $template->setDateCreated($revision->getDateCreated()); $comments = array(); if (strlen($revision->getSummary())) { $summary_comment = clone $template; $summary_comment->setContent($revision->getSummary()); $summary_comment->setAction(DifferentialAction::ACTION_SUMMARIZE); $comments[] = $summary_comment; } if (strlen($revision->getTestPlan())) { $testplan_comment = clone $template; $testplan_comment->setContent($revision->getTestPlan()); $testplan_comment->setAction(DifferentialAction::ACTION_TESTPLAN); $comments[] = $testplan_comment; } return $comments; } private function getRevisionActions(DifferentialRevision $revision) { $user = $this->getRequest()->getUser(); $viewer_phid = $user->getPHID(); $viewer_is_owner = ($revision->getAuthorPHID() == $viewer_phid); $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers()); $viewer_is_cc = in_array($viewer_phid, $revision->getCCPHIDs()); $viewer_is_anonymous = !$this->getRequest()->getUser()->isLoggedIn(); $status = $revision->getStatus(); $revision_id = $revision->getID(); $revision_phid = $revision->getPHID(); $links = array(); if ($viewer_is_owner) { $links[] = array( 'class' => 'revision-edit', 'href' => "/differential/revision/edit/{$revision_id}/", 'name' => 'Edit Revision', ); } if (!$viewer_is_anonymous) { require_celerity_resource('phabricator-flag-css'); $flag = PhabricatorFlagQuery::loadUserFlag($user, $revision_phid); if ($flag) { $class = PhabricatorFlagColor::getCSSClass($flag->getColor()); $color = PhabricatorFlagColor::getColorName($flag->getColor()); $links[] = array( 'class' => 'flag-clear '.$class, 'href' => '/flag/delete/'.$flag->getID().'/', 'name' => phutil_escape_html('Remove '.$color.' Flag'), 'sigil' => 'workflow', ); } else { $links[] = array( 'class' => 'flag-add phabricator-flag-ghost', 'href' => '/flag/edit/'.$revision_phid.'/', 'name' => 'Flag Revision', 'sigil' => 'workflow', ); } if (!$viewer_is_owner && !$viewer_is_reviewer) { $action = $viewer_is_cc ? 'rem' : 'add'; $links[] = array( 'class' => $viewer_is_cc ? 'subscribe-rem' : 'subscribe-add', 'href' => "/differential/subscribe/{$action}/{$revision_id}/", 'name' => $viewer_is_cc ? 'Unsubscribe' : 'Subscribe', 'instant' => true, ); } else { $links[] = array( 'class' => 'subscribe-rem unavailable', 'name' => 'Automatically Subscribed', ); } require_celerity_resource('phabricator-object-selector-css'); require_celerity_resource('javelin-behavior-phabricator-object-selector'); $links[] = array( 'class' => 'action-dependencies', 'name' => 'Edit Dependencies', 'href' => "/search/attach/{$revision_phid}/DREV/dependencies/", 'sigil' => 'workflow', ); if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) { $links[] = array( 'class' => 'attach-maniphest', 'name' => 'Edit Maniphest Tasks', 'href' => "/search/attach/{$revision_phid}/TASK/", 'sigil' => 'workflow', ); } if ($user->getIsAdmin()) { $links[] = array( 'class' => 'transcripts-metamta', 'name' => 'MetaMTA Transcripts', 'href' => "/mail/?phid={$revision_phid}", ); } $links[] = array( 'class' => 'transcripts-herald', 'name' => 'Herald Transcripts', 'href' => "/herald/transcript/?phid={$revision_phid}", ); } return $links; } private function getRevisionCommentActions(DifferentialRevision $revision) { $actions = array( DifferentialAction::ACTION_COMMENT => true, ); $viewer = $this->getRequest()->getUser(); $viewer_phid = $viewer->getPHID(); $viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID()); $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers()); $viewer_did_accept = ($viewer_phid === $revision->loadReviewedBy()); $status = $revision->getStatus(); if ($viewer_is_owner) { switch ($status) { case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: $actions[DifferentialAction::ACTION_ABANDON] = true; $actions[DifferentialAction::ACTION_RETHINK] = true; break; case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: $actions[DifferentialAction::ACTION_ABANDON] = true; $actions[DifferentialAction::ACTION_REQUEST] = true; break; case ArcanistDifferentialRevisionStatus::ACCEPTED: $actions[DifferentialAction::ACTION_ABANDON] = true; $actions[DifferentialAction::ACTION_REQUEST] = true; $actions[DifferentialAction::ACTION_RETHINK] = true; $actions[DifferentialAction::ACTION_CLOSE] = true; break; case ArcanistDifferentialRevisionStatus::CLOSED: break; case ArcanistDifferentialRevisionStatus::ABANDONED: $actions[DifferentialAction::ACTION_RECLAIM] = true; break; } } else { switch ($status) { case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: $actions[DifferentialAction::ACTION_ACCEPT] = true; $actions[DifferentialAction::ACTION_REJECT] = true; $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer; break; case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: $actions[DifferentialAction::ACTION_ACCEPT] = true; $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer; break; case ArcanistDifferentialRevisionStatus::ACCEPTED: $actions[DifferentialAction::ACTION_REJECT] = true; $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer && !$viewer_did_accept; break; case ArcanistDifferentialRevisionStatus::CLOSED: case ArcanistDifferentialRevisionStatus::ABANDONED: break; } if ($status != ArcanistDifferentialRevisionStatus::CLOSED) { $actions[DifferentialAction::ACTION_CLAIM] = true; } } $actions[DifferentialAction::ACTION_ADDREVIEWERS] = true; $actions[DifferentialAction::ACTION_ADDCCS] = true; $actions = array_keys(array_filter($actions)); $actions_dict = array(); foreach ($actions as $action) { $actions_dict[$action] = DifferentialAction::getActionVerb($action); } return $actions_dict; } private function loadInlineComments(array $comments, array &$changesets) { assert_instances_of($comments, 'DifferentialComment'); assert_instances_of($changesets, 'DifferentialChangeset'); $inline_comments = array(); $comment_ids = array_filter(mpull($comments, 'getID')); if (!$comment_ids) { return $inline_comments; } $inline_comments = id(new DifferentialInlineComment()) ->loadAllWhere( 'commentID in (%Ld)', $comment_ids); $load_changesets = array(); foreach ($inline_comments as $inline) { $changeset_id = $inline->getChangesetID(); if (isset($changesets[$changeset_id])) { continue; } $load_changesets[$changeset_id] = true; } $more_changesets = array(); if ($load_changesets) { $changeset_ids = array_keys($load_changesets); $more_changesets += id(new DifferentialChangeset()) ->loadAllWhere( 'id IN (%Ld)', $changeset_ids); } if ($more_changesets) { $changesets += $more_changesets; $changesets = msort($changesets, 'getSortKey'); } return $inline_comments; } private function loadChangesetsAndVsMap( DifferentialDiff $target, DifferentialDiff $diff_vs = null, PhabricatorRepository $repository = null) { $load_ids = array(); if ($diff_vs) { $load_ids[] = $diff_vs->getID(); } $load_ids[] = $target->getID(); $raw_changesets = id(new DifferentialChangeset()) ->loadAllWhere( 'diffID IN (%Ld)', $load_ids); $changeset_groups = mgroup($raw_changesets, 'getDiffID'); $changesets = idx($changeset_groups, $target->getID(), array()); $changesets = mpull($changesets, null, 'getID'); $refs = array(); foreach ($changesets as $changeset) { $refs[$changeset->getID()] = $changeset->getID(); } $vs_map = array(); if ($diff_vs) { $vs_changesets = array(); $vs_id = $diff_vs->getID(); foreach (idx($changeset_groups, $vs_id, array()) as $changeset) { $path = $changeset->getAbsoluteRepositoryPath($repository, $diff_vs); $vs_changesets[$path] = $changeset; } foreach ($changesets as $key => $changeset) { $file = $changeset->getAbsoluteRepositoryPath($repository, $target); if (isset($vs_changesets[$file])) { $vs_map[$changeset->getID()] = $vs_changesets[$file]->getID(); $refs[$changeset->getID()] = $changeset->getID().'/'.$vs_changesets[$file]->getID(); unset($vs_changesets[$file]); } else { $refs[$changeset->getID()] = $changeset->getID(); } } foreach ($vs_changesets as $changeset) { $changesets[$changeset->getID()] = $changeset; $vs_map[$changeset->getID()] = -1; $refs[$changeset->getID()] = $changeset->getID().'/-1'; } } $changesets = msort($changesets, 'getSortKey'); return array($changesets, $vs_map, $refs); } private function loadAuxiliaryFieldsAndProperties( DifferentialRevision $revision, DifferentialDiff $diff, array $special_properties) { $aux_fields = DifferentialFieldSelector::newSelector() ->getFieldSpecifications(); foreach ($aux_fields as $key => $aux_field) { if (!$aux_field->shouldAppearOnRevisionView()) { unset($aux_fields[$key]); } else { $aux_field->setUser($this->getRequest()->getUser()); } } $aux_fields = DifferentialAuxiliaryField::loadFromStorage( $revision, $aux_fields); $aux_props = array(); foreach ($aux_fields as $key => $aux_field) { $aux_field->setDiff($diff); $aux_props[$key] = $aux_field->getRequiredDiffProperties(); } $required_properties = array_mergev($aux_props); $required_properties = array_merge( $required_properties, $special_properties); $property_map = array(); if ($required_properties) { $properties = id(new DifferentialDiffProperty())->loadAllWhere( 'diffID = %d AND name IN (%Ls)', $diff->getID(), $required_properties); $property_map = mpull($properties, 'getData', 'getName'); } foreach ($aux_fields as $key => $aux_field) { // Give each field only the properties it specifically required, and // set 'null' for each requested key which we didn't actually load a // value for (otherwise, getDiffProperty() will throw). if ($aux_props[$key]) { $props = array_select_keys($property_map, $aux_props[$key]) + array_fill_keys($aux_props[$key], null); } else { $props = array(); } $aux_field->setDiffProperties($props); } return array( $aux_fields, array_select_keys( $property_map, $special_properties)); } private function buildSymbolIndexes( PhabricatorRepositoryArcanistProject $arc_project, array $visible_changesets) { assert_instances_of($visible_changesets, 'DifferentialChangeset'); $engine = PhabricatorSyntaxHighlighter::newEngine(); $langs = $arc_project->getSymbolIndexLanguages(); if (!$langs) { return array(); } $symbol_indexes = array(); $project_phids = array_merge( array($arc_project->getPHID()), nonempty($arc_project->getSymbolIndexProjects(), array())); $indexed_langs = array_fill_keys($langs, true); foreach ($visible_changesets as $key => $changeset) { $lang = $engine->getLanguageFromFilename($changeset->getFilename()); if (isset($indexed_langs[$lang])) { $symbol_indexes[$key] = array( 'lang' => $lang, 'projects' => $project_phids, ); } } return $symbol_indexes; } 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(); } $query = id(new DifferentialRevisionQuery()) ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) ->setOrder(DifferentialRevisionQuery::ORDER_PATH_MODIFIED) ->setLimit(10) ->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; } private function renderOtherRevisions(array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $view = id(new DifferentialRevisionListView()) ->setRevisions($revisions) ->setFields(DifferentialRevisionListView::getDefaultFields()) ->setUser($this->getRequest()->getUser()); $phids = $view->getRequiredHandlePHIDs(); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $view->setHandles($handles); return '

'. '

Open Revisions Affecting These Files

'. $view->render(). '
'; } } diff --git a/src/applications/diffusion/query/branch/base/DiffusionBranchQuery.php b/src/applications/diffusion/query/branch/base/DiffusionBranchQuery.php index 84703d6ce7..7372a22b2e 100644 --- a/src/applications/diffusion/query/branch/base/DiffusionBranchQuery.php +++ b/src/applications/diffusion/query/branch/base/DiffusionBranchQuery.php @@ -1,80 +1,77 @@ offset = $offset; return $this; } public function getOffset() { return $this->offset; } public function setLimit($limit) { $this->limit = $limit; return $this; } protected function getLimit() { return $this->limit; } final private function __construct() { // } final public static function newFromDiffusionRequest( DiffusionRequest $request) { $repository = $request->getRepository(); switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $class = 'DiffusionGitBranchQuery'; + $query = new DiffusionGitBranchQuery(); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $class = 'DiffusionMercurialBranchQuery'; + $query = new DiffusionMercurialBranchQuery(); break; default: throw new Exception("Unsupported VCS!"); } - PhutilSymbolLoader::loadClass($class); - $query = new $class(); - $query->request = $request; return $query; } final protected function getRequest() { return $this->request; } final public function loadBranches() { return $this->executeQuery(); } abstract protected function executeQuery(); } diff --git a/src/applications/diffusion/query/browse/base/DiffusionBrowseQuery.php b/src/applications/diffusion/query/browse/base/DiffusionBrowseQuery.php index 4c38efcc62..675623e663 100644 --- a/src/applications/diffusion/query/browse/base/DiffusionBrowseQuery.php +++ b/src/applications/diffusion/query/browse/base/DiffusionBrowseQuery.php @@ -1,158 +1,155 @@ } final public static function newFromDiffusionRequest( DiffusionRequest $request) { $repository = $request->getRepository(); switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: // TODO: Verify local-path? - $class = 'DiffusionGitBrowseQuery'; + $query = new DiffusionGitBrowseQuery(); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $class = 'DiffusionMercurialBrowseQuery'; + $query = new DiffusionMercurialBrowseQuery(); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $class = 'DiffusionSvnBrowseQuery'; + $query = new DiffusionSvnBrowseQuery(); break; default: throw new Exception("Unsupported VCS!"); } - PhutilSymbolLoader::loadClass($class); - $query = new $class(); - $query->request = $request; return $query; } final protected function getRequest() { return $this->request; } final public function getReasonForEmptyResultSet() { return $this->reason; } final public function getExistedAtCommit() { return $this->existedAtCommit; } final public function getDeletedAtCommit() { return $this->deletedAtCommit; } final public function loadPaths() { return $this->executeQuery(); } final public function shouldOnlyTestValidity() { return $this->validityOnly; } final public function needValidityOnly($need_validity_only) { $this->validityOnly = $need_validity_only; return $this; } final public function renderReadme(array $results) { $drequest = $this->getRequest(); $readme = null; foreach ($results as $result) { $path = $result->getPath(); if (preg_match('/^readme(|\.txt|\.remarkup|\.rainbow)$/i', $path)) { $readme = $result; break; } } if (!$readme) { return null; } $readme_request = DiffusionRequest::newFromDictionary( array( 'repository' => $drequest->getRepository(), 'commit' => $drequest->getStableCommitName(), 'path' => $readme->getFullPath(), )); $content_query = DiffusionFileContentQuery::newFromDiffusionRequest( $readme_request); $content_query->loadFileContent(); $readme_content = $content_query->getRawData(); if (preg_match('/\\.txt$/', $readme->getPath())) { $readme_content = phutil_escape_html($readme_content); $readme_content = nl2br($readme_content); $class = null; } else if (preg_match('/\\.rainbow$/', $readme->getPath())) { $highlighter = new PhutilRainbowSyntaxHighlighter(); $readme_content = $highlighter ->getHighlightFuture($readme_content) ->resolve(); $readme_content = nl2br($readme_content); require_celerity_resource('syntax-highlighting-css'); $class = 'remarkup-code'; } else { // Markup extensionless files as remarkup so we get links and such. $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); $readme_content = $engine->markupText($readme_content); $class = 'phabricator-remarkup'; } $readme_content = phutil_render_tag( 'div', array( 'class' => $class, ), $readme_content); return $readme_content; } abstract protected function executeQuery(); } diff --git a/src/applications/files/engine/s3/PhabricatorS3FileStorageEngine.php b/src/applications/files/engine/s3/PhabricatorS3FileStorageEngine.php index e06ccee299..3e9ef01b21 100644 --- a/src/applications/files/engine/s3/PhabricatorS3FileStorageEngine.php +++ b/src/applications/files/engine/s3/PhabricatorS3FileStorageEngine.php @@ -1,135 +1,130 @@ newS3API(); $name = 'phabricator/'.Filesystem::readRandomCharacters(20); AphrontWriteGuard::willWrite(); $s3->putObject( $data, $this->getBucketName(), $name, $acl = 'private'); return $name; } /** * Load a stored blob from S3. * @task impl */ public function readFile($handle) { $result = $this->newS3API()->getObject( $this->getBucketName(), $handle); return $result->body; } /** * Delete a blob from S3. * @task impl */ public function deleteFile($handle) { AphrontWriteGuard::willWrite(); $this->newS3API()->deleteObject( $this->getBucketName(), $handle); } /* -( Internals )---------------------------------------------------------- */ /** * Retrieve the S3 bucket name. * * @task internal */ private function getBucketName() { $bucket = PhabricatorEnv::getEnvConfig('storage.s3.bucket'); if (!$bucket) { throw new PhabricatorFileStorageConfigurationException( "No 'storage.s3.bucket' specified!"); } return $bucket; } /** * Create a new S3 API object. * * @task internal + * @phutil-external-symbol class S3 */ private function newS3API() { $libroot = dirname(phutil_get_library_root('phabricator')); require_once $libroot.'/externals/s3/S3.php'; $access_key = PhabricatorEnv::getEnvConfig('amazon-s3.access-key'); $secret_key = PhabricatorEnv::getEnvConfig('amazon-s3.secret-key'); if (!$access_key || !$secret_key) { throw new PhabricatorFileStorageConfigurationException( "Specify 'amazon-s3.access-key' and 'amazon-s3.secret-key'!"); } - $s3 = newv( - 'S3', - array( - $access_key, - $secret_key, - $use_ssl = true, - )); + $s3 = new S3($access_key, $secret_key, $use_ssl = true); $s3->setExceptions(true); return $s3; } } diff --git a/src/applications/markup/engine/PhabricatorMarkupEngine.php b/src/applications/markup/engine/PhabricatorMarkupEngine.php index 3ba9378a5b..9e4d30950d 100644 --- a/src/applications/markup/engine/PhabricatorMarkupEngine.php +++ b/src/applications/markup/engine/PhabricatorMarkupEngine.php @@ -1,192 +1,190 @@ markupText($content_block); $phids = $engine->getTextMetadata( PhabricatorRemarkupRuleMention::KEY_MENTIONED, array()); $mentions += $phids; } return $mentions; } public static function newManiphestMarkupEngine() { return self::newMarkupEngine(array( )); } public static function newPhrictionMarkupEngine() { return self::newMarkupEngine(array( // Disable image macros on the wiki since they're less useful, we don't // cache documents, and the module is prohibitively expensive for large // documents. 'macros' => false, 'header.generate-toc' => true, )); } public static function newPhameMarkupEngine() { return self::newMarkupEngine(array( 'macros' => false, )); } public static function newDifferentialMarkupEngine(array $options = array()) { return self::newMarkupEngine(array( 'custom-inline' => PhabricatorEnv::getEnvConfig( 'differential.custom-remarkup-rules'), 'custom-block' => PhabricatorEnv::getEnvConfig( 'differential.custom-remarkup-block-rules'), 'differential.diff' => idx($options, 'differential.diff'), )); } public static function newDiffusionMarkupEngine(array $options = array()) { return self::newMarkupEngine(array( )); } public static function newProfileMarkupEngine() { return self::newMarkupEngine(array( )); } public static function newSlowvoteMarkupEngine() { return self::newMarkupEngine(array( )); } private static function getMarkupEngineDefaultConfiguration() { return array( 'pygments' => PhabricatorEnv::getEnvConfig('pygments.enabled'), 'fileproxy' => PhabricatorEnv::getEnvConfig('files.enable-proxy'), 'youtube' => PhabricatorEnv::getEnvConfig( 'remarkup.enable-embedded-youtube'), 'custom-inline' => array(), 'custom-block' => array(), 'differential.diff' => null, 'header.generate-toc' => false, 'macros' => true, 'uri.allowed-protocols' => PhabricatorEnv::getEnvConfig( 'uri.allowed-protocols'), ); } private static function newMarkupEngine(array $options) { $options += self::getMarkupEngineDefaultConfiguration(); $engine = new PhutilRemarkupEngine(); $engine->setConfig('preserve-linebreaks', true); $engine->setConfig('pygments.enabled', $options['pygments']); $engine->setConfig( 'uri.allowed-protocols', $options['uri.allowed-protocols']); $engine->setConfig('differential.diff', $options['differential.diff']); $engine->setConfig('header.generate-toc', $options['header.generate-toc']); $rules = array(); $rules[] = new PhutilRemarkupRuleEscapeRemarkup(); $rules[] = new PhutilRemarkupRuleMonospace(); $custom_rule_classes = $options['custom-inline']; if ($custom_rule_classes) { foreach ($custom_rule_classes as $custom_rule_class) { - PhutilSymbolLoader::loadClass($custom_rule_class); $rules[] = newv($custom_rule_class, array()); } } if ($options['fileproxy']) { $rules[] = new PhabricatorRemarkupRuleProxyImage(); } if ($options['youtube']) { $rules[] = new PhabricatorRemarkupRuleYoutube(); } $rules[] = new PhutilRemarkupRuleHyperlink(); $rules[] = new PhabricatorRemarkupRulePhriction(); $rules[] = new PhabricatorRemarkupRuleDifferentialHandle(); $rules[] = new PhabricatorRemarkupRuleManiphestHandle(); $rules[] = new PhabricatorRemarkupRuleEmbedFile(); $rules[] = new PhabricatorRemarkupRuleDifferential(); $rules[] = new PhabricatorRemarkupRuleDiffusion(); $rules[] = new PhabricatorRemarkupRuleManiphest(); $rules[] = new PhabricatorRemarkupRulePaste(); if ($options['macros']) { $rules[] = new PhabricatorRemarkupRuleImageMacro(); } $rules[] = new PhabricatorRemarkupRuleMention(); $rules[] = new PhutilRemarkupRuleEscapeHTML(); $rules[] = new PhutilRemarkupRuleBold(); $rules[] = new PhutilRemarkupRuleItalic(); $rules[] = new PhutilRemarkupRuleDel(); $blocks = array(); $blocks[] = new PhutilRemarkupEngineRemarkupQuotesBlockRule(); $blocks[] = new PhutilRemarkupEngineRemarkupLiteralBlockRule(); $blocks[] = new PhutilRemarkupEngineRemarkupHeaderBlockRule(); $blocks[] = new PhutilRemarkupEngineRemarkupListBlockRule(); $blocks[] = new PhutilRemarkupEngineRemarkupCodeBlockRule(); $blocks[] = new PhutilRemarkupEngineRemarkupNoteBlockRule(); $blocks[] = new PhutilRemarkupEngineRemarkupDefaultBlockRule(); $custom_block_rule_classes = $options['custom-block']; if ($custom_block_rule_classes) { foreach ($custom_block_rule_classes as $custom_block_rule_class) { - PhutilSymbolLoader::loadClass($custom_block_rule_class); $blocks[] = newv($custom_block_rule_class, array()); } } foreach ($blocks as $block) { if ($block instanceof PhutilRemarkupEngineRemarkupLiteralBlockRule) { $literal_rules = array(); $literal_rules[] = new PhutilRemarkupRuleEscapeHTML(); $literal_rules[] = new PhutilRemarkupRuleLinebreaks(); $block->setMarkupRules($literal_rules); } else if ( !($block instanceof PhutilRemarkupEngineRemarkupCodeBlockRule)) { $block->setMarkupRules($rules); } } $engine->setBlockRules($blocks); return $engine; } } diff --git a/src/applications/metamta/adapter/amazonses/PhabricatorMailImplementationAmazonSESAdapter.php b/src/applications/metamta/adapter/amazonses/PhabricatorMailImplementationAmazonSESAdapter.php index 3602c0df43..b383519f96 100644 --- a/src/applications/metamta/adapter/amazonses/PhabricatorMailImplementationAmazonSESAdapter.php +++ b/src/applications/metamta/adapter/amazonses/PhabricatorMailImplementationAmazonSESAdapter.php @@ -1,49 +1,52 @@ mailer->Mailer = 'amazon-ses'; $this->mailer->customMailer = $this; } public function supportsMessageIDHeader() { // Amazon SES will ignore any Message-ID we provide. return false; } + /** + * @phutil-external-symbol class SimpleEmailService + */ public function executeSend($body) { $key = PhabricatorEnv::getEnvConfig('amazon-ses.access-key'); $secret = PhabricatorEnv::getEnvConfig('amazon-ses.secret-key'); $root = phutil_get_library_root('phabricator'); $root = dirname($root); require_once $root.'/externals/amazon-ses/ses.php'; - $service = newv('SimpleEmailService', array($key, $secret)); + $service = new SimpleEmailService($key, $secret); $service->enableUseExceptions(true); return $service->sendRawEmail($body); } } diff --git a/src/applications/metamta/adapter/phpmailerlite/PhabricatorMailImplementationPHPMailerLiteAdapter.php b/src/applications/metamta/adapter/phpmailerlite/PhabricatorMailImplementationPHPMailerLiteAdapter.php index dffc69865f..99b21a695a 100644 --- a/src/applications/metamta/adapter/phpmailerlite/PhabricatorMailImplementationPHPMailerLiteAdapter.php +++ b/src/applications/metamta/adapter/phpmailerlite/PhabricatorMailImplementationPHPMailerLiteAdapter.php @@ -1,103 +1,106 @@ mailer = newv('PHPMailerLite', array($use_exceptions = true)); + $this->mailer = new PHPMailerLite($use_exceptions = true); $this->mailer->CharSet = 'utf-8'; } public function supportsMessageIDHeader() { return true; } public function setFrom($email, $name = '') { $this->mailer->SetFrom($email, $name, $crazy_side_effects = false); return $this; } public function addReplyTo($email, $name = '') { $this->mailer->AddReplyTo($email, $name); return $this; } public function addTos(array $emails) { foreach ($emails as $email) { $this->mailer->AddAddress($email); } return $this; } public function addCCs(array $emails) { foreach ($emails as $email) { $this->mailer->AddCC($email); } return $this; } public function addAttachment($data, $filename, $mimetype) { $this->mailer->AddStringAttachment( $data, $filename, 'base64', $mimetype ); return $this; } public function addHeader($header_name, $header_value) { if (strtolower($header_name) == 'message-id') { $this->mailer->MessageID = $header_value; } else { $this->mailer->AddCustomHeader($header_name.': '.$header_value); } return $this; } public function setBody($body) { $this->mailer->Body = $body; return $this; } public function setSubject($subject) { $this->mailer->Subject = $subject; return $this; } public function setIsHTML($is_html) { $this->mailer->IsHTML($is_html); return $this; } public function hasValidRecipients() { return true; } public function send() { return $this->mailer->Send(); } } diff --git a/src/applications/metamta/storage/receivedmail/PhabricatorMetaMTAReceivedMail.php b/src/applications/metamta/storage/receivedmail/PhabricatorMetaMTAReceivedMail.php index f3baa5915a..f7d2122f20 100644 --- a/src/applications/metamta/storage/receivedmail/PhabricatorMetaMTAReceivedMail.php +++ b/src/applications/metamta/storage/receivedmail/PhabricatorMetaMTAReceivedMail.php @@ -1,308 +1,308 @@ array( 'headers' => self::SERIALIZATION_JSON, 'bodies' => self::SERIALIZATION_JSON, 'attachments' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function setHeaders(array $headers) { // Normalize headers to lowercase. $normalized = array(); foreach ($headers as $name => $value) { $normalized[strtolower($name)] = $value; } $this->headers = $normalized; return $this; } public function getMessageID() { return idx($this->headers, 'message-id'); } public function getSubject() { return idx($this->headers, 'subject'); } public function processReceivedMail() { // If Phabricator sent the mail, always drop it immediately. This prevents // loops where, e.g., the public bug address is also a user email address // and creating a bug sends them an email, which loops. $is_phabricator_mail = idx( $this->headers, 'x-phabricator-sent-this-message'); if ($is_phabricator_mail) { $message = "Ignoring email with 'X-Phabricator-Sent-This-Message' ". "header to avoid loops."; return $this->setMessage($message)->save(); } $to = idx($this->headers, 'to'); $to = $this->getRawEmailAddress($to); $from = idx($this->headers, 'from'); $create_task = PhabricatorEnv::getEnvConfig( 'metamta.maniphest.public-create-email'); if ($create_task && $to == $create_task) { $receiver = new ManiphestTask(); $user = $this->lookupPublicUser(); if ($user) { $this->setAuthorPHID($user->getPHID()); } else { $default_author = PhabricatorEnv::getEnvConfig( 'metamta.maniphest.default-public-author'); if ($default_author) { $user = id(new PhabricatorUser())->loadOneWhere( 'username = %s', $default_author); if ($user) { $receiver->setOriginalEmailSource($from); } else { throw new Exception( "Phabricator is misconfigured, the configuration key ". "'metamta.maniphest.default-public-author' is set to user ". "'{$default_author}' but that user does not exist."); } } else { // TODO: We should probably bounce these since from the user's // perspective their email vanishes into a black hole. return $this->setMessage("Invalid public user '{$from}'.")->save(); } } $receiver->setAuthorPHID($user->getPHID()); $receiver->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE); $editor = new ManiphestTransactionEditor(); $handler = $editor->buildReplyHandler($receiver); $handler->setActor($user); $handler->receiveEmail($this); $this->setRelatedPHID($receiver->getPHID()); $this->setMessage('OK'); return $this->save(); } // We've already stripped this, so look for an object address which has // a format like: D291+291+b0a41ca848d66dcc@example.com $matches = null; $single_handle_prefix = PhabricatorEnv::getEnvConfig( 'metamta.single-reply-handler-prefix'); $prefixPattern = ($single_handle_prefix) ? preg_quote($single_handle_prefix, '/') . '\+' : ''; $pattern = "/^{$prefixPattern}((?:D|T|C)\d+)\+([\w]+)\+([a-f0-9]{16})@/U"; $ok = preg_match( $pattern, $to, $matches); if (!$ok) { return $this->setMessage("Unrecognized 'to' format: {$to}")->save(); } $receiver_name = $matches[1]; $user_id = $matches[2]; $hash = $matches[3]; if ($user_id == 'public') { if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) { return $this->setMessage("Public replies not enabled.")->save(); } $user = $this->lookupPublicUser(); if (!$user) { return $this->setMessage("Invalid public user '{$from}'.")->save(); } $use_user_hash = false; } else { $user = id(new PhabricatorUser())->load($user_id); if (!$user) { return $this->setMessage("Invalid private user '{$user_id}'.")->save(); } $use_user_hash = true; } if ($user->getIsDisabled()) { return $this->setMessage("User '{$user_id}' is disabled")->save(); } $this->setAuthorPHID($user->getPHID()); $receiver = self::loadReceiverObject($receiver_name); if (!$receiver) { return $this->setMessage("Invalid object '{$receiver_name}'")->save(); } $this->setRelatedPHID($receiver->getPHID()); if ($use_user_hash) { // This is a private reply-to address, check that the user hash is // correct. $check_phid = $user->getPHID(); } else { // This is a public reply-to address, check that the object hash is // correct. $check_phid = $receiver->getPHID(); } $expect_hash = self::computeMailHash($receiver->getMailKey(), $check_phid); // See note at computeOldMailHash(). $old_hash = self::computeOldMailHash($receiver->getMailKey(), $check_phid); if ($expect_hash != $hash && $old_hash != $hash) { return $this->setMessage("Invalid mail hash!")->save(); } if ($receiver instanceof ManiphestTask) { $editor = new ManiphestTransactionEditor(); $handler = $editor->buildReplyHandler($receiver); } else if ($receiver instanceof DifferentialRevision) { $handler = DifferentialMail::newReplyHandlerForRevision($receiver); } else if ($receiver instanceof PhabricatorRepositoryCommit) { $handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit( $receiver); } $handler->setActor($user); $handler->receiveEmail($this); $this->setMessage('OK'); return $this->save(); } public function getCleanTextBody() { $body = idx($this->bodies, 'text'); $parser = new PhabricatorMetaMTAEmailBodyParser(); return $parser->stripTextBody($body); } public static function loadReceiverObject($receiver_name) { if (!$receiver_name) { return null; } $receiver_type = $receiver_name[0]; $receiver_id = substr($receiver_name, 1); $class_obj = null; switch ($receiver_type) { case 'T': - $class_obj = newv('ManiphestTask', array()); + $class_obj = new ManiphestTask(); break; case 'D': - $class_obj = newv('DifferentialRevision', array()); + $class_obj = new DifferentialRevision(); break; case 'C': - $class_obj = newv('PhabricatorRepositoryCommit', array()); + $class_obj = new PhabricatorRepositoryCommit(); break; default: return null; } return $class_obj->load($receiver_id); } public static function computeMailHash($mail_key, $phid) { $global_mail_key = PhabricatorEnv::getEnvConfig('phabricator.mail-key'); $hash = PhabricatorHash::digest($mail_key.$global_mail_key.$phid); return substr($hash, 0, 16); } public static function computeOldMailHash($mail_key, $phid) { // TODO: Remove this method entirely in a couple of months. We've moved from // plain sha1 to sha1+hmac to make the codebase more auditable for good uses // of hash functions, but still accept the old hashes on email replies to // avoid breaking things. Once we've been sending only hmac hashes for a // while, remove this and start rejecting old hashes. See T547. $global_mail_key = PhabricatorEnv::getEnvConfig('phabricator.mail-key'); $hash = sha1($mail_key.$global_mail_key.$phid); return substr($hash, 0, 16); } /** * Strip an email address down to the actual user@domain.tld part if * necessary, since sometimes it will have formatting like * '"Abraham Lincoln" '. */ private function getRawEmailAddress($address) { $matches = null; $ok = preg_match('/<(.*)>/', $address, $matches); if ($ok) { $address = $matches[1]; } return $address; } private function lookupPublicUser() { $from = idx($this->headers, 'from'); $from = $this->getRawEmailAddress($from); $user = PhabricatorUser::loadOneWithEmailAddress($from); // If Phabricator is configured to allow "Reply-To" authentication, try // the "Reply-To" address if we failed to match the "From" address. $config_key = 'metamta.insecure-auth-with-reply-to'; $allow_reply_to = PhabricatorEnv::getEnvConfig($config_key); if (!$user && $allow_reply_to) { $reply_to = idx($this->headers, 'reply-to'); $reply_to = $this->getRawEmailAddress($reply_to); if ($reply_to) { $user = PhabricatorUser::loadOneWithEmailAddress($reply_to); } } return $user; } } diff --git a/src/applications/phid/handle/data/PhabricatorObjectHandleData.php b/src/applications/phid/handle/data/PhabricatorObjectHandleData.php index 55cb010c7b..9aac38a5b6 100644 --- a/src/applications/phid/handle/data/PhabricatorObjectHandleData.php +++ b/src/applications/phid/handle/data/PhabricatorObjectHandleData.php @@ -1,490 +1,470 @@ phids = array_unique($phids); } public static function loadOneHandle($phid) { $handles = id(new PhabricatorObjectHandleData(array($phid)))->loadHandles(); return $handles[$phid]; } public function loadObjects() { $types = array(); foreach ($this->phids as $phid) { $type = phid_get_type($phid); $types[$type][] = $phid; } $objects = array(); foreach ($types as $type => $phids) { switch ($type) { case PhabricatorPHIDConstants::PHID_TYPE_USER: - $user_dao = newv('PhabricatorUser', array()); + $user_dao = new PhabricatorUser(); $users = $user_dao->loadAllWhere( 'phid in (%Ls)', $phids); foreach ($users as $user) { $objects[$user->getPHID()] = $user; } break; case PhabricatorPHIDConstants::PHID_TYPE_CMIT: - $commit_dao = newv('PhabricatorRepositoryCommit', array()); + $commit_dao = new PhabricatorRepositoryCommit(); $commits = $commit_dao->loadAllWhere( 'phid IN (%Ls)', $phids); $commit_data = array(); if ($commits) { - $data_dao = newv('PhabricatorRepositoryCommitData', array()); + $data_dao = new PhabricatorRepositoryCommitData(); $commit_data = $data_dao->loadAllWhere( 'commitID IN (%Ld)', mpull($commits, 'getID')); $commit_data = mpull($commit_data, null, 'getCommitID'); } foreach ($commits as $commit) { $data = idx($commit_data, $commit->getID()); if ($data) { $commit->attachCommitData($data); $objects[$commit->getPHID()] = $commit; } else { // If we couldn't load the commit data, just act as though we // couldn't load the object at all so we don't load half an object. } } break; case PhabricatorPHIDConstants::PHID_TYPE_TASK: - $task_dao = newv('ManiphestTask', array()); + $task_dao = new ManiphestTask(); $tasks = $task_dao->loadAllWhere( 'phid IN (%Ls)', $phids); foreach ($tasks as $task) { $objects[$task->getPHID()] = $task; } break; case PhabricatorPHIDConstants::PHID_TYPE_DREV: - $revision_dao = newv('DifferentialRevision', array()); + $revision_dao = new DifferentialRevision(); $revisions = $revision_dao->loadAllWhere( 'phid IN (%Ls)', $phids); foreach ($revisions as $revision) { $objects[$revision->getPHID()] = $revision; } break; } } return $objects; } public function loadHandles() { $types = phid_group_by_type($this->phids); $handles = array(); $external_loaders = PhabricatorEnv::getEnvConfig('phid.external-loaders'); foreach ($types as $type => $phids) { switch ($type) { case PhabricatorPHIDConstants::PHID_TYPE_MAGIC: // Black magic! foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); $handle->setType($type); switch ($phid) { case ManiphestTaskOwner::OWNER_UP_FOR_GRABS: $handle->setName('Up For Grabs'); $handle->setFullName('upforgrabs (Up For Grabs)'); $handle->setComplete(true); break; case ManiphestTaskOwner::PROJECT_NO_PROJECT: $handle->setName('No Project'); $handle->setFullName('noproject (No Project)'); $handle->setComplete(true); break; default: $handle->setName('Foul Magicks'); break; } $handles[$phid] = $handle; } break; case PhabricatorPHIDConstants::PHID_TYPE_USER: - $class = 'PhabricatorUser'; - PhutilSymbolLoader::loadClass($class); - $object = newv($class, array()); + $object = new PhabricatorUser(); $users = $object->loadAllWhere('phid IN (%Ls)', $phids); $users = mpull($users, null, 'getPHID'); $image_phids = mpull($users, 'getProfileImagePHID'); $image_phids = array_unique(array_filter($image_phids)); $images = array(); if ($image_phids) { $images = id(new PhabricatorFile())->loadAllWhere( 'phid IN (%Ls)', $image_phids); $images = mpull($images, 'getBestURI', 'getPHID'); } $statuses = id(new PhabricatorUserStatus())->loadCurrentStatuses( $phids); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); $handle->setType($type); if (empty($users[$phid])) { $handle->setName('Unknown User'); } else { $user = $users[$phid]; $handle->setName($user->getUsername()); $handle->setURI('/p/'.$user->getUsername().'/'); $handle->setFullName( $user->getUsername().' ('.$user->getRealName().')'); $handle->setAlternateID($user->getID()); $handle->setComplete(true); if (isset($statuses[$phid])) { $handle->setStatus($statuses[$phid]->getTextStatus()); } $handle->setDisabled($user->getIsDisabled()); $img_uri = idx($images, $user->getProfileImagePHID()); if ($img_uri) { $handle->setImageURI($img_uri); } else { $handle->setImageURI( PhabricatorUser::getDefaultProfileImageURI()); } } $handles[$phid] = $handle; } break; case PhabricatorPHIDConstants::PHID_TYPE_MLST: - $class = 'PhabricatorMetaMTAMailingList'; - - PhutilSymbolLoader::loadClass($class); - $object = newv($class, array()); + $object = new PhabricatorMetaMTAMailingList(); $lists = $object->loadAllWhere('phid IN (%Ls)', $phids); $lists = mpull($lists, null, 'getPHID'); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); $handle->setType($type); if (empty($lists[$phid])) { $handle->setName('Unknown Mailing List'); } else { $list = $lists[$phid]; $handle->setName($list->getName()); $handle->setURI($list->getURI()); $handle->setFullName($list->getName()); $handle->setComplete(true); } $handles[$phid] = $handle; } break; case PhabricatorPHIDConstants::PHID_TYPE_DREV: - $class = 'DifferentialRevision'; - PhutilSymbolLoader::loadClass($class); - $object = newv($class, array()); + $object = new DifferentialRevision(); $revs = $object->loadAllWhere('phid in (%Ls)', $phids); $revs = mpull($revs, null, 'getPHID'); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); $handle->setType($type); if (empty($revs[$phid])) { $handle->setName('Unknown Revision'); } else { $rev = $revs[$phid]; $handle->setName($rev->getTitle()); $handle->setURI('/D'.$rev->getID()); $handle->setFullName('D'.$rev->getID().': '.$rev->getTitle()); $handle->setComplete(true); $status = $rev->getStatus(); if (($status == ArcanistDifferentialRevisionStatus::CLOSED) || ($status == ArcanistDifferentialRevisionStatus::ABANDONED)) { $closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; $handle->setStatus($closed); } } $handles[$phid] = $handle; } break; case PhabricatorPHIDConstants::PHID_TYPE_CMIT: - $class = 'PhabricatorRepositoryCommit'; - PhutilSymbolLoader::loadClass($class); - $object = newv($class, array()); + $object = new PhabricatorRepositoryCommit(); $commits = $object->loadAllWhere('phid in (%Ls)', $phids); $commits = mpull($commits, null, 'getPHID'); $repository_ids = array(); $callsigns = array(); if ($commits) { $repository_ids = mpull($commits, 'getRepositoryID'); $repositories = id(new PhabricatorRepository())->loadAllWhere( 'id in (%Ld)', array_unique($repository_ids)); $callsigns = mpull($repositories, 'getCallsign'); } foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); $handle->setType($type); if (empty($commits[$phid]) || !isset($callsigns[$repository_ids[$phid]])) { $handle->setName('Unknown Commit'); } else { $commit = $commits[$phid]; $callsign = $callsigns[$repository_ids[$phid]]; $repository = $repositories[$repository_ids[$phid]]; $commit_identifier = $commit->getCommitIdentifier(); // In case where the repository for the commit was deleted, // we don't have have info about the repository anymore. if ($repository) { $name = $repository->formatCommitName($commit_identifier); $handle->setName($name); } else { $handle->setName('Commit '.'r'.$callsign.$commit_identifier); } $handle->setURI('/r'.$callsign.$commit_identifier); $handle->setFullName('r'.$callsign.$commit_identifier); $handle->setTimestamp($commit->getEpoch()); $handle->setComplete(true); } $handles[$phid] = $handle; } break; case PhabricatorPHIDConstants::PHID_TYPE_TASK: - $class = 'ManiphestTask'; - PhutilSymbolLoader::loadClass($class); - $object = newv($class, array()); + $object = new ManiphestTask(); $tasks = $object->loadAllWhere('phid in (%Ls)', $phids); $tasks = mpull($tasks, null, 'getPHID'); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); $handle->setType($type); if (empty($tasks[$phid])) { $handle->setName('Unknown Revision'); } else { $task = $tasks[$phid]; $handle->setName($task->getTitle()); $handle->setURI('/T'.$task->getID()); $handle->setFullName('T'.$task->getID().': '.$task->getTitle()); $handle->setComplete(true); $handle->setAlternateID($task->getID()); if ($task->getStatus() != ManiphestTaskStatus::STATUS_OPEN) { $closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; $handle->setStatus($closed); } } $handles[$phid] = $handle; } break; case PhabricatorPHIDConstants::PHID_TYPE_FILE: - $class = 'PhabricatorFile'; - PhutilSymbolLoader::loadClass($class); - $object = newv($class, array()); + $object = new PhabricatorFile(); $files = $object->loadAllWhere('phid IN (%Ls)', $phids); $files = mpull($files, null, 'getPHID'); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); $handle->setType($type); if (empty($files[$phid])) { $handle->setName('Unknown File'); } else { $file = $files[$phid]; $handle->setName($file->getName()); $handle->setURI($file->getBestURI()); $handle->setComplete(true); } $handles[$phid] = $handle; } break; case PhabricatorPHIDConstants::PHID_TYPE_PROJ: - $class = 'PhabricatorProject'; - PhutilSymbolLoader::loadClass($class); - $object = newv($class, array()); + $object = new PhabricatorProject(); $projects = $object->loadAllWhere('phid IN (%Ls)', $phids); $projects = mpull($projects, null, 'getPHID'); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); $handle->setType($type); if (empty($projects[$phid])) { $handle->setName('Unknown Project'); } else { $project = $projects[$phid]; $handle->setName($project->getName()); $handle->setURI('/project/view/'.$project->getID().'/'); $handle->setComplete(true); } $handles[$phid] = $handle; } break; case PhabricatorPHIDConstants::PHID_TYPE_REPO: - $class = 'PhabricatorRepository'; - PhutilSymbolLoader::loadClass($class); - $object = newv($class, array()); + $object = new PhabricatorRepository(); $repositories = $object->loadAllWhere('phid in (%Ls)', $phids); $repositories = mpull($repositories, null, 'getPHID'); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); $handle->setType($type); if (empty($repositories[$phid])) { $handle->setName('Unknown Repository'); } else { $repository = $repositories[$phid]; $handle->setName($repository->getCallsign()); $handle->setURI('/diffusion/'.$repository->getCallsign().'/'); $handle->setComplete(true); } $handles[$phid] = $handle; } break; case PhabricatorPHIDConstants::PHID_TYPE_OPKG: - $class = 'PhabricatorOwnersPackage'; - PhutilSymbolLoader::loadClass($class); - $object = newv($class, array()); + $object = new PhabricatorOwnersPackage(); $packages = $object->loadAllWhere('phid in (%Ls)', $phids); $packages = mpull($packages, null, 'getPHID'); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); $handle->setType($type); if (empty($packages[$phid])) { $handle->setName('Unknown Package'); } else { $package = $packages[$phid]; $handle->setName($package->getName()); $handle->setURI('/owners/package/'.$package->getID().'/'); $handle->setComplete(true); } $handles[$phid] = $handle; } break; case PhabricatorPHIDConstants::PHID_TYPE_APRJ: - $project_dao = newv('PhabricatorRepositoryArcanistProject', array()); + $project_dao = new PhabricatorRepositoryArcanistProject(); $projects = $project_dao->loadAllWhere( 'phid IN (%Ls)', $phids); $projects = mpull($projects, null, 'getPHID'); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); $handle->setType($type); if (empty($projects[$phid])) { $handle->setName('Unknown Arcanist Project'); } else { $project = $projects[$phid]; $handle->setName($project->getName()); $handle->setComplete(true); } $handles[$phid] = $handle; } break; case PhabricatorPHIDConstants::PHID_TYPE_WIKI: - $document_dao = newv('PhrictionDocument', array()); - $content_dao = newv('PhrictionContent', array()); + $document_dao = new PhrictionDocument(); + $content_dao = new PhrictionContent(); $conn = $document_dao->establishConnection('r'); $documents = queryfx_all( $conn, 'SELECT * FROM %T document JOIN %T content ON document.contentID = content.id WHERE document.phid IN (%Ls)', $document_dao->getTableName(), $content_dao->getTableName(), $phids); $documents = ipull($documents, null, 'phid'); foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($phid); $handle->setType($type); if (empty($documents[$phid])) { $handle->setName('Unknown Document'); } else { $info = $documents[$phid]; $handle->setName($info['title']); $handle->setURI(PhrictionDocument::getSlugURI($info['slug'])); $handle->setComplete(true); } $handles[$phid] = $handle; } break; default: $loader = null; if (isset($external_loaders[$type])) { $loader = $external_loaders[$type]; } else if (isset($external_loaders['*'])) { $loader = $external_loaders['*']; } if ($loader) { - PhutilSymbolLoader::loadClass($loader); $object = newv($loader, array()); $handles += $object->loadHandles($phids); break; } foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); $handle->setType($type); $handle->setPHID($phid); $handle->setName('Unknown Object'); $handle->setFullName('An Unknown Object'); $handles[$phid] = $handle; } break; } } return $handles; } } diff --git a/src/applications/repository/worker/commitmessageparser/base/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/base/PhabricatorRepositoryCommitMessageParserWorker.php index cd835ef914..9017a8cc88 100644 --- a/src/applications/repository/worker/commitmessageparser/base/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/base/PhabricatorRepositoryCommitMessageParserWorker.php @@ -1,327 +1,326 @@ commit; $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere( 'commitID = %d', $commit->getID()); if (!$data) { $data = new PhabricatorRepositoryCommitData(); } $data->setCommitID($commit->getID()); $data->setAuthorName($author); $data->setCommitMessage($message); if ($committer) { $data->setCommitDetail('committer', $committer); } $repository = $this->repository; $detail_parser = $repository->getDetail( 'detail-parser', 'PhabricatorRepositoryDefaultCommitMessageDetailParser'); if ($detail_parser) { - PhutilSymbolLoader::loadClass($detail_parser); $parser_obj = newv($detail_parser, array($commit, $data)); $parser_obj->parseCommitDetails(); } $author_phid = $data->getCommitDetail('authorPHID'); if ($author_phid) { $commit->setAuthorPHID($author_phid); $commit->save(); } $conn_w = id(new DifferentialRevision())->establishConnection('w'); // NOTE: The `differential_commit` table has a unique ID on `commitPHID`, // preventing more than one revision from being associated with a commit. // Generally this is good and desirable, but with the advent of hash // tracking we may end up in a situation where we match several different // revisions. We just kind of ignore this and pick one, we might want to // revisit this and do something differently. (If we match several revisions // someone probably did something very silly, though.) $revision_id = $data->getCommitDetail('differential.revisionID'); if (!$revision_id) { $hashes = $this->getCommitHashes( $this->repository, $this->commit); if ($hashes) { $query = new DifferentialRevisionQuery(); $query->withCommitHashes($hashes); $revisions = $query->execute(); if (!empty($revisions)) { $revision = $this->identifyBestRevision($revisions); $revision_id = $revision->getID(); } } } if ($revision_id) { $revision = id(new DifferentialRevision())->load($revision_id); if ($revision) { queryfx( $conn_w, 'INSERT IGNORE INTO %T (revisionID, commitPHID) VALUES (%d, %s)', DifferentialRevision::TABLE_COMMIT, $revision->getID(), $commit->getPHID()); $commit_is_new = $conn_w->getAffectedRows(); $message = null; $committer = $data->getCommitDetail('authorPHID'); if (!$committer) { $committer = $revision->getAuthorPHID(); $message = 'Closed by '.$data->getAuthorName().'.'; } if ($commit_is_new) { $diff = $this->attachToRevision($revision, $committer); } $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; $should_close = ($revision->getStatus() != $status_closed) && (!$repository->getDetail('disable-autoclose', false)); if ($should_close) { $revision->setDateCommitted($commit->getEpoch()); $editor = new DifferentialCommentEditor( $revision, $committer, DifferentialAction::ACTION_CLOSE); $editor->setIsDaemonWorkflow(true); if ($commit_is_new) { $vs_diff = $this->loadChangedByCommit($diff); if ($vs_diff) { $data->setCommitDetail('vsDiff', $vs_diff->getID()); $changed_by_commit = PhabricatorEnv::getProductionURI( '/D'.$revision->getID(). '?vs='.$vs_diff->getID(). '&id='.$diff->getID(). '#differential-review-toc'); $editor->setChangedByCommit($changed_by_commit); } } $editor->setMessage($message)->save(); } } } $data->save(); } private function attachToRevision( DifferentialRevision $revision, $committer) { $drequest = DiffusionRequest::newFromDictionary(array( 'repository' => $this->repository, 'commit' => $this->commit->getCommitIdentifier(), )); $raw_diff = DiffusionRawDiffQuery::newFromDiffusionRequest($drequest) ->loadRawDiff(); $changes = id(new ArcanistDiffParser())->parseDiff($raw_diff); $diff = DifferentialDiff::newFromRawChanges($changes) ->setRevisionID($revision->getID()) ->setAuthorPHID($committer) ->setCreationMethod('commit') ->setSourceControlSystem($this->repository->getVersionControlSystem()) ->setLintStatus(DifferentialLintStatus::LINT_SKIP) ->setUnitStatus(DifferentialUnitStatus::UNIT_SKIP) ->setDateCreated($this->commit->getEpoch()) ->setDescription( 'Commit r'. $this->repository->getCallsign(). $this->commit->getCommitIdentifier()); // TODO: This is not correct in SVN where one repository can have multiple // Arcanist projects. $arcanist_project = id(new PhabricatorRepositoryArcanistProject()) ->loadOneWhere('repositoryID = %d LIMIT 1', $this->repository->getID()); if ($arcanist_project) { $diff->setArcanistProjectPHID($arcanist_project->getPHID()); } $parents = DiffusionCommitParentsQuery::newFromDiffusionRequest($drequest) ->loadParents(); if ($parents) { $diff->setSourceControlBaseRevision(head_key($parents)); } // TODO: Attach binary files. return $diff->save(); } private function loadChangedByCommit(DifferentialDiff $diff) { $repository = $this->repository; $vs_changesets = array(); $vs_diff = id(new DifferentialDiff())->loadOneWhere( 'revisionID = %d AND creationMethod != %s ORDER BY id DESC LIMIT 1', $diff->getRevisionID(), 'commit'); foreach ($vs_diff->loadChangesets() as $changeset) { $path = $changeset->getAbsoluteRepositoryPath($repository, $vs_diff); $vs_changesets[$path] = $changeset; } $changesets = array(); foreach ($diff->getChangesets() as $changeset) { $path = $changeset->getAbsoluteRepositoryPath($repository, $diff); $changesets[$path] = $changeset; } if (array_fill_keys(array_keys($changesets), true) != array_fill_keys(array_keys($vs_changesets), true)) { return $vs_diff; } $hunks = id(new DifferentialHunk())->loadAllWhere( 'changesetID IN (%Ld)', mpull($vs_changesets, 'getID')); $hunks = mgroup($hunks, 'getChangesetID'); foreach ($vs_changesets as $changeset) { $changeset->attachHunks(idx($hunks, $changeset->getID(), array())); } $file_phids = array(); foreach ($vs_changesets as $changeset) { $metadata = $changeset->getMetadata(); $file_phid = idx($metadata, 'new:binary-phid'); if ($file_phid) { $file_phids[$file_phid] = $file_phid; } } $files = array(); if ($file_phids) { $files = id(new PhabricatorFile())->loadAllWhere( 'phid IN (%Ls)', $file_phids); $files = mpull($files, null, 'getPHID'); } foreach ($changesets as $path => $changeset) { $vs_changeset = $vs_changesets[$path]; $file_phid = idx($vs_changeset->getMetadata(), 'new:binary-phid'); if ($file_phid) { if (!isset($files[$file_phid])) { return $vs_diff; } $drequest = DiffusionRequest::newFromDictionary(array( 'repository' => $this->repository, 'commit' => $this->commit->getCommitIdentifier(), 'path' => $path, )); $corpus = DiffusionFileContentQuery::newFromDiffusionRequest($drequest) ->loadFileContent() ->getCorpus(); if ($files[$file_phid]->loadFileData() != $corpus) { return $vs_diff; } } else if ($changeset->makeChangesWithContext() != $vs_changeset->makeChangesWithContext()) { return $vs_diff; } } return null; } /** * When querying for revisions by hash, more than one revision may be found. * This function identifies the "best" revision from such a set. Typically, * there is only one revision found. Otherwise, we try to pick an accepted * revision first, followed by an open revision, and otherwise we go with a * closed or abandoned revision as a last resort. */ private function identifyBestRevision(array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); // get the simplest, common case out of the way if (count($revisions) == 1) { return reset($revisions); } $first_choice = array(); $second_choice = array(); $third_choice = array(); foreach ($revisions as $revision) { switch ($revision->getStatus()) { // "Accepted" revisions -- ostensibly what we're looking for! case ArcanistDifferentialRevisionStatus::ACCEPTED: $first_choice[] = $revision; break; // "Open" revisions case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: $second_choice[] = $revision; break; // default is a wtf? here default: case ArcanistDifferentialRevisionStatus::ABANDONED: case ArcanistDifferentialRevisionStatus::CLOSED: $third_choice[] = $revision; break; } } // go down the ladder like a bro at last call if (!empty($first_choice)) { return $this->identifyMostRecentRevision($first_choice); } if (!empty($second_choice)) { return $this->identifyMostRecentRevision($second_choice); } if (!empty($third_choice)) { return $this->identifyMostRecentRevision($third_choice); } } /** * Given a set of revisions, returns the revision with the latest * updated time. This is ostensibly the most recent revision. */ private function identifyMostRecentRevision(array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $revisions = msort($revisions, 'getDateModified'); return end($revisions); } } diff --git a/src/infrastructure/daemon/workers/taskmaster/PhabricatorTaskmasterDaemon.php b/src/infrastructure/daemon/workers/taskmaster/PhabricatorTaskmasterDaemon.php index 8152adc206..e3fdb6c418 100644 --- a/src/infrastructure/daemon/workers/taskmaster/PhabricatorTaskmasterDaemon.php +++ b/src/infrastructure/daemon/workers/taskmaster/PhabricatorTaskmasterDaemon.php @@ -1,128 +1,128 @@ getLeaseOwnershipName(); $task_table = new PhabricatorWorkerTask(); $taskdata_table = new PhabricatorWorkerTaskData(); $sleep = 0; do { $conn_w = $task_table->establishConnection('w'); queryfx( $conn_w, 'UPDATE %T SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + 15 WHERE leaseOwner IS NULL LIMIT 1', $task_table->getTableName(), $lease_ownership_name); $rows = $conn_w->getAffectedRows(); if (!$rows) { $rows = queryfx( $conn_w, 'UPDATE %T SET leaseOwner = %s, leaseExpires = UNIX_TIMESTAMP() + 15 WHERE leaseExpires < UNIX_TIMESTAMP() LIMIT 1', $task_table->getTableName(), $lease_ownership_name); $rows = $conn_w->getAffectedRows(); } if ($rows) { $data = queryfx_all( $conn_w, 'SELECT task.*, taskdata.data _taskData, UNIX_TIMESTAMP() _serverTime FROM %T task LEFT JOIN %T taskdata ON taskdata.id = task.dataID WHERE leaseOwner = %s AND leaseExpires > UNIX_TIMESTAMP() LIMIT 1', $task_table->getTableName(), $taskdata_table->getTableName(), $lease_ownership_name); $tasks = $task_table->loadAllFromArray($data); $tasks = mpull($tasks, null, 'getID'); $task_data = array(); foreach ($data as $row) { $tasks[$row['id']]->setServerTime($row['_serverTime']); if ($row['_taskData']) { $task_data[$row['id']] = json_decode($row['_taskData'], true); } else { $task_data[$row['id']] = null; } } foreach ($tasks as $task) { // TODO: We should detect if we acquired a task with an expired lease // and log about it / bump up failure count. // TODO: We should detect if we acquired a task with an excessive // failure count and fail it permanently. $data = idx($task_data, $task->getID()); $class = $task->getTaskClass(); try { - PhutilSymbolLoader::loadClass($class); - if (!is_subclass_of($class, 'PhabricatorWorker')) { + if (!class_exists($class) || + !is_subclass_of($class, 'PhabricatorWorker')) { throw new Exception( "Task class '{$class}' does not extend PhabricatorWorker."); } $worker = newv($class, array($data)); $lease = $worker->getRequiredLeaseTime(); if ($lease !== null) { $task->setLeaseDuration($lease); } $worker->executeTask(); $task->delete(); if ($data !== null) { queryfx( $conn_w, 'DELETE FROM %T WHERE id = %d', $taskdata_table->getTableName(), $task->getDataID()); } } catch (Exception $ex) { $task->setFailureCount($task->getFailureCount() + 1); $task->save(); throw $ex; } } $sleep = 0; } else { $sleep = min($sleep + 1, 30); } $this->sleep($sleep); } while (true); } private function getLeaseOwnershipName() { static $name = null; if ($name === null) { $name = getmypid().':'.time().':'.php_uname('n'); } return $name; } } diff --git a/src/infrastructure/setup/PhabricatorSetup.php b/src/infrastructure/setup/PhabricatorSetup.php index 2ce57a9193..e42c96b471 100644 --- a/src/infrastructure/setup/PhabricatorSetup.php +++ b/src/infrastructure/setup/PhabricatorSetup.php @@ -1,818 +1,816 @@ getMessage(); self::write("Unable to load modules from libphutil: {$message}\n"); $open_libphutil = false; } try { - phutil_require_module('arcanist', 'workflow/base'); - $open_arcanist = true; + $open_arcanist = class_exists('ArcanistDiffParser'); } catch (Exception $ex) { $message = $ex->getMessage(); self::write("Unable to load modules from Arcanist: {$message}\n"); $open_arcanist = false; } $open_urandom = false; try { Filesystem::readRandomBytes(1); $open_urandom = true; } catch (FilesystemException $ex) { self::write($ex->getMessage()."\n"); } try { $tmp = new TempFile(); file_put_contents($tmp, '.'); $open_tmp = @fopen((string)$tmp, 'r'); } catch (Exception $ex) { $message = $ex->getMessage(); $dir = sys_get_temp_dir(); self::write("Unable to open temp files from '{$dir}': {$message}\n"); $open_tmp = false; } if (!$open_urandom || !$open_tmp || !$open_libphutil || !$open_arcanist) { self::writeFailure(); self::write( "Setup failure! Your server is configured with 'open_basedir' in ". "php.ini which prevents Phabricator from opening files it needs to ". "access. Either make the setting more permissive or remove it. It ". "is unlikely you derive significant security benefits from having ". "this configured; files outside this directory can still be ". "accessed through system command execution."); return; } else { self::write( "[WARN] You have an 'open_basedir' configured in your php.ini. ". "Although the setting seems permissive enough that Phabricator ". "will run properly, you may run into problems because of it. It is ". "unlikely you gain much real security benefit from having it ". "configured, because the application can still access files outside ". "the 'open_basedir' by running system commands.\n"); } } else { self::write(" okay 'open_basedir' is not set.\n"); } if (!PhabricatorEnv::getEnvConfig('security.alternate-file-domain')) { self::write( "[WARN] You have not configured 'security.alternate-file-domain'. ". "This makes your installation vulnerable to attack. Make sure you ". "read the documentation for this parameter and understand the ". "consequences of leaving it unconfigured.\n"); } $path = getenv('PATH'); if (empty($path)) { self::writeFailure(); self::write( "Setup failure! The environmental \$PATH variable is empty. ". "Phabricator needs to execute system commands like 'svn', 'git', ". "'hg', and 'diff'. Set up your webserver so that it passes a valid ". "\$PATH to the PHP process.\n\n"); if (php_sapi_name() == 'fpm-fcgi') { self::write( "You're running php-fpm, so the easiest way to do this is to add ". "this line to your php-fpm.conf:\n\n". " env[PATH] = /usr/local/bin:/usr/bin:/bin\n\n". "Then restart php-fpm.\n"); } return; } else { self::write(" okay \$PATH is nonempty.\n"); } self::write("[OKAY] Core configuration OKAY.\n"); self::writeHeader("REQUIRED PHP EXTENSIONS"); $extensions = array( 'mysql', 'hash', 'json', 'openssl', 'mbstring', 'iconv', // There is a chance we might not need this, but some configurations (like // Amazon SES) will require it. Just mark it 'required' since it's widely // available and relatively core. 'curl', ); foreach ($extensions as $extension) { $ok = self::requireExtension($extension); if (!$ok) { self::writeFailure(); self::write("Setup failure! Install PHP extension '{$extension}'."); return; } } list($err, $stdout, $stderr) = exec_manual('which php'); if ($err) { self::writeFailure(); self::write("Unable to locate 'php' on the command line from the web ". "server. Verify that 'php' is in the webserver's PATH.\n". " err: {$err}\n". "stdout: {$stdout}\n". "stderr: {$stderr}\n"); return; } else { self::write(" okay PHP binary found on the command line.\n"); $php_bin = trim($stdout); } // NOTE: In cPanel + suphp installs, 'php' may be the PHP CGI SAPI, not the // PHP CLI SAPI. proc_open() will pass the environment to the child process, // which will re-execute the webpage (causing an infinite number of // processes to spawn). To test that the 'php' binary is safe to execute, // we call php_sapi_name() using "env -i" to wipe the environment so it // doesn't execute another reuqest if it's the wrong binary. We can't use // "-r" because php-cgi doesn't support that flag. $tmp_file = new TempFile('sapi.php'); Filesystem::writeFile($tmp_file, 'getProtocol(); $allowed_protocols = array( 'http' => true, 'https' => true, ); if (empty($allowed_protocols[$protocol])) { self::writeFailure(); self::write( "You must specify the protocol over which your host works (e.g.: ". "\"http:// or https://\")\nin your custom config file.\nRefer to ". "'default.conf.php' for documentation on configuration options.\n"); return; } if (preg_match('/.*\/$/', $host)) { self::write(" okay phabricator.base-uri protocol\n"); } else { self::writeFailure(); self::write( "You must add a trailing slash at the end of the host\n(e.g.: ". "\"http://phabricator.example.com/ instead of ". "http://phabricator.example.com\")\nin your custom config file.". "\nRefer to 'default.conf.php' for documentation on configuration ". "options.\n"); return; } $host_domain = $host_uri->getDomain(); if (strpos($host_domain, '.') !== false) { self::write(" okay phabricator.base-uri domain\n"); } else { self::writeFailure(); self::write( "You must host Phabricator on a domain that contains a dot ('.'). ". "The current domain, '{$host_domain}', does not have a dot, so some ". "browsers will not set cookies on it. For instance, ". "'http://example.com/ is OK, but 'http://example/' won't work. ". "If you are using localhost, create an entry in the hosts file like ". "'127.0.0.1 example.com', and access the localhost with ". "'http://example.com/'."); return; } } $timezone = nonempty( PhabricatorEnv::getEnvConfig('phabricator.timezone'), ini_get('date.timezone')); if (!$timezone) { self::writeFailure(); self::write( "Setup failure! Your configuration fails to specify a server ". "timezone. Either set 'date.timezone' in your php.ini or ". "'phabricator.timezone' in your Phabricator configuration. See the ". "PHP documentation for a list of supported timezones:\n\n". "http://us.php.net/manual/en/timezones.php\n"); return; } else { self::write(" okay Timezone '{$timezone}' configured.\n"); } self::write("[OKAY] Basic configuration OKAY\n"); $issue_gd_warning = false; self::writeHeader('GD LIBRARY'); if (extension_loaded('gd')) { self::write(" okay Extension 'gd' is loaded.\n"); $image_type_map = array( 'imagepng' => 'PNG', 'imagegif' => 'GIF', 'imagejpeg' => 'JPEG', ); foreach ($image_type_map as $function => $image_type) { if (function_exists($function)) { self::write(" okay Support for '{$image_type}' is available.\n"); } else { self::write(" warn Support for '{$image_type}' is not available!\n"); $issue_gd_warning = true; } } } else { self::write(" warn Extension 'gd' is not loaded.\n"); $issue_gd_warning = true; } if ($issue_gd_warning) { self::write( "[WARN] The 'gd' library is missing or lacks full support. ". "Phabricator will not be able to generate image thumbnails without ". "gd.\n"); } else { self::write("[OKAY] 'gd' loaded and has full image type support.\n"); } self::writeHeader('FACEBOOK INTEGRATION'); $fb_auth = PhabricatorEnv::getEnvConfig('facebook.auth-enabled'); if (!$fb_auth) { self::write(" skip 'facebook.auth-enabled' not enabled.\n"); } else { self::write(" okay 'facebook.auth-enabled' is enabled.\n"); $app_id = PhabricatorEnv::getEnvConfig('facebook.application-id'); $app_secret = PhabricatorEnv::getEnvConfig('facebook.application-secret'); if (!$app_id) { self::writeFailure(); self::write( "Setup failure! 'facebook.auth-enabled' is true but there is no ". "setting for 'facebook.application-id'.\n"); return; } else { self::write(" okay 'facebook.application-id' is set.\n"); } if (!is_string($app_id)) { self::writeFailure(); self::write( "Setup failure! 'facebook.application-id' should be a string."); return; } else { self::write(" okay 'facebook.application-id' is string.\n"); } if (!$app_secret) { self::writeFailure(); self::write( "Setup failure! 'facebook.auth-enabled' is true but there is no ". "setting for 'facebook.application-secret'."); return; } else { self::write(" okay 'facebook.application-secret is set.\n"); } self::write("[OKAY] Facebook integration OKAY\n"); } self::writeHeader("MySQL DATABASE & STORAGE CONFIGURATION"); $conf = PhabricatorEnv::newObjectFromConfig('mysql.configuration-provider'); $conn_user = $conf->getUser(); $conn_pass = $conf->getPassword(); $conn_host = $conf->getHost(); $timeout = ini_get('mysql.connect_timeout'); if ($timeout > 5) { self::writeNote( "Your MySQL connect timeout is very high ({$timeout} seconds). ". "Consider reducing it to 5 or below by setting ". "'mysql.connect_timeout' in your php.ini."); } self::write(" okay Trying to connect to MySQL database ". "{$conn_user}@{$conn_host}...\n"); ini_set('mysql.connect_timeout', 2); $conn_raw = PhabricatorEnv::newObjectFromConfig( 'mysql.implementation', array( array( 'user' => $conn_user, 'pass' => $conn_pass, 'host' => $conn_host, 'database' => null, ), )); try { queryfx($conn_raw, 'SELECT 1'); self::write(" okay Connection successful!\n"); } catch (AphrontQueryConnectionException $ex) { $message = $ex->getMessage(); self::writeFailure(); self::write( "Setup failure! MySQL exception: {$message} \n". "Edit Phabricator configuration keys 'mysql.user', ". "'mysql.host' and 'mysql.pass' to enable Phabricator ". "to connect."); return; } $engines = queryfx_all($conn_raw, 'SHOW ENGINES'); $engines = ipull($engines, 'Support', 'Engine'); $innodb = idx($engines, 'InnoDB'); if ($innodb != 'YES' && $innodb != 'DEFAULT') { self::writeFailure(); self::write( "Setup failure! The 'InnoDB' engine is not available. Enable ". "InnoDB in your MySQL configuration. If you already created tables, ". "MySQL incorrectly used some other engine. You need to convert ". "them or drop and reinitialize them."); return; } else { self::write(" okay InnoDB is available.\n"); } $databases = queryfx_all($conn_raw, 'SHOW DATABASES'); $databases = ipull($databases, 'Database', 'Database'); if (empty($databases['phabricator_meta_data'])) { self::writeFailure(); self::write( "Setup failure! You haven't run 'bin/storage upgrade'. See this ". "article for instructions:\n"); self::writeDoc('article/Configuration_Guide.html'); return; } else { self::write(" okay Databases have been initialized.\n"); } $index_min_length = queryfx_one( $conn_raw, 'SHOW VARIABLES LIKE %s', 'ft_min_word_len'); $index_min_length = idx($index_min_length, 'Value', 4); if ($index_min_length >= 4) { self::writeNote( "MySQL is configured with a 'ft_min_word_len' of 4 or greater, which ". "means you will not be able to search for 3-letter terms. Consider ". "setting this in your configuration:\n". "\n". " [mysqld]\n". " ft_min_word_len=3\n". "\n". "Then optionally run:\n". "\n". " REPAIR TABLE phabricator_search.search_documentfield QUICK;\n". "\n". "...to reindex existing documents."); } $max_allowed_packet = queryfx_one( $conn_raw, 'SHOW VARIABLES LIKE %s', 'max_allowed_packet'); $max_allowed_packet = idx($max_allowed_packet, 'Value', PHP_INT_MAX); $recommended_minimum = 1024 * 1024; if ($max_allowed_packet < $recommended_minimum) { self::writeNote( "MySQL is configured with a small 'max_allowed_packet' ". "('{$max_allowed_packet}'), which may cause some large writes to ". "fail. Consider raising this to at least {$recommended_minimum}."); } else { self::write(" okay max_allowed_packet = {$max_allowed_packet}.\n"); } $mysql_key = 'storage.mysql-engine.max-size'; $mysql_limit = PhabricatorEnv::getEnvConfig($mysql_key); if ($mysql_limit && ($mysql_limit + 8192) > $max_allowed_packet) { self::writeFailure(); self::write( "Setup failure! Your Phabricator 'storage.mysql-engine.max-size' ". "configuration ('{$mysql_limit}') must be at least 8KB smaller ". "than your MySQL 'max_allowed_packet' configuration ". "('{$max_allowed_packet}'). Raise the 'max_allowed_packet' in your ". "MySQL configuration, or reduce the maximum file size allowed by ". "the Phabricator configuration.\n"); return; } else if (!$mysql_limit) { self::write(" skip MySQL file storage engine not configured.\n"); } else { self::write(" okay MySQL file storage engine configuration okay.\n"); } $local_key = 'storage.local-disk.path'; $local_path = PhabricatorEnv::getEnvConfig($local_key); if ($local_path) { if (!Filesystem::pathExists($local_path) || !is_readable($local_path) || !is_writable($local_path)) { self::writeFailure(); self::write( "Setup failure! You have configured local disk storage but the ". "path you specified ('{$local_path}') does not exist or is not ". "readable or writable.\n"); if ($open_basedir) { self::write( "You have an 'open_basedir' setting -- make sure Phabricator is ". "allowed to open files in the local storage directory.\n"); } return; } else { self::write(" okay Local disk storage exists and is writable.\n"); } } else { self::write(" skip Not configured for local disk storage.\n"); } $selector = PhabricatorEnv::getEnvConfig('storage.engine-selector'); try { $storage_selector_exists = class_exists($selector); } catch (Exception $ex) { $storage_selector_exists = false; } if ($storage_selector_exists) { self::write(" okay Using '{$selector}' as a storage engine selector.\n"); } else { self::writeFailure(); self::write( "Setup failure! You have configured '{$selector}' as a storage engine ". "selector but it does not exist or could not be loaded.\n"); return; } self::write("[OKAY] Database and storage configuration OKAY\n"); self::writeHeader("OUTBOUND EMAIL CONFIGURATION"); $have_adapter = false; $is_ses = false; $adapter = PhabricatorEnv::getEnvConfig('metamta.mail-adapter'); switch ($adapter) { case 'PhabricatorMailImplementationPHPMailerLiteAdapter': $have_adapter = true; if (!Filesystem::pathExists('/usr/bin/sendmail') && !Filesystem::pathExists('/usr/sbin/sendmail')) { self::writeFailure(); self::write( "Setup failure! You don't have a 'sendmail' binary on this system ". "but outbound email is configured to use sendmail. Install an MTA ". "(like sendmail, qmail or postfix) or use a different outbound ". "mail configuration. See this guide for configuring outbound ". "email:\n"); self::writeDoc('article/Configuring_Outbound_Email.html'); return; } else { self::write(" okay Sendmail is configured.\n"); } break; case 'PhabricatorMailImplementationAmazonSESAdapter': $is_ses = true; $have_adapter = true; if (PhabricatorEnv::getEnvConfig('metamta.can-send-as-user')) { self::writeFailure(); self::write( "Setup failure! 'metamta.can-send-as-user' must be false when ". "configured with Amazon SES."); return; } else { self::write(" okay Sender config looks okay.\n"); } if (!PhabricatorEnv::getEnvConfig('amazon-ses.access-key')) { self::writeFailure(); self::write( "Setup failure! 'amazon-ses.access-key' is not set, but ". "outbound mail is configured to deliver via Amazon SES."); return; } else { self::write(" okay Amazon SES access key is set.\n"); } if (!PhabricatorEnv::getEnvConfig('amazon-ses.secret-key')) { self::writeFailure(); self::write( "Setup failure! 'amazon-ses.secret-key' is not set, but ". "outbound mail is configured to deliver via Amazon SES."); return; } else { self::write(" okay Amazon SES secret key is set.\n"); } if (PhabricatorEnv::getEnvConfig('metamta.send-immediately')) { self::writeNote( "Your configuration uses Amazon SES to deliver email but tries ". "to send it immediately. This will work, but it's slow. ". "Consider configuring the MetaMTA daemon."); } break; case 'PhabricatorMailImplementationTestAdapter': self::write(" skip You have disabled outbound email.\n"); break; default: self::write(" skip Configured with a custom adapter.\n"); break; } if ($have_adapter) { $default = PhabricatorEnv::getEnvConfig('metamta.default-address'); if (!$default || $default == 'noreply@example.com') { self::writeFailure(); self::write( "Setup failure! You have not set 'metamta.default-address'."); return; } else { self::write(" okay metamta.default-address is set.\n"); } if ($is_ses) { self::writeNote( "Make sure you've verified your 'from' address ('{$default}') with ". "Amazon SES. Until you verify it, you will be unable to send mail ". "using Amazon SES."); } $domain = PhabricatorEnv::getEnvConfig('metamta.domain'); if (!$domain || $domain == 'example.com') { self::writeFailure(); self::write( "Setup failure! You have not set 'metamta.domain'."); return; } else { self::write(" okay metamta.domain is set.\n"); } self::write("[OKAY] Mail configuration OKAY\n"); } self::writeHeader('CONFIG CLASSES'); foreach (PhabricatorEnv::getRequiredClasses() as $key => $instanceof) { $config = PhabricatorEnv::getEnvConfig($key); if (!$config) { self::writeNote("'$key' is not set."); } else { try { $r = new ReflectionClass($config); if (!$r->isSubclassOf($instanceof)) { throw new Exception( "Config setting '$key' must be an instance of '$instanceof'."); } else if (!$r->isInstantiable()) { throw new Exception("Config setting '$key' must be instantiable."); } } catch (Exception $ex) { self::writeFailure(); self::write("Setup failure! ".$ex->getMessage()); return; } } } self::write("[OKAY] Config classes OKAY\n"); self::writeHeader('SUCCESS!'); self::write( "Congratulations! Your setup seems mostly correct, or at least fairly ". "reasonable.\n\n". "*** NEXT STEP ***\n". "Edit your configuration file (conf/{$env}.conf.php) and remove the ". "'phabricator.setup' line to finish installation."); } public static function requireExtension($extension) { if (extension_loaded($extension)) { self::write(" okay Extension '{$extension}' installed.\n"); return true; } else { self::write("[FAIL] Extension '{$extension}' is NOT INSTALLED!\n"); return false; } } private static function writeFailure() { self::write("\n\n<<< *** FAILURE! *** >>>\n"); } private static function write($str) { echo $str; ob_flush(); flush(); // This, uh, makes it look cool. -_- usleep(20000); } private static function writeNote($note) { $note = "*** NOTE: ".wordwrap($note, 75, "\n", true); $note = "\n".str_replace("\n", "\n ", $note)."\n\n"; self::write($note); } public static function writeHeader($header) { $template = '>>>'.str_repeat('-', 77); $template = substr_replace( $template, ' '.$header.' ', 3, strlen($header) + 4); self::write("\n\n{$template}\n\n"); } public static function writeDoc($doc) { self::write( "\n". ' http://www.phabricator.com/docs/phabricator/'.$doc. "\n\n"); } } diff --git a/webroot/index.php b/webroot/index.php index d1604a15f2..a2e817c74e 100644 --- a/webroot/index.php +++ b/webroot/index.php @@ -1,378 +1,372 @@ ', where '' ". "is one of 'development', 'production', or a custom environment."); } if (!isset($_REQUEST['__path__'])) { phabricator_fatal_config_error( "__path__ is not set. Your rewrite rules are not configured correctly."); } if (get_magic_quotes_gpc()) { phabricator_fatal_config_error( "Your server is configured with PHP 'magic_quotes_gpc' enabled. This ". "feature is 'highly discouraged' by PHP's developers and you must ". "disable it to run Phabricator. Consult the PHP manual for instructions."); } register_shutdown_function('phabricator_shutdown'); require_once dirname(dirname(__FILE__)).'/conf/__init_conf__.php'; try { setup_aphront_basics(); $conf = phabricator_read_config_file($env); $conf['phabricator.env'] = $env; - phutil_require_module('phabricator', 'infrastructure/env'); PhabricatorEnv::setEnvConfig($conf); // This is the earliest we can get away with this, we need env config first. PhabricatorAccessLog::init(); $access_log = PhabricatorAccessLog::getLog(); if ($access_log) { $access_log->setData( array( 'R' => idx($_SERVER, 'HTTP_REFERER', '-'), 'r' => idx($_SERVER, 'REMOTE_ADDR', '-'), 'M' => idx($_SERVER, 'REQUEST_METHOD', '-'), )); } - phutil_require_module('phabricator', 'aphront/console/plugin/xhprof/api'); DarkConsoleXHProfPluginAPI::hookProfiler(); - phutil_require_module('phabricator', 'aphront/console/plugin/errorlog/api'); - PhutilErrorHandler::initialize(); } catch (Exception $ex) { phabricator_fatal("[Initialization Exception] ".$ex->getMessage()); } $tz = PhabricatorEnv::getEnvConfig('phabricator.timezone'); if ($tz) { date_default_timezone_set($tz); } -phutil_require_module('phabricator', 'aphront/console/plugin/errorlog/api'); -phutil_require_module('phutil', 'error'); PhutilErrorHandler::setErrorListener( array('DarkConsoleErrorLogPluginAPI', 'handleErrors')); foreach (PhabricatorEnv::getEnvConfig('load-libraries') as $library) { phutil_load_library($library); } if (PhabricatorEnv::getEnvConfig('phabricator.setup')) { try { PhabricatorSetup::runSetup(); } catch (Exception $ex) { echo "EXCEPTION!\n"; echo $ex; } return; } phabricator_detect_bad_base_uri(); $host = $_SERVER['HTTP_HOST']; $path = $_REQUEST['__path__']; switch ($host) { default: $config_key = 'aphront.default-application-configuration-class'; $application = PhabricatorEnv::newObjectFromConfig($config_key); break; } $application->setHost($host); $application->setPath($path); $application->willBuildRequest(); $request = $application->buildRequest(); $write_guard = new AphrontWriteGuard($request); PhabricatorEventEngine::initialize(); $application->setRequest($request); list($controller, $uri_data) = $application->buildController(); if ($access_log) { $access_log->setData( array( 'U' => (string)$request->getRequestURI()->getPath(), 'C' => get_class($controller), )); } try { $response = $controller->willBeginExecution(); if ($access_log) { if ($request->getUser() && $request->getUser()->getPHID()) { $access_log->setData( array( 'u' => $request->getUser()->getUserName(), )); } } if (!$response) { $controller->willProcessRequest($uri_data); $response = $controller->processRequest(); } } catch (AphrontRedirectException $ex) { $response = id(new AphrontRedirectResponse()) ->setURI($ex->getURI()); } catch (Exception $ex) { $response = $application->handleException($ex); } try { $response = $application->willSendResponse($response); $response->setRequest($request); $response_string = $response->buildResponseString(); } catch (Exception $ex) { $write_guard->dispose(); if ($access_log) { $access_log->write(); } phabricator_fatal('[Rendering Exception] '.$ex->getMessage()); } $write_guard->dispose(); // TODO: Share the $sink->writeResponse() pathway here? $sink = new AphrontPHPHTTPSink(); $sink->writeHTTPStatus($response->getHTTPResponseCode()); $headers = $response->getCacheHeaders(); $headers = array_merge($headers, $response->getHeaders()); $sink->writeHeaders($headers); // TODO: This shouldn't be possible in a production-configured environment. if (isset($_REQUEST['__profile__']) && ($_REQUEST['__profile__'] == 'all')) { $profile = DarkConsoleXHProfPluginAPI::stopProfiler(); $profile = ''; if (strpos($response_string, '') !== false) { $response_string = str_replace( '', ''.$profile, $response_string); } else { $sink->writeData($profile); } } $sink->writeData($response_string); if ($access_log) { $access_log->setData( array( 'c' => $response->getHTTPResponseCode(), 'T' => (int)(1000000 * (microtime(true) - $__start__)), )); $access_log->write(); } /** * @group aphront */ function setup_aphront_basics() { $aphront_root = dirname(dirname(__FILE__)); $libraries_root = dirname($aphront_root); $root = null; if (!empty($_SERVER['PHUTIL_LIBRARY_ROOT'])) { $root = $_SERVER['PHUTIL_LIBRARY_ROOT']; } ini_set( 'include_path', $libraries_root.PATH_SEPARATOR.ini_get('include_path')); @include_once $root.'libphutil/src/__phutil_library_init__.php'; if (!@constant('__LIBPHUTIL__')) { echo "ERROR: Unable to load libphutil. Put libphutil/ next to ". "phabricator/, or update your PHP 'include_path' to include ". "the parent directory of libphutil/.\n"; exit(1); } // Load Phabricator itself using the absolute path, so we never end up doing // anything surprising (loading index.php and libraries from different // directories). phutil_load_library($aphront_root.'/src'); phutil_load_library('arcanist/src'); } function phabricator_fatal_config_error($msg) { phabricator_fatal("CONFIG ERROR: ".$msg."\n"); } function phabricator_detect_bad_base_uri() { $conf = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); $uri = new PhutilURI($conf); switch ($uri->getProtocol()) { case 'http': case 'https': break; default: return phabricator_fatal_config_error( "'phabricator.base-uri' is set to '{$conf}', which is invalid. ". "The URI must start with 'http://' or 'https://'."); } if (strpos($uri->getDomain(), '.') === false) { phabricator_fatal_config_error( "'phabricator.base-uri' is set to '{$conf}', which is invalid. The URI ". "must contain a dot ('.'), like 'http://example.com/', not just ". "'http://example/'. Some web browsers will not set cookies on domains ". "with no TLD, and Phabricator requires cookies for login. ". "If you are using localhost, create an entry in the hosts file like ". "'127.0.0.1 example.com', and access the localhost with ". "'http://example.com/'."); } } function phabricator_detect_insane_memory_limit() { $memory_limit = ini_get('memory_limit'); $char_limit = 12; if (strlen($memory_limit) <= $char_limit) { return; } // colmdoyle ran into an issue on an Ubuntu box with Suhosin where his // 'memory_limit' was set to: // // 3232323232323232323232323232323232323232323232323232323232323232M // // Not a typo. A wizard did it. // // Anyway, with this 'memory_limit', the machine would immediately fatal // when executing the ini_set() later. I wasn't able to reproduce this on my // EC2 Ubuntu + Suhosin box, but verified that it caused the problem on his // machine and that setting it to a more sensible value fixed it. Since I // have no idea how to actually trigger the issue, we look for a coarse // approximation of it (a memory_limit setting more than 12 characters in // length). phabricator_fatal_config_error( "Your PHP 'memory_limit' is set to something ridiculous ". "(\"{$memory_limit}\"). Set it to a more reasonable value (it must be no ". "more than {$char_limit} characters long)."); } function phabricator_shutdown() { $event = error_get_last(); if (!$event) { return; } if ($event['type'] != E_ERROR && $event['type'] != E_PARSE) { return; } $msg = ">>> UNRECOVERABLE FATAL ERROR <<<\n\n"; if ($event) { // Even though we should be emitting this as text-plain, escape things just // to be sure since we can't really be sure what the program state is when // we get here. $msg .= phutil_escape_html($event['message'])."\n\n"; $msg .= phutil_escape_html($event['file'].':'.$event['line']); } // flip dem tables $msg .= "\n\n\n"; $msg .= "\xe2\x94\xbb\xe2\x94\x81\xe2\x94\xbb\x20\xef\xb8\xb5\x20\xc2\xaf". "\x5c\x5f\x28\xe3\x83\x84\x29\x5f\x2f\xc2\xaf\x20\xef\xb8\xb5\x20". "\xe2\x94\xbb\xe2\x94\x81\xe2\x94\xbb"; phabricator_fatal($msg); } function phabricator_fatal($msg) { global $access_log; if ($access_log) { $access_log->setData( array( 'c' => 500, )); $access_log->write(); } header( 'Content-Type: text/plain; charset=utf-8', $replace = true, $http_error = 500); error_log($msg); echo $msg; exit(1); }