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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ error_reporting(E_ALL | E_STRICT); ini_set('display_errors', 1); $include_path = ini_get('include_path'); ini_set( 'include_path', $include_path.PATH_SEPARATOR.dirname(__FILE__).'/../../'); @include_once 'libphutil/scripts/__init_script__.php'; if (!@constant('__LIBPHUTIL__')) { echo "ERROR: Unable to load libphutil. Update your PHP 'include_path' to ". "include the parent directory of libphutil/.\n"; exit(1); } phutil_load_library(dirname(__FILE__).'/../src/'); // NOTE: This is dangerous in general, but we know we're in a script context and // are not vulnerable to CSRF. AphrontWriteGuard::allowDangerousUnguardedWrites(true); require_once dirname(dirname(__FILE__)).'/conf/__init_conf__.php'; $env = isset($_SERVER['PHABRICATOR_ENV']) ? $_SERVER['PHABRICATOR_ENV'] : getenv('PHABRICATOR_ENV'); if (!$env) { - phutil_require_module('phutil', 'console'); echo phutil_console_wrap( phutil_console_format( "**ERROR**: PHABRICATOR_ENV Not Set\n\n". "Define the __PHABRICATOR_ENV__ environment variable before running ". "this script. You can do it on the command line like this:\n\n". " $ PHABRICATOR_ENV=__custom/myconfig__ %s ...\n\n". "Replace __custom/myconfig__ with the path to your configuration file. ". "For more information, see the 'Configuration Guide' in the ". "Phabricator documentation.\n\n", $argv[0])); exit(1); } $conf = phabricator_read_config_file($env); $conf['phabricator.env'] = $env; -phutil_require_module('phabricator', 'infrastructure/env'); PhabricatorEnv::setEnvConfig($conf); phutil_load_library('arcanist/src'); foreach (PhabricatorEnv::getEnvConfig('load-libraries') as $library) { phutil_load_library($library); } PhutilErrorHandler::initialize(); PhabricatorEventEngine::initialize(); $tz = PhabricatorEnv::getEnvConfig('phabricator.timezone'); if ($tz) { date_default_timezone_set($tz); } diff --git a/scripts/celerity_mapper.php b/scripts/celerity_mapper.php index e21f89d0d5..d563fb8cf5 100755 --- a/scripts/celerity_mapper.php +++ b/scripts/celerity_mapper.php @@ -1,356 +1,351 @@ #!/usr/bin/env php <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $package_spec = array( 'javelin.pkg.js' => 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} <webroot>\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 = <<<EOFILE <?php /** * This file is automatically generated. Use 'celerity_mapper.php' to rebuild * it. * {$generated} */ celerity_register_resource_map({$runtime_map}, {$package_map}); EOFILE; echo "Writing map...\n"; Filesystem::writeFile( $root.'/../src/__celerity_resource_map__.php', $resource_map); echo "Done.\n"; diff --git a/scripts/conduit/api.php b/scripts/conduit/api.php index e2da82ce6e..e18cc880e4 100644 --- a/scripts/conduit/api.php +++ b/scripts/conduit/api.php @@ -1,97 +1,95 @@ #!/usr/bin/env php <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -phutil_require_module('phutil', 'console'); - $time_start = microtime(true); if ($argc !== 3) { echo "usage: api.php <user_phid> <method>\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 <user_phid> <method>\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 <user_phid> <method>\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 <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -phutil_require_module('phabricator', 'infrastructure/daemon/control'); $control = new PhabricatorDaemonControl(); must_have_extension('pcntl'); must_have_extension('posix'); function must_have_extension($ext) { if (!extension_loaded($ext)) { echo "ERROR: The PHP extension '{$ext}' is not installed. You must ". "install it to run daemons on this machine.\n"; exit(1); } } $command = isset($argv[1]) ? $argv[1] : 'help'; switch ($command) { case 'list': $err = $control->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 <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -phutil_require_module('phutil', 'console'); - $revision = new DifferentialRevision(); $empty_revisions = queryfx_all( $revision->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 <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -phutil_require_module('phutil', 'console'); -phutil_require_module('phutil', 'future/exec'); - PhutilServiceProfiler::installEchoListener(); $allocator = new DrydockAllocator(); $allocator->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 <?php /* - * Copyright 2011 Facebook, Inc. + * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ if ($argc > 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 <?php /* - * Copyright 2011 Facebook, Inc. + * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -phutil_require_module('phutil', 'symbols'); -PhutilSymbolLoader::loadClass('PhabricatorRepository'); -PhutilSymbolLoader::loadClass('PhabricatorRepositoryCommit'); - $commit = new PhabricatorRepositoryCommit(); $conn_w = id(new PhabricatorRepository())->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 <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -phutil_require_module('phutil', 'console'); - $is_all = false; $reparse_message = false; $reparse_change = false; $reparse_herald = false; $reparse_owners = false; $reparse_what = false; $force = false; $args = array_slice($argv, 1); foreach ($args as $arg) { if (!strncmp($arg, '--', 2)) { $flag = substr($arg, 2); switch ($flag) { case 'all': $is_all = true; break; case 'message': case 'messages': $reparse_message = true; break; case 'change': case 'changes': $reparse_change = true; break; case 'herald': $reparse_herald = true; break; case 'owners': $reparse_owners = true; break; case 'force': $force = true; break; case 'trace': PhutilServiceProfiler::installEchoListener(); break; case 'help': help(); break; default: usage("Unknown flag '{$arg}'."); break; } } else { if ($reparse_what) { usage("Specify exactly one thing to reparse."); } $reparse_what = $arg; } } if (!$reparse_what) { usage("Specify a commit or repository to reparse."); } if (!$reparse_message && !$reparse_change && !$reparse_herald && !$reparse_owners) { usage("Specify what information to reparse with --message, --change, ". "--herald, and/or --owners"); } if ($reparse_owners && !$force) { echo phutil_console_wrap( "You are about to recreate the relationship entries between the commits ". "and the packages they touch. This might delete some existing ". "relationship entries for some old commits."); if (!phutil_console_confirm('Are you ready to continue?')) { echo "Cancelled.\n"; exit(1); } } $commits = array(); if ($is_all) { $repository = id(new PhabricatorRepository())->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 = <<<EOHELP **SUMMARY** **reparse.php** __what__ __which_parts__ [--trace] [--force] Rerun the Diffusion parser on specific commits and repositories. Mostly useful for debugging changes to Diffusion. __what__: what to reparse __commit__ Reparse one commit. This mode will reparse the commit in-process. --all __repository_callsign__ --all __repository_phid__ Reparse all commits in the specified repository. These modes queue parsers into the task queue, you must run taskmasters to actually do the parses. __which_parts__: which parts of the thing to reparse __--message__ Reparse commit messages. __--change__ Reparse changes. __--herald__ Reevaluate Herald rules (may send huge amounts of email!) __--owners__ Reevaluate related commits for owners packages (may delete existing relationship entries between your package and some old commits!) __--force__: act noninteractively, without prompting __--trace__: run with debug tracing __--help__: show this help **EXAMPLES** reparse.php rX123 --change # Reparse change for "rX123". reparse.php --all E --message # Reparse all messages in "E" repository. EOHELP; echo phutil_console_format($help); exit(1); } diff --git a/scripts/repository/test_connection.php b/scripts/repository/test_connection.php index 4849ea9ac5..4ccabbaed8 100755 --- a/scripts/repository/test_connection.php +++ b/scripts/repository/test_connection.php @@ -1,114 +1,111 @@ #!/usr/bin/env php <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -phutil_require_module('phutil', 'console'); -phutil_require_module('phutil', 'future/exec'); - if (empty($argv[1])) { echo "usage: test_connection.php <repository_callsign>\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( "<bg:red>** FAIL **</bg> Connection failed. The credentials for this ". "repository appear to be incorrectly configured.\n"); exit(1); } else { echo phutil_console_format( "<bg:green>** OKAY **</bg> 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 <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -phutil_require_module('phutil', 'console'); - if (ctags_check_executable() == false) { echo phutil_console_format( "Could not find Exuberant ctags. Make sure it is installed and\n". "available in executable path.\n\n". "Exuberant ctags project page: http://ctags.sourceforge.net/\n"); exit(1); } if ($argc !== 1 || posix_isatty(STDIN)) { echo phutil_console_format( "usage: find . -type f -name '*.py' | ./generate_ctags_symbols.php\n"); exit(1); } $input = file_get_contents('php://stdin'); $input = trim($input); $input = explode("\n", $input); $data = array(); $futures = array(); foreach ($input as $file) { $file = Filesystem::readablePath($file); $futures[$file] = ctags_get_parser_future($file); } foreach (Futures($futures)->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 <?php /* - * Copyright 2011 Facebook, Inc. + * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -phutil_require_module('phutil', 'console'); -phutil_require_module('phutil', 'parser/xhpast/bin'); - if ($argc !== 1 || posix_isatty(STDIN)) { echo phutil_console_format( "usage: find . -type f -name '*.php' | ./generate_php_symbols.php\n"); exit(1); } $input = file_get_contents('php://stdin'); $input = trim($input); $input = explode("\n", $input); $data = array(); $futures = array(); foreach ($input as $file) { $file = Filesystem::readablePath($file); $data[$file] = Filesystem::readFile($file); $futures[$file] = xhpast_get_parser_future($data[$file]); } foreach (Futures($futures)->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 <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -phutil_require_module('phutil', 'console'); -phutil_require_module('phutil', 'future/exec'); - echo "Enter a username to create a new account or edit an existing account."; $username = phutil_console_prompt("Enter a username:"); if (!strlen($username)) { echo "Cancelled.\n"; exit(1); } if (!PhabricatorUser::validateUsername($username)) { echo "The username '{$username}' is invalid. Usernames must consist of only ". "numbers and letters.\n"; exit(1); } $user = id(new PhabricatorUser())->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 <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -phutil_require_module('phutil', 'console'); -phutil_require_module('phutil', 'future/exec'); - if ($argc !== 5) { echo "usage: add_user.php <username> <email> <realname> <admin_user>\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 <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; -phutil_require_module('phutil', 'console'); -phutil_require_module('phabricator', 'storage/queryfx'); - $purge_changesets = false; $purge_differential = false; $purge_maniphest = false; $args = array_slice($argv, 1); if (!$args) { usage("Specify which caches you want to purge."); } $changesets = array(); $len = count($args); for ($ii = 0; $ii < $len; $ii++) { switch ($args[$ii]) { case '--all': $purge_changesets = true; $purge_differential = true; $purge_maniphest = true; break; case '--changesets': $purge_changesets = true; while (isset($args[$ii + 1]) && (substr($args[$ii + 1], 0, 2) !== '--')) { $changeset = $args[++$ii]; if (!is_numeric($changeset)) { return usage("Changeset argument '{$changeset}' ". "is not a positive integer."); } $changesets[] = intval($changeset); } break; case '--differential': $purge_differential = true; break; case '--maniphest': $purge_maniphest = true; break; case '--help': return help(); default: return usage("Unrecognized argument '{$args[$ii]}'."); } } if ($purge_changesets) { $table = new DifferentialChangeset(); if ($changesets) { echo "Purging changeset cache for changesets ". implode($changesets, ",")."\n"; queryfx( $table->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 = <<<EOHELP **SUMMARY** **purge_cache.php** [--maniphest] [--differential] [--changesets [changeset_id ...]] **purge_cache.php** --all **purge_cache.php** --help Purge various long-lived caches. Normally, Phabricator caches some data for a long time or indefinitely, but certain configuration changes might invalidate these caches. You can use this script to manually purge them. For instance, if you change display widths in Differential or configure syntax highlighting, you may want to purge the changeset cache (with "--changesets") so your changes are reflected in older diffs. If you change Remarkup rules, you may want to purge the Maniphest or Differential comment caches ("--maniphest", "--differential") so older comments pick up the new rules. __--all__ Purge all long-lived caches. __--changesets [changeset_id ...]__ Purge Differential changeset render cache. If changeset_ids are present, the script will delete the cache for those changesets; otherwise it will delete the cache for all the changesets. __--differential__ Purge Differential comment formatting cache. __--maniphest__: show this help Purge Maniphest comment formatting cache. __--help__: show this help EOHELP; echo phutil_console_format($help); exit(1); } diff --git a/src/aphront/applicationconfiguration/AphrontApplicationConfiguration.php b/src/aphront/applicationconfiguration/AphrontApplicationConfiguration.php index 4da7ea92f1..aa78186021 100644 --- a/src/aphront/applicationconfiguration/AphrontApplicationConfiguration.php +++ b/src/aphront/applicationconfiguration/AphrontApplicationConfiguration.php @@ -1,116 +1,115 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @group aphront */ abstract class AphrontApplicationConfiguration { private $request; private $host; private $path; private $console; abstract public function getApplicationName(); abstract public function getURIMap(); abstract public function buildRequest(); abstract public function build404Controller(); abstract public function buildRedirectController($uri); final public function setRequest(AphrontRequest $request) { $this->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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @group console * @phutil-external-symbol function xhprof_enable * @phutil-external-symbol function xhprof_disable */ final class DarkConsoleXHProfPluginAPI { private static $profilerStarted; public static function isProfilerAvailable() { return extension_loaded('xhprof'); } public static function includeXHProfLib() { // TODO: this is incredibly stupid, but we may not have Phutil metamodule // stuff loaded yet so we can't just phutil_get_library_root() our way // to victory. $root = __FILE__; for ($ii = 0; $ii < 7; $ii++) { $root = dirname($root); } require_once $root.'/externals/xhprof/xhprof_lib.php'; } public static function hookProfiler() { if (empty($_REQUEST['__profile__'])) { return; } if (!self::isProfilerAvailable()) { return; } if (self::$profilerStarted) { return; } self::startProfiler(); self::$profilerStarted = true; } public static function startProfiler() { self::includeXHProfLib(); // Note: HPHP's implementation of XHProf currently requires an argument // to xhprof_enable() -- see Facebook Task #531011. xhprof_enable(0); } public static function stopProfiler() { if (self::$profilerStarted) { $data = xhprof_disable(); $data = serialize($data); $file_class = 'PhabricatorFile'; - PhutilSymbolLoader::loadClass($file_class); // Since these happen on GET we can't do guarded writes. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $file = call_user_func( array($file_class, 'newFromFileData'), $data, array( 'mime-type' => '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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ abstract class PhabricatorOAuthProvider { const PROVIDER_FACEBOOK = 'facebook'; const PROVIDER_GITHUB = 'github'; const PROVIDER_GOOGLE = 'google'; const PROVIDER_PHABRICATOR = 'phabricator'; const PROVIDER_DISQUS = 'disqus'; private $accessToken; abstract public function getProviderKey(); abstract public function getProviderName(); abstract public function isProviderEnabled(); abstract public function isProviderLinkPermanent(); abstract public function isProviderRegistrationEnabled(); abstract public function getClientID(); abstract public function renderGetClientIDHelp(); abstract public function getClientSecret(); abstract public function renderGetClientSecretHelp(); abstract public function getAuthURI(); abstract public function getTestURIs(); /** * If the provider needs extra stuff in the auth request, return it here. * For example, Google needs a response_type parameter. */ public function getExtraAuthParameters() { return array(); } /** * If the provider supports application login, the diagnostics page can try * to test it. Most providers do not support this (Facebook does). */ public function shouldDiagnoseAppLogin() { return false; } abstract public function getTokenURI(); /** * Access tokens expire based on an implementation-specific key. */ abstract protected function getTokenExpiryKey(); public function getTokenExpiryFromArray(array $data) { $key = $this->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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ abstract class PhabricatorController extends AphrontController { public function shouldRequireLogin() { return true; } public function shouldRequireAdmin() { return false; } public function shouldRequireEnabledUser() { return true; } public function shouldRequireEmailVerification() { $need_verify = PhabricatorUserEmail::isEmailVerificationRequired(); $need_login = $this->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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ final class DifferentialRevisionViewController extends DifferentialController { private $revisionID; public function shouldRequireLogin() { return !$this->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( '<p>All specified reviewers are disabled. You may want to add '. 'some new reviewers.</p>'); } else { $reviewer_warning->appendChild( '<p>This revision has no specified reviewers. You may want to '. 'add some.</p>'); } } $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( "<p>This diff is very large and affects {$count} files. Load ". "each file individually. ". "<strong>". phutil_render_tag( 'a', array( 'href' => $request_uri ->alter('large', 'true') ->setFragment('differential-review-toc'), ), 'Show All Files Inline'). "</strong>"); $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 '<div class="differential-panel">'. '<h1>Open Revisions Affecting These Files</h1>'. $view->render(). '</div>'; } } 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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ abstract class DiffusionBranchQuery { private $request; private $limit; private $offset; public function setOffset($offset) { $this->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() { // <private> } 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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ abstract class DiffusionBrowseQuery { private $request; protected $reason; protected $existedAtCommit; protected $deletedAtCommit; protected $validityOnly; const REASON_IS_FILE = 'is-file'; const REASON_IS_DELETED = 'is-deleted'; const REASON_IS_NONEXISTENT = 'nonexistent'; const REASON_BAD_COMMIT = 'bad-commit'; const REASON_IS_EMPTY = 'empty'; const REASON_IS_UNTRACKED_PARENT = 'untracked-parent'; final private function __construct() { // <private> } 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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Amazon S3 file storage engine. This engine scales well but is relatively * high-latency since data has to be pulled off S3. * * @task impl Implementation * @task internal Internals * @group filestorage */ final class PhabricatorS3FileStorageEngine extends PhabricatorFileStorageEngine { /* -( Implementation )----------------------------------------------------- */ /** * This engine identifies as "amazon-s3". * * @task impl */ public function getEngineIdentifier() { return 'amazon-s3'; } /** * Write file data into S3. * @task impl */ public function writeFile($data, array $params) { $s3 = $this->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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class PhabricatorMarkupEngine { public static function extractPHIDsFromMentions(array $content_blocks) { $mentions = array(); $engine = self::newDifferentialMarkupEngine(); foreach ($content_blocks as $content_block) { $engine->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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ final class PhabricatorMailImplementationAmazonSESAdapter extends PhabricatorMailImplementationPHPMailerLiteAdapter { private $message; private $isHTML; public function __construct() { parent::__construct(); $this->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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * TODO: Should be final, but inherited by SES. */ class PhabricatorMailImplementationPHPMailerLiteAdapter extends PhabricatorMailImplementationAdapter { + /** + * @phutil-external-symbol class PHPMailerLite + */ public function __construct() { $root = phutil_get_library_root('phabricator'); $root = dirname($root); require_once $root.'/externals/phpmailer/class.phpmailer-lite.php'; - $this->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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO { protected $headers = array(); protected $bodies = array(); protected $attachments = array(); protected $relatedPHID; protected $authorPHID; protected $message; public function getConfiguration() { return array( self::CONFIG_SERIALIZATION => 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" <alincoln@logcab.in>'. */ 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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ final class PhabricatorObjectHandleData { private $phids; public function __construct(array $phids) { $this->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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ abstract class PhabricatorRepositoryCommitMessageParserWorker extends PhabricatorRepositoryCommitParserWorker { abstract protected function getCommitHashes( PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit); final protected function updateCommitData($author, $message, $committer = null) { $commit = $this->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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ final class PhabricatorTaskmasterDaemon extends PhabricatorDaemon { public function run() { $lease_ownership_name = $this->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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ final class PhabricatorSetup { public static function runSetup() { header("Content-Type: text/plain"); self::write("PHABRICATOR SETUP\n\n"); // Force browser to stop buffering. self::write(str_repeat(' ', 2048)); usleep(250000); self::write("This setup mode will guide you through setting up your ". "Phabricator configuration.\n"); self::writeHeader("CORE CONFIGURATION"); // NOTE: Test this first since other tests depend on the ability to // execute system commands and will fail if safe_mode is enabled. $safe_mode = ini_get('safe_mode'); if ($safe_mode) { self::writeFailure(); self::write( "Setup failure! You have 'safe_mode' enabled. Phabricator will not ". "run in safe mode, and it has been deprecated in PHP 5.3 and removed ". "in PHP 5.4.\n"); return; } else { self::write(" okay PHP's deprecated 'safe_mode' is disabled.\n"); } // NOTE: Also test this early since we can't include files from other // libraries if this is set strictly. $open_basedir = ini_get('open_basedir'); if ($open_basedir) { // 'open_basedir' restricts which files we're allowed to access with // file operations. This might be okay -- we don't need to write to // arbitrary places in the filesystem -- but we need to access certain // resources. This setting is unlikely to be providing any real measure // of security so warn even if things look OK. try { - phutil_require_module('phutil', 'utils'); - $open_libphutil = true; + $open_libphutil = class_exists('Future'); } catch (Exception $ex) { $message = $ex->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, '<?php echo php_sapi_name();'); list($err, $stdout, $stderr) = exec_manual( '/usr/bin/env -i %s -f %s', $php_bin, $tmp_file); if ($err) { self::writeFailure(); self::write("Unable to execute 'php' on the command line from the web ". "server.\n". " err: {$err}\n". "stdout: {$stdout}\n". "stderr: {$stderr}\n"); return; } else { self::write(" okay PHP is available from the command line.\n"); $sapi = trim($stdout); if ($sapi != 'cli') { self::writeFailure(); self::write( "The 'php' binary on this system uses the '{$sapi}' SAPI, but the ". "'cli' SAPI is expected. Replace 'php' with the php-cli SAPI ". "binary, or edit your webserver configuration so the first 'php' ". "in PATH is the 'cli' SAPI.\n\n". "If you're running cPanel with suphp, the easiest way to fix this ". "is to add '/usr/local/bin' before '/usr/bin' for 'env_path' in ". "suconf.php:\n\n". ' env_path="/bin:/usr/local/bin:/usr/bin"'. "\n\n"); return; } else { self::write(" okay 'php' is CLI SAPI.\n"); } } $root = dirname(phutil_get_library_root('phabricator')); // On RHEL6, doing a distro install of pcntl makes it available from the // CLI binary but not from the Apache module. This isn't entirely // unreasonable and we don't need it from Apache, so do an explicit test // for CLI availability. list($err, $stdout, $stderr) = exec_manual( 'php %s', "{$root}/scripts/setup/pcntl_available.php"); if ($err) { self::writeFailure(); self::write("Unable to execute scripts/setup/pcntl_available.php to ". "test for the availability of pcntl from the CLI.\n". " err: {$err}\n". "stdout: {$stdout}\n". "stderr: {$stderr}\n"); return; } else { if (trim($stdout) == 'YES') { self::write(" okay pcntl is available from the command line.\n"); self::write("[OKAY] All extensions OKAY\n"); } else { self::write(" warn pcntl is not available!\n"); self::write("[WARN] *** WARNING *** pcntl extension not available. ". "You will not be able to run daemons.\n"); } } self::writeHeader("GIT SUBMODULES"); if (!Filesystem::pathExists($root.'/.git')) { self::write(" skip Not a git clone.\n\n"); } else { list($info) = execx( '(cd %s && git submodule status)', $root); foreach (explode("\n", rtrim($info)) as $line) { $matches = null; if (!preg_match('/^(.)([0-9a-f]{40}) (\S+)(?: |$)/', $line, $matches)) { self::writeFailure(); self::write( "Setup failure! 'git submodule' produced unexpected output:\n". $line); return; } $status = $matches[1]; $module = $matches[3]; switch ($status) { case '-': case '+': case 'U': self::writeFailure(); self::write( "Setup failure! Git submodule '{$module}' is not up to date. ". "Run:\n\n". " cd {$root} && git submodule update --init\n\n". "...to update submodules."); return; case ' ': self::write(" okay Git submodule '{$module}' up to date.\n"); break; default: self::writeFailure(); self::write( "Setup failure! 'git submodule' reported unknown status ". "'{$status}' for submodule '{$module}'. This is a bug; report ". "it to the Phabricator maintainers."); return; } } } self::write("[OKAY] All submodules OKAY.\n"); self::writeHeader("BASIC CONFIGURATION"); $env = PhabricatorEnv::getEnvConfig('phabricator.env'); if ($env == 'production' || $env == 'default' || $env == 'development') { self::writeFailure(); self::write( "Setup failure! Your PHABRICATOR_ENV is set to '{$env}', which is ". "a Phabricator environmental default. You should create a custom ". "environmental configuration instead of editing the defaults ". "directly. See this document for instructions:\n"); self::writeDoc('article/Configuration_Guide.html'); return; } else { $host = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); $host_uri = new PhutilURI($host); $protocol = $host_uri->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 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $__start__ = microtime(true); $access_log = null; error_reporting(E_ALL | E_STRICT); if ($_SERVER['REQUEST_METHOD'] == 'POST' && !$_POST && isset($_REQUEST['__file__'])) { $size = ini_get('post_max_size'); phabricator_fatal( "Request size exceeds PHP 'post_max_size' ('{$size}')."); } $required_version = '5.2.3'; if (version_compare(PHP_VERSION, $required_version) < 0) { phabricator_fatal_config_error( "You are running PHP version '".PHP_VERSION."', which is older than ". "the minimum version, '{$required_version}'. Update to at least ". "'{$required_version}'."); } phabricator_detect_insane_memory_limit(); ini_set('memory_limit', -1); $env = getenv('PHABRICATOR_ENV'); // Apache if (!$env) { if (isset($_ENV['PHABRICATOR_ENV'])) { $env = $_ENV['PHABRICATOR_ENV']; } } if (!$env) { phabricator_fatal_config_error( "The 'PHABRICATOR_ENV' environmental variable is not defined. Modify ". "your httpd.conf to include 'SetEnv PHABRICATOR_ENV <env>', where '<env>' ". "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 = '<div style="text-align: center; background: #ff00ff; padding: 1em; font-size: 24px; font-weight: bold;">'. '<a href="/xhprof/profile/'.$profile.'/">'. '>>> View Profile <<<'. '</a>'. '</div>'; if (strpos($response_string, '<body>') !== false) { $response_string = str_replace( '<body>', '<body>'.$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); }