diff --git a/src/applications/auth/view/PhabricatorOAuthFailureView.php b/src/applications/auth/view/PhabricatorOAuthFailureView.php index b8fa2afd2..a1e454974 100644 --- a/src/applications/auth/view/PhabricatorOAuthFailureView.php +++ b/src/applications/auth/view/PhabricatorOAuthFailureView.php @@ -1,88 +1,89 @@ request = $request; return $this; } public function setOAuthProvider($provider) { $this->provider = $provider; return $this; } public function setException(Exception $e) { $this->exception = $e; return $this; } public function render() { $request = $this->request; $provider = $this->provider; $provider_name = $provider->getProviderName(); $diagnose = null; $view = new AphrontRequestFailureView(); $view->setHeader(pht('%s Auth Failed', $provider_name)); if ($this->request) { $view->appendChild( hsprintf( '

Description: %s

', $request->getStr('error_description'))); $view->appendChild( hsprintf( '

Error: %s

', $request->getStr('error'))); $view->appendChild( hsprintf( '

Error Reason: %s

', $request->getStr('error_reason'))); } else if ($this->exception) { $view->appendChild( hsprintf( '

Error Details: %s

', $this->exception->getMessage())); } else { // TODO: We can probably refine this. $view->appendChild( hsprintf( '

Unable to authenticate with %s. '. 'There are several reasons this might happen:

'. ''. '

You can try again, or login using another method.

', $provider_name, $provider_name, $provider_name, $provider_name)); $provider_key = $provider->getProviderKey(); $diagnose = hsprintf( - ''. + ''. 'Diagnose %s OAuth Problems'. '', + $provider_key, $provider_name); } $view->appendChild( '
'. $diagnose. ''.pht('Continue').''. '
'); return $view->render(); } } diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php index 004faf0a2..a7ada70a8 100644 --- a/src/applications/diffusion/view/DiffusionBrowseTableView.php +++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php @@ -1,289 +1,288 @@ paths = $paths; return $this; } public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; return $this; } public function renderLastModifiedColumns( array $handles, PhabricatorRepositoryCommit $commit = null, PhabricatorRepositoryCommitData $data = null) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $drequest = $this->getDiffusionRequest(); if ($commit) { $epoch = $commit->getEpoch(); $modified = DiffusionView::linkCommit( $drequest->getRepository(), $commit->getCommitIdentifier()); $date = phabricator_date($epoch, $this->user); $time = phabricator_time($epoch, $this->user); } else { $modified = ''; $date = ''; $time = ''; } if ($data) { $author_phid = $data->getCommitDetail('authorPHID'); if ($author_phid && isset($handles[$author_phid])) { $author = $handles[$author_phid]->renderLink(); } else { $author = self::renderName($data->getAuthorName()); } $committer = $data->getCommitDetail('committer'); if ($committer) { $committer_phid = $data->getCommitDetail('committerPHID'); if ($committer_phid && isset($handles[$committer_phid])) { $committer = $handles[$committer_phid]->renderLink(); } else { $committer = self::renderName($committer); } if ($author != $committer) { $author .= '/'.$committer; } } $details = AphrontTableView::renderSingleDisplayLine( phutil_escape_html($data->getSummary())); } else { $author = ''; $details = ''; } $return = array( 'commit' => $modified, 'date' => $date, 'time' => $time, 'author' => $author, 'details' => $details, ); $lint = self::loadLintMessagesCount($drequest); if ($lint !== null) { $return['lint'] = hsprintf( '%s', $drequest->generateURI(array( 'action' => 'lint', 'lint' => null, )), number_format($lint)); } return $return; } private static function loadLintMessagesCount(DiffusionRequest $drequest) { $branch = $drequest->loadBranch(); if (!$branch) { return null; } $conn = $drequest->getRepository()->establishConnection('r'); - $where = ''; + $path = '/'.$drequest->getPath(); + $where = (substr($path, -1) == '/' + ? qsprintf($conn, 'AND path LIKE %>', $path) + : qsprintf($conn, 'AND path = %s', $path)); + if ($drequest->getLint()) { - $where = qsprintf( - $conn, - 'AND code = %s', - $drequest->getLint()); + $where .= qsprintf($conn, ' AND code = %s', $drequest->getLint()); } - $like = (substr($drequest->getPath(), -1) == '/' ? 'LIKE %>' : '= %s'); return head(queryfx_one( $conn, - 'SELECT COUNT(*) FROM %T WHERE branchID = %d %Q AND path '.$like, + 'SELECT COUNT(*) FROM %T WHERE branchID = %d %Q', PhabricatorRepository::TABLE_LINTMESSAGE, $branch->getID(), - $where, - '/'.$drequest->getPath())); + $where)); } public function render() { $request = $this->getDiffusionRequest(); $repository = $request->getRepository(); $base_path = trim($request->getPath(), '/'); if ($base_path) { $base_path = $base_path.'/'; } $need_pull = array(); $rows = array(); $show_edit = false; foreach ($this->paths as $path) { $dir_slash = null; $file_type = $path->getFileType(); if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { $browse_text = $path->getPath().'/'; $dir_slash = '/'; $browse_link = ''.$this->linkBrowse( $base_path.$path->getPath().$dir_slash, array( 'html' => $this->renderPathIcon( 'dir', $browse_text), )).''; } else if ($file_type == DifferentialChangeType::FILE_SUBMODULE) { $browse_text = $path->getPath().'/'; $browse_link = ''. $this->linkExternal( $path->getHash(), $path->getExternalURI(), $this->renderPathIcon( 'ext', $browse_text)). ''; } else { if ($file_type == DifferentialChangeType::FILE_SYMLINK) { $type = 'link'; } else { $type = 'file'; } $browse_text = $path->getPath(); $browse_link = $this->linkBrowse( $base_path.$path->getPath(), array( 'html' => $this->renderPathIcon($type, $browse_text), )); } $commit = $path->getLastModifiedCommit(); if ($commit) { $drequest = clone $request; $drequest->setPath($request->getPath().$path->getPath().$dir_slash); $dict = $this->renderLastModifiedColumns( $this->handles, $commit, $path->getLastCommitData()); } else { $dict = array( 'lint' => celerity_generate_unique_node_id(), 'commit' => celerity_generate_unique_node_id(), 'date' => celerity_generate_unique_node_id(), 'time' => celerity_generate_unique_node_id(), 'author' => celerity_generate_unique_node_id(), 'details' => celerity_generate_unique_node_id(), ); $uri = (string)$request->generateURI( array( 'action' => 'lastmodified', 'path' => $base_path.$path->getPath().$dir_slash, )); $need_pull[$uri] = $dict; foreach ($dict as $k => $uniq) { $dict[$k] = ''; } } $editor_button = ''; if ($this->user) { $editor_link = $this->user->loadEditorLink( $base_path.$path->getPath(), 1, $request->getRepository()->getCallsign()); if ($editor_link) { $show_edit = true; $editor_button = phutil_render_tag( 'a', array( 'href' => $editor_link, ), 'Edit'); } } $rows[] = array( $this->linkHistory($base_path.$path->getPath().$dir_slash), $editor_button, $browse_link, idx($dict, 'lint'), $dict['commit'], $dict['date'], $dict['time'], $dict['author'], $dict['details'], ); } if ($need_pull) { Javelin::initBehavior('diffusion-pull-lastmodified', $need_pull); } $branch = $this->getDiffusionRequest()->loadBranch(); $show_lint = ($branch && $branch->getLintCommit()); $lint = $request->getLint(); $view = new AphrontTableView($rows); $view->setHeaders( array( 'History', 'Edit', 'Path', ($lint ? phutil_escape_html($lint) : 'Lint'), 'Modified', 'Date', 'Time', 'Author/Committer', 'Details', )); $view->setColumnClasses( array( '', '', '', 'n', '', '', 'right', '', 'wide', )); $view->setColumnVisibility( array( true, $show_edit, true, $show_lint, true, true, true, true, true, )); return $view->render(); } private function renderPathIcon($type, $text) { require_celerity_resource('diffusion-icons-css'); return phutil_render_tag( 'span', array( 'class' => 'diffusion-path-icon diffusion-path-icon-'.$type, ), phutil_escape_html($text)); } } diff --git a/src/applications/search/engine/PhabricatorSearchEngineMySQL.php b/src/applications/search/engine/PhabricatorSearchEngineMySQL.php index cdc95be54..65f3b6b4d 100644 --- a/src/applications/search/engine/PhabricatorSearchEngineMySQL.php +++ b/src/applications/search/engine/PhabricatorSearchEngineMySQL.php @@ -1,320 +1,321 @@ getPHID(); if (!$phid) { throw new Exception("Document has no PHID!"); } $store = new PhabricatorSearchDocument(); $store->setPHID($doc->getPHID()); $store->setDocumentType($doc->getDocumentType()); $store->setDocumentTitle($doc->getDocumentTitle()); $store->setDocumentCreated($doc->getDocumentCreated()); $store->setDocumentModified($doc->getDocumentModified()); $store->replace(); $conn_w = $store->establishConnection('w'); $field_dao = new PhabricatorSearchDocumentField(); queryfx( $conn_w, 'DELETE FROM %T WHERE phid = %s', $field_dao->getTableName(), $phid); foreach ($doc->getFieldData() as $field) { list($ftype, $corpus, $aux_phid) = $field; queryfx( $conn_w, 'INSERT INTO %T (phid, phidType, field, auxPHID, corpus) '. ' VALUES (%s, %s, %s, %ns, %s)', $field_dao->getTableName(), $phid, $doc->getDocumentType(), $ftype, $aux_phid, $corpus); } $sql = array(); foreach ($doc->getRelationshipData() as $relationship) { list($rtype, $to_phid, $to_type, $time) = $relationship; $sql[] = qsprintf( $conn_w, '(%s, %s, %s, %s, %d)', $phid, $to_phid, $rtype, $to_type, $time); } $rship_dao = new PhabricatorSearchDocumentRelationship(); queryfx( $conn_w, 'DELETE FROM %T WHERE phid = %s', $rship_dao->getTableName(), $phid); if ($sql) { queryfx( $conn_w, 'INSERT INTO %T'. ' (phid, relatedPHID, relation, relatedType, relatedTime) '. ' VALUES %Q', $rship_dao->getTableName(), implode(', ', $sql)); } } /** * Rebuild the PhabricatorSearchAbstractDocument that was used to index * an object out of the index itself. This is primarily useful for debugging, * as it allows you to inspect the search index representation of a * document. * * @param phid PHID of a document which exists in the search index. * @return null|PhabricatorSearchAbstractDocument Abstract document object * which corresponds to the original abstract document used to * build the document index. */ public function reconstructDocument($phid) { $dao_doc = new PhabricatorSearchDocument(); $dao_field = new PhabricatorSearchDocumentField(); $dao_relationship = new PhabricatorSearchDocumentRelationship(); $t_doc = $dao_doc->getTableName(); $t_field = $dao_field->getTableName(); $t_relationship = $dao_relationship->getTableName(); $doc = queryfx_one( $dao_doc->establishConnection('r'), 'SELECT * FROM %T WHERE phid = %s', $t_doc, $phid); if (!$doc) { return null; } $fields = queryfx_all( $dao_field->establishConnection('r'), 'SELECT * FROM %T WHERE phid = %s', $t_field, $phid); $relationships = queryfx_all( $dao_relationship->establishConnection('r'), 'SELECT * FROM %T WHERE phid = %s', $t_relationship, $phid); $adoc = id(new PhabricatorSearchAbstractDocument()) ->setPHID($phid) ->setDocumentType($doc['documentType']) ->setDocumentTitle($doc['documentTitle']) ->setDocumentCreated($doc['documentCreated']) ->setDocumentModified($doc['documentModified']); foreach ($fields as $field) { $adoc->addField( $field['field'], $field['corpus'], $field['auxPHID']); } foreach ($relationships as $relationship) { $adoc->addRelationship( $relationship['relation'], $relationship['relatedPHID'], $relationship['relatedType'], $relationship['relatedTime']); } return $adoc; } public function executeSearch(PhabricatorSearchQuery $query) { $where = array(); $join = array(); $order = 'ORDER BY documentCreated DESC'; $dao_doc = new PhabricatorSearchDocument(); $dao_field = new PhabricatorSearchDocumentField(); $t_doc = $dao_doc->getTableName(); $t_field = $dao_field->getTableName(); $conn_r = $dao_doc->establishConnection('r'); $q = $query->getQuery(); if (strlen($q)) { $join[] = qsprintf( $conn_r, - "{$t_field} field ON field.phid = document.phid"); + '%T field ON field.phid = document.phid', + $t_field); $where[] = qsprintf( $conn_r, 'MATCH(corpus) AGAINST (%s IN BOOLEAN MODE)', $q); // When searching for a string, promote user listings above other // listings. $order = qsprintf( $conn_r, 'ORDER BY IF(documentType = %s, 0, 1) ASC, MAX(MATCH(corpus) AGAINST (%s)) DESC', 'USER', $q); $field = $query->getParameter('field'); if ($field/* && $field != AdjutantQuery::FIELD_ALL*/) { $where[] = qsprintf( $conn_r, 'field.field = %s', $field); } } $exclude = $query->getParameter('exclude'); if ($exclude) { $where[] = qsprintf($conn_r, 'document.phid != %s', $exclude); } if ($query->getParameter('type')) { if (strlen($q)) { // TODO: verify that this column actually does something useful in query // plans once we have nontrivial amounts of data. $where[] = qsprintf( $conn_r, 'field.phidType = %s', $query->getParameter('type')); } $where[] = qsprintf( $conn_r, 'document.documentType = %s', $query->getParameter('type')); } $join[] = $this->joinRelationship( $conn_r, $query, 'author', PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR); $join[] = $this->joinRelationship( $conn_r, $query, 'open', PhabricatorSearchRelationship::RELATIONSHIP_OPEN); $join[] = $this->joinRelationship( $conn_r, $query, 'owner', PhabricatorSearchRelationship::RELATIONSHIP_OWNER); $join[] = $this->joinRelationship( $conn_r, $query, 'subscribers', PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER); $join[] = $this->joinRelationship( $conn_r, $query, 'project', PhabricatorSearchRelationship::RELATIONSHIP_PROJECT); $join[] = $this->joinRelationship( $conn_r, $query, 'repository', PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY); $join = array_filter($join); foreach ($join as $key => $clause) { $join[$key] = ' JOIN '.$clause; } $join = implode(' ', $join); if ($where) { $where = 'WHERE '.implode(' AND ', $where); } else { $where = ''; } $offset = (int)$query->getParameter('offset', 0); $limit = (int)$query->getParameter('limit', 25); $hits = queryfx_all( $conn_r, 'SELECT document.phid FROM %T document %Q %Q GROUP BY document.phid %Q LIMIT %d, %d', $t_doc, $join, $where, $order, $offset, $limit); return ipull($hits, 'phid'); } protected function joinRelationship( AphrontDatabaseConnection $conn, PhabricatorSearchQuery $query, $field, $type) { $phids = $query->getParameter($field, array()); if (!$phids) { return null; } $is_existence = false; switch ($type) { case PhabricatorSearchRelationship::RELATIONSHIP_OPEN: $is_existence = true; break; } $sql = qsprintf( $conn, '%T AS %C ON %C.phid = document.phid AND %C.relation = %s', id(new PhabricatorSearchDocumentRelationship())->getTableName(), $field, $field, $field, $type); if (!$is_existence) { $sql .= qsprintf( $conn, ' AND %C.relatedPHID in (%Ls)', $field, $phids); } return $sql; } } diff --git a/src/infrastructure/celerity/CelerityResourceTransformer.php b/src/infrastructure/celerity/CelerityResourceTransformer.php index d650a91b4..180de4941 100644 --- a/src/infrastructure/celerity/CelerityResourceTransformer.php +++ b/src/infrastructure/celerity/CelerityResourceTransformer.php @@ -1,111 +1,111 @@ translateURICallback = $translate_uricallback; return $this; } public function setMinify($minify) { $this->minify = $minify; return $this; } public function setRawResourceMap(array $raw_resource_map) { $this->rawResourceMap = $raw_resource_map; return $this; } public function setCelerityMap(CelerityResourceMap $celerity_map) { $this->celerityMap = $celerity_map; return $this; } public function transformResource($path, $data) { $type = self::getResourceType($path); switch ($type) { case 'css': $data = preg_replace_callback( '@url\s*\((\s*[\'"]?.*?)\)@s', nonempty( $this->translateURICallback, array($this, 'translateResourceURI')), $data); break; } if (!$this->minify) { return $data; } // Some resources won't survive minification (like Raphael.js), and are // marked so as not to be minified. if (strpos($data, '@'.'do-not-minify') !== false) { return $data; } switch ($type) { case 'css': // Remove comments. $data = preg_replace('@/\*.*?\*/@s', '', $data); // Remove whitespace around symbols. $data = preg_replace('@\s*([{}:;,])\s*@', '\1', $data); // Remove unnecessary semicolons. $data = preg_replace('@;}@', '}', $data); // Replace #rrggbb with #rgb when possible. $data = preg_replace( '@#([a-f0-9])\1([a-f0-9])\2([a-f0-9])\3@i', '#\1\2\3', $data); $data = trim($data); break; case 'js': $root = dirname(phutil_get_library_root('phabricator')); $bin = $root.'/externals/javelin/support/jsxmin/jsxmin'; if (@file_exists($bin)) { - $future = new ExecFuture("{$bin} __DEV__:0"); + $future = new ExecFuture('%s __DEV__:0', $bin); $future->write($data); list($err, $result) = $future->resolve(); if (!$err) { $data = $result; } } break; } return $data; } public static function getResourceType($path) { return last(explode('.', $path)); } public function translateResourceURI(array $matches) { $uri = trim($matches[1], "'\" \r\t\n"); if ($this->rawResourceMap) { if (isset($this->rawResourceMap[$uri]['uri'])) { $uri = $this->rawResourceMap[$uri]['uri']; } } else if ($this->celerityMap) { $info = $this->celerityMap->lookupFileInformation($uri); if ($info) { $uri = $info['uri']; } } return 'url('.$uri.')'; } } diff --git a/src/infrastructure/lint/linter/PhabricatorJavelinLinter.php b/src/infrastructure/lint/linter/PhabricatorJavelinLinter.php index e2d39c368..dc1a09957 100644 --- a/src/infrastructure/lint/linter/PhabricatorJavelinLinter.php +++ b/src/infrastructure/lint/linter/PhabricatorJavelinLinter.php @@ -1,247 +1,245 @@ haveSymbolsBinary === null) { list($err) = exec_manual('which javelinsymbols'); $this->haveSymbolsBinary = !$err; if (!$this->haveSymbolsBinary) { return; } } $futures = array(); foreach ($paths as $path) { if ($this->shouldIgnorePath($path)) { continue; } $future = $this->newSymbolsFuture($path); $futures[$path] = $future; } foreach (Futures($futures)->limit(8) as $path => $future) { $this->symbols[$path] = $future->resolvex(); } } public function getLinterName() { return 'JAVELIN'; } public function getLintSeverityMap() { return array( self::LINT_MISSING_BINARY => ArcanistLintSeverity::SEVERITY_WARNING, ); } public function getLintNameMap() { return array( self::LINT_PRIVATE_ACCESS => 'Private Method/Member Access', self::LINT_MISSING_DEPENDENCY => 'Missing Javelin Dependency', self::LINT_UNNECESSARY_DEPENDENCY => 'Unnecessary Javelin Dependency', self::LINT_UNKNOWN_DEPENDENCY => 'Unknown Javelin Dependency', self::LINT_MISSING_BINARY => '`javelinsymbols` Not In Path', ); } private function shouldIgnorePath($path) { return preg_match('@/__tests__/|externals/javelinjs/src/docs/@', $path); } public function lintPath($path) { if ($this->shouldIgnorePath($path)) { return; } if (!$this->haveSymbolsBinary) { if (!$this->haveWarnedAboutBinary) { $this->haveWarnedAboutBinary = true; // TODO: Write build documentation for the Javelin binaries and point // the user at it. $this->raiseLintAtLine( 1, 0, self::LINT_MISSING_BINARY, "The 'javelinsymbols' binary in the Javelin project is not ". "available in \$PATH, so the Javelin linter can't run. This ". "isn't a big concern, but means some Javelin problems can't be ". "automatically detected."); } return; } list($uses, $installs) = $this->getUsedAndInstalledSymbolsForPath($path); foreach ($uses as $symbol => $line) { $parts = explode('.', $symbol); foreach ($parts as $part) { if ($part[0] == '_' && $part[1] != '_') { $base = implode('.', array_slice($parts, 0, 2)); if (!array_key_exists($base, $installs)) { $this->raiseLintAtLine( $line, 0, self::LINT_PRIVATE_ACCESS, "This file accesses private symbol '{$symbol}' across file ". "boundaries. You may only access private members and methods ". "from the file where they are defined."); } break; } } } if ($this->getEngine()->getCommitHookMode()) { // Don't do the dependency checks in commit-hook mode because we won't // have an available working copy. return; } $external_classes = array(); foreach ($uses as $symbol => $line) { $parts = explode('.', $symbol); $class = implode('.', array_slice($parts, 0, 2)); if (!array_key_exists($class, $external_classes) && !array_key_exists($class, $installs)) { $external_classes[$class] = $line; } } $celerity = CelerityResourceMap::getInstance(); $path = preg_replace( '@^externals/javelinjs/src/@', 'webroot/rsrc/js/javelin/', $path); $need = $external_classes; $info = $celerity->lookupFileInformation(substr($path, strlen('webroot'))); if (!$info) { $info = array(); } $requires = idx($info, 'requires', array()); foreach ($requires as $key => $name) { $symbol_info = $celerity->lookupSymbolInformation($name); if (!$symbol_info) { $this->raiseLintAtLine( 0, 0, self::LINT_UNKNOWN_DEPENDENCY, "This file @requires component '{$name}', but it does not ". "exist. You may need to rebuild the Celerity map."); unset($requires[$key]); continue; } $symbol_path = 'webroot'.$symbol_info['disk']; list($ignored, $req_install) = $this->getUsedAndInstalledSymbolsForPath( $symbol_path); if (array_intersect_key($req_install, $external_classes)) { $need = array_diff_key($need, $req_install); unset($requires[$key]); } } foreach ($need as $class => $line) { $this->raiseLintAtLine( $line, 0, self::LINT_MISSING_DEPENDENCY, "This file uses '{$class}' but does not @requires the component ". "which installs it. You may need to rebuild the Celerity map."); } foreach ($requires as $component) { $this->raiseLintAtLine( 0, 0, self::LINT_UNNECESSARY_DEPENDENCY, "This file @requires component '{$component}' but does not use ". "anything it provides."); } } private function loadSymbols($path) { if (empty($this->symbols[$path])) { $this->symbols[$path] = $this->newSymbolsFuture($path)->resolvex(); } return $this->symbols[$path]; } private function newSymbolsFuture($path) { - $javelinsymbols = 'javelinsymbols'; - - $future = new ExecFuture($javelinsymbols.' # '.escapeshellarg($path)); + $future = new ExecFuture('javelinsymbols # %s', $path); $future->write($this->getData($path)); return $future; } private function getUsedAndInstalledSymbolsForPath($path) { list($symbols) = $this->loadSymbols($path); $symbols = trim($symbols); $uses = array(); $installs = array(); if (empty($symbols)) { // This file has no symbols. return array($uses, $installs); } $symbols = explode("\n", trim($symbols)); foreach ($symbols as $line) { $matches = null; if (!preg_match('/^([?+\*])([^:]*):(\d+)$/', $line, $matches)) { throw new Exception( "Received malformed output from `javelinsymbols`."); } $type = $matches[1]; $symbol = $matches[2]; $line = $matches[3]; switch ($type) { case '?': $uses[$symbol] = $line; break; case '+': $installs['JX.'.$symbol] = $line; break; } } $contents = $this->getData($path); $matches = null; $count = preg_match_all( '/@javelin-installs\W+(\S+)/', $contents, $matches, PREG_PATTERN_ORDER); if ($count) { foreach ($matches[1] as $symbol) { $installs[$symbol] = 0; } } return array($uses, $installs); } }