diff --git a/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php b/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php index d1e61640a8..f91401797a 100644 --- a/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php @@ -1,115 +1,116 @@ formatStringConstants($statuses); return array( 'ids' => 'optional list', 'names' => 'optional list', 'phids' => 'optional list', 'slugs' => 'optional list', 'status' => 'optional '.$status_const, 'members' => 'optional list', 'limit' => 'optional int', 'offset' => 'optional int', ); } public function defineReturnType() { return 'list'; } public function defineErrorTypes() { return array(); } protected function execute(ConduitAPIRequest $request) { $query = new PhabricatorProjectQuery(); $query->setViewer($request->getUser()); $query->needMembers(true); $query->needSlugs(true); $ids = $request->getValue('ids'); if ($ids) { $query->withIDs($ids); } $names = $request->getValue('names'); if ($names) { $query->withNames($names); } $status = $request->getValue('status'); if ($status) { $query->withStatus($status); } $phids = $request->getValue('phids'); if ($phids) { $query->withPHIDs($phids); } $slugs = $request->getValue('slugs'); if ($slugs) { $query->withSlugs($slugs); } $members = $request->getValue('members'); if ($members) { $query->withMemberPHIDs($members); } $limit = $request->getValue('limit'); if ($limit) { $query->setLimit($limit); } $offset = $request->getValue('offset'); if ($offset) { $query->setOffset($offset); } $pager = $this->newPager($request); $results = $query->executeWithCursorPager($pager); $projects = $this->buildProjectInfoDictionaries($results); // TODO: This is pretty hideous. $slug_map = array(); foreach ($slugs as $slug) { + $normal = rtrim(PhabricatorSlug::normalize($slug), '/'); foreach ($projects as $project) { - if (in_array($slug, $project['slugs'])) { + if (in_array($normal, $project['slugs'])) { $slug_map[$slug] = $project['phid']; } } } $result = array( 'data' => $projects, 'slugMap' => $slug_map, ); return $this->addPagerResults($result, $pager); } } diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index c0beaf2742..bb2d280ef3 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -1,361 +1,366 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function withMemberPHIDs(array $member_phids) { $this->memberPHIDs = $member_phids; return $this; } public function withSlugs(array $slugs) { $this->slugs = $slugs; return $this; } public function withPhrictionSlugs(array $slugs) { $this->phrictionSlugs = $slugs; return $this; } public function withNames(array $names) { $this->names = $names; return $this; } public function withDatasourceQuery($string) { $this->datasourceQuery = $string; return $this; } public function needMembers($need_members) { $this->needMembers = $need_members; return $this; } public function needWatchers($need_watchers) { $this->needWatchers = $need_watchers; return $this; } public function needImages($need_images) { $this->needImages = $need_images; return $this; } public function needSlugs($need_slugs) { $this->needSlugs = $need_slugs; return $this; } protected function getPagingColumn() { return 'name'; } protected function getPagingValue($result) { return $result->getName(); } protected function getReversePaging() { return true; } protected function loadPage() { $table = new PhabricatorProject(); $conn_r = $table->establishConnection('r'); // NOTE: Because visibility checks for projects depend on whether or not // the user is a project member, we always load their membership. If we're // loading all members anyway we can piggyback on that; otherwise we // do an explicit join. $select_clause = ''; if (!$this->needMembers) { $select_clause = ', vm.dst viewerIsMember'; } $data = queryfx_all( $conn_r, 'SELECT p.* %Q FROM %T p %Q %Q %Q %Q %Q', $select_clause, $table->getTableName(), $this->buildJoinClause($conn_r), $this->buildWhereClause($conn_r), $this->buildGroupClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $projects = $table->loadAllFromArray($data); if ($projects) { $viewer_phid = $this->getViewer()->getPHID(); $project_phids = mpull($projects, 'getPHID'); $member_type = PhabricatorEdgeConfig::TYPE_PROJ_MEMBER; $watcher_type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_WATCHER; $need_edge_types = array(); if ($this->needMembers) { $need_edge_types[] = $member_type; } else { foreach ($data as $row) { $projects[$row['id']]->setIsUserMember( $viewer_phid, ($row['viewerIsMember'] !== null)); } } if ($this->needWatchers) { $need_edge_types[] = $watcher_type; } if ($need_edge_types) { $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs($project_phids) ->withEdgeTypes($need_edge_types) ->execute(); if ($this->needMembers) { foreach ($projects as $project) { $phid = $project->getPHID(); $project->attachMemberPHIDs( array_keys($edges[$phid][$member_type])); $project->setIsUserMember( $viewer_phid, isset($edges[$phid][$member_type][$viewer_phid])); } } if ($this->needWatchers) { foreach ($projects as $project) { $phid = $project->getPHID(); $project->attachWatcherPHIDs( array_keys($edges[$phid][$watcher_type])); $project->setIsUserWatcher( $viewer_phid, isset($edges[$phid][$watcher_type][$viewer_phid])); } } } } return $projects; } protected function didFilterPage(array $projects) { if ($this->needImages) { $default = null; $file_phids = mpull($projects, 'getProfileImagePHID'); $files = id(new PhabricatorFileQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); foreach ($projects as $project) { $file = idx($files, $project->getProfileImagePHID()); if (!$file) { if (!$default) { $default = PhabricatorFile::loadBuiltin( $this->getViewer(), 'project.png'); } $file = $default; } $project->attachProfileImageFile($file); } } if ($this->needSlugs) { $slugs = id(new PhabricatorProjectSlug()) ->loadAllWhere( 'projectPHID IN (%Ls)', mpull($projects, 'getPHID')); $slugs = mgroup($slugs, 'getProjectPHID'); foreach ($projects as $project) { $project_slugs = idx($slugs, $project->getPHID(), array()); $project->attachSlugs($project_slugs); } } return $projects; } private function buildWhereClause($conn_r) { $where = array(); if ($this->status != self::STATUS_ANY) { switch ($this->status) { case self::STATUS_OPEN: case self::STATUS_ACTIVE: $filter = array( PhabricatorProjectStatus::STATUS_ACTIVE, ); break; case self::STATUS_CLOSED: case self::STATUS_ARCHIVED: $filter = array( PhabricatorProjectStatus::STATUS_ARCHIVED, ); break; default: throw new Exception( "Unknown project status '{$this->status}'!"); } $where[] = qsprintf( $conn_r, 'status IN (%Ld)', $filter); } if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->memberPHIDs) { $where[] = qsprintf( $conn_r, 'e.dst IN (%Ls)', $this->memberPHIDs); } if ($this->slugs) { + $slugs = array(); + foreach ($this->slugs as $slug) { + $slugs[] = rtrim(PhabricatorSlug::normalize($slug), '/'); + } + $where[] = qsprintf( $conn_r, 'slug.slug IN (%Ls)', - $this->slugs); + $slugs); } if ($this->phrictionSlugs) { $where[] = qsprintf( $conn_r, 'phrictionSlug IN (%Ls)', $this->phrictionSlugs); } if ($this->names) { $where[] = qsprintf( $conn_r, 'name IN (%Ls)', $this->names); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } private function buildGroupClause($conn_r) { if ($this->memberPHIDs || $this->datasourceQuery) { return 'GROUP BY p.id'; } else { return $this->buildApplicationSearchGroupClause($conn_r); } } private function buildJoinClause($conn_r) { $joins = array(); if (!$this->needMembers !== null) { $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T vm ON vm.src = p.phid AND vm.type = %d AND vm.dst = %s', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_PROJ_MEMBER, $this->getViewer()->getPHID()); } if ($this->memberPHIDs !== null) { $joins[] = qsprintf( $conn_r, 'JOIN %T e ON e.src = p.phid AND e.type = %d', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_PROJ_MEMBER); } if ($this->slugs !== null) { $joins[] = qsprintf( $conn_r, 'JOIN %T slug on slug.projectPHID = p.phid', id(new PhabricatorProjectSlug())->getTableName()); } if ($this->datasourceQuery !== null) { $tokens = PhabricatorTypeaheadDatasource::tokenizeString( $this->datasourceQuery); if (!$tokens) { throw new PhabricatorEmptyQueryException(); } $likes = array(); foreach ($tokens as $token) { $likes[] = qsprintf($conn_r, 'token.token LIKE %>', $token); } $joins[] = qsprintf( $conn_r, 'JOIN %T token ON token.projectID = p.id AND (%Q)', PhabricatorProject::TABLE_DATASOURCE_TOKEN, '('.implode(') OR (', $likes).')'); } $joins[] = $this->buildApplicationSearchJoinClause($conn_r); return implode(' ', $joins); } public function getQueryApplicationClass() { return 'PhabricatorProjectApplication'; } protected function getApplicationSearchObjectPHIDColumn() { return 'p.phid'; } }