diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 953cbb5c11..42dc88ab03 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -1,260 +1,259 @@ id = idx($data, 'id'); $this->page = idx($data, 'page'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $project = id(new PhabricatorProjectQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->needMembers(true) ->needProfiles(true) ->executeOne(); if (!$project) { return new Aphront404Response(); } $profile = $project->getProfile(); - - $picture = $profile->loadProfileImageURI(); + $picture = $profile->getProfileImageURI(); require_celerity_resource('phabricator-profile-css'); $tasks = $this->renderTasksPage($project, $profile); $query = new PhabricatorFeedQuery(); $query->setFilterPHIDs( array( $project->getPHID(), )); $query->setLimit(50); $query->setViewer($this->getRequest()->getUser()); $stories = $query->execute(); $feed = $this->renderStories($stories); $people = $this->renderPeoplePage($project, $profile); $content = id(new AphrontMultiColumnView()) ->addColumn($people) ->addColumn($feed) ->setFluidLayout(true); $content = hsprintf( '
%s
', pht( 'No one is affiliated with this project.')); } return hsprintf( '%s
', pht( 'NOTE: Policy settings are not yet fully implemented. '. 'Some interfaces still ignore these settings, '. 'particularly "Visible To".'))) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($user) ->setName('can_view') ->setCaption(pht('Members can always view a project.')) ->setPolicyObject($project) ->setPolicies($policies) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($user) ->setName('can_edit') ->setPolicyObject($project) ->setPolicies($policies) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($user) ->setName('can_join') ->setCaption( pht('Users who can edit a project can always join a project.')) ->setPolicyObject($project) ->setPolicies($policies) ->setCapability(PhabricatorPolicyCapability::CAN_JOIN)) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Profile Image')) ->setValue( phutil_tag( 'img', array( 'src' => $img_src, )))) ->appendChild( id(new AphrontFormImageControl()) ->setLabel(pht('Change Image')) ->setName('image') ->setError($e_image) ->setCaption( pht('Supported formats:').' '.implode(', ', $supported_formats))) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/project/view/'.$project->getID().'/') ->setValue(pht('Save'))); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormError($error_view) ->setForm($form); $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName($project->getName()) ->setHref('/project/view/'.$project->getID().'/')); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Edit Project')) ->setHref($this->getApplicationURI())); return $this->buildApplicationPage( array( $crumbs, $form_box, ), array( 'title' => $title, 'device' => true, )); } } diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index 0f4c6440e2..00f6ead387 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -1,228 +1,258 @@ 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 withPhrictionSlugs(array $slugs) { $this->slugs = $slugs; return $this; } public function needMembers($need_members) { $this->needMembers = $need_members; return $this; } public function needProfiles($need_profiles) { $this->needProfiles = $need_profiles; 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(); if ($this->needMembers) { $etype = PhabricatorEdgeConfig::TYPE_PROJ_MEMBER; $members = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($projects, 'getPHID')) ->withEdgeTypes(array($etype)) ->execute(); foreach ($projects as $project) { $phid = $project->getPHID(); $project->attachMemberPHIDs(array_keys($members[$phid][$etype])); $project->setIsUserMember( $viewer_phid, isset($members[$phid][$etype][$viewer_phid])); } } else { foreach ($data as $row) { $projects[$row['id']]->setIsUserMember( $viewer_phid, ($row['viewerIsMember'] !== null)); } } if ($this->needProfiles) { $profiles = id(new PhabricatorProjectProfile())->loadAllWhere( 'projectPHID IN (%Ls)', mpull($projects, 'getPHID')); $profiles = mpull($profiles, null, 'getProjectPHID'); + + $default = null; + + if ($profiles) { + $file_phids = mpull($profiles, 'getProfileImagePHID'); + $files = id(new PhabricatorFileQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + foreach ($profiles as $profile) { + $file = idx($files, $profile->getProfileImagePHID()); + if (!$file) { + if (!$default) { + $default = PhabricatorFile::loadBuiltin( + $this->getViewer(), + 'profile.png'); + } + $file = $default; + } + $profile->attachProfileImageFile($file); + } + } + foreach ($projects as $project) { $profile = idx($profiles, $project->getPHID()); if (!$profile) { + if (!$default) { + $default = PhabricatorFile::loadBuiltin( + $this->getViewer(), + 'profile.png'); + } $profile = id(new PhabricatorProjectProfile()) - ->setProjectPHID($project->getPHID()); + ->setProjectPHID($project->getPHID()) + ->attachProfileImageFile($default); } $project->attachProfile($profile); } } } 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) { $where[] = qsprintf( $conn_r, 'phrictionSlug IN (%Ls)', $this->slugs); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } private function buildGroupClause($conn_r) { if ($this->memberPHIDs) { return 'GROUP BY p.id'; } else { return ''; } } private function buildJoinClause($conn_r) { $joins = array(); if (!$this->needMembers) { $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) { $joins[] = qsprintf( $conn_r, 'JOIN %T e ON e.src = p.phid AND e.type = %d', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_PROJ_MEMBER); } return implode(' ', $joins); } } diff --git a/src/applications/project/storage/PhabricatorProjectProfile.php b/src/applications/project/storage/PhabricatorProjectProfile.php index ee8f42abea..57c835761f 100644 --- a/src/applications/project/storage/PhabricatorProjectProfile.php +++ b/src/applications/project/storage/PhabricatorProjectProfile.php @@ -1,21 +1,24 @@ getProfileImagePHID(); + private $profileImageFile = self::ATTACHABLE; - // TODO: (T603) Can we get rid of this and move it to a Query? - $file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $src_phid); - if ($file) { - return $file->getBestURI(); - } + public function getProfileImageURI() { + return $this->getProfileImageFile()->getBestURI(); + } + + public function attachProfileImageFile(PhabricatorFile $file) { + $this->profileImageFile = $file; + return $this; + } - return PhabricatorUser::getDefaultProfileImageURI(); + public function getProfileImageFile() { + return $this->assertAttached($this->profileImageFile); } } diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php index 74a4f35563..ce2bcce99f 100644 --- a/src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php +++ b/src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php @@ -1,380 +1,380 @@ type = $data['type']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $query = $request->getStr('q'); $raw_query = $request->getStr('raw'); $need_rich_data = false; $need_users = false; $need_agents = false; $need_applications = false; $need_all_users = false; $need_lists = false; $need_projs = false; $need_repos = false; $need_packages = false; $need_upforgrabs = false; $need_arcanist_projects = false; $need_noproject = false; $need_symbols = false; $need_jump_objects = false; switch ($this->type) { case 'mainsearch': $need_users = true; $need_applications = true; $need_rich_data = true; $need_symbols = true; $need_projs = true; $need_jump_objects = true; break; case 'searchowner': $need_users = true; $need_upforgrabs = true; break; case 'searchproject': $need_projs = true; $need_noproject = true; break; case 'users': $need_users = true; break; case 'authors': $need_users = true; $need_agents = true; break; case 'mailable': $need_users = true; $need_lists = true; break; case 'allmailable': $need_users = true; $need_all_users = true; $need_lists = true; break; case 'projects': $need_projs = true; break; case 'usersorprojects': $need_users = true; $need_projs = true; break; case 'repositories': $need_repos = true; break; case 'packages': $need_packages = true; break; case 'accounts': $need_users = true; $need_all_users = true; break; case 'accountsorprojects': $need_users = true; $need_all_users = true; $need_projs = true; break; case 'arcanistprojects': $need_arcanist_projects = true; break; } $results = array(); if ($need_upforgrabs) { $results[] = id(new PhabricatorTypeaheadResult()) ->setName('upforgrabs (Up For Grabs)') ->setPHID(ManiphestTaskOwner::OWNER_UP_FOR_GRABS); } if ($need_noproject) { $results[] = id(new PhabricatorTypeaheadResult()) ->setName('noproject (No Project)') ->setPHID(ManiphestTaskOwner::PROJECT_NO_PROJECT); } if ($need_users) { $columns = array( 'isSystemAgent', 'isAdmin', 'isDisabled', 'userName', 'realName', 'phid'); if ($query) { // This is an arbitrary limit which is just larger than any limit we // actually use in the application. // TODO: The datasource should pass this in the query. $limit = 15; $user_table = new PhabricatorUser(); $conn_r = $user_table->establishConnection('r'); $ids = queryfx_all( $conn_r, 'SELECT id FROM %T WHERE username LIKE %> ORDER BY username ASC LIMIT %d', $user_table->getTableName(), $query, $limit); $ids = ipull($ids, 'id'); if (count($ids) < $limit) { // If we didn't find enough username hits, look for real name hits. // We need to pull the entire pagesize so that we end up with the // right number of items if this query returns many duplicate IDs // that we've already selected. $realname_ids = queryfx_all( $conn_r, 'SELECT DISTINCT userID FROM %T WHERE token LIKE %> ORDER BY token ASC LIMIT %d', PhabricatorUser::NAMETOKEN_TABLE, $query, $limit); $realname_ids = ipull($realname_ids, 'userID'); $ids = array_merge($ids, $realname_ids); $ids = array_unique($ids); $ids = array_slice($ids, 0, $limit); } // Always add the logged-in user because some tokenizers autosort them // first. They'll be filtered out on the client side if they don't // match the query. $ids[] = $request->getUser()->getID(); if ($ids) { $users = id(new PhabricatorUser())->loadColumnsWhere( $columns, 'id IN (%Ld)', $ids); } else { $users = array(); } } else { $users = id(new PhabricatorUser())->loadColumns($columns); } if ($need_rich_data) { $phids = mpull($users, 'getPHID'); $handles = $this->loadViewerHandles($phids); } foreach ($users as $user) { if (!$need_all_users) { if (!$need_agents && $user->getIsSystemAgent()) { continue; } if ($user->getIsDisabled()) { continue; } } $result = id(new PhabricatorTypeaheadResult()) ->setName($user->getFullName()) ->setURI('/p/'.$user->getUsername()) ->setPHID($user->getPHID()) ->setPriorityString($user->getUsername()); if ($need_rich_data) { $display_type = 'User'; if ($user->getIsAdmin()) { $display_type = 'Administrator'; } $result->setDisplayType($display_type); $result->setImageURI($handles[$user->getPHID()]->getImageURI()); $result->setPriorityType('user'); } $results[] = $result; } } if ($need_lists) { $lists = id(new PhabricatorMailingListQuery()) ->setViewer($viewer) ->execute(); foreach ($lists as $list) { $results[] = id(new PhabricatorTypeaheadResult()) ->setName($list->getName()) ->setURI($list->getURI()) ->setPHID($list->getPHID()); } } if ($need_projs) { $projs = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withStatus(PhabricatorProjectQuery::STATUS_OPEN) ->needProfiles(true) ->execute(); foreach ($projs as $proj) { $proj_result = id(new PhabricatorTypeaheadResult()) ->setName($proj->getName()) ->setDisplayType("Project") ->setURI('/project/view/'.$proj->getID().'/') ->setPHID($proj->getPHID()); + $prof = $proj->getProfile(); - if ($prof) { - $proj_result->setImageURI($prof->loadProfileImageURI()); - } + $proj_result->setImageURI($prof->getProfileImageURI()); + $results[] = $proj_result; } } if ($need_repos) { $repos = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->execute(); foreach ($repos as $repo) { $results[] = id(new PhabricatorTypeaheadResult()) ->setName('r'.$repo->getCallsign().' ('.$repo->getName().')') ->setURI('/diffusion/'.$repo->getCallsign().'/') ->setPHID($repo->getPHID()) ->setPriorityString('r'.$repo->getCallsign()); } } if ($need_packages) { $packages = id(new PhabricatorOwnersPackage())->loadAll(); foreach ($packages as $package) { $results[] = id(new PhabricatorTypeaheadResult()) ->setName($package->getName()) ->setURI('/owners/package/'.$package->getID().'/') ->setPHID($package->getPHID()); } } if ($need_arcanist_projects) { $arcprojs = id(new PhabricatorRepositoryArcanistProject())->loadAll(); foreach ($arcprojs as $proj) { $results[] = id(new PhabricatorTypeaheadResult()) ->setName($proj->getName()) ->setPHID($proj->getPHID()); } } if ($need_applications) { $applications = PhabricatorApplication::getAllInstalledApplications(); foreach ($applications as $application) { $uri = $application->getTypeaheadURI(); if (!$uri) { continue; } $name = $application->getName().' '.$application->getShortDescription(); $results[] = id(new PhabricatorTypeaheadResult()) ->setName($name) ->setURI($uri) ->setPHID($application->getPHID()) ->setPriorityString($application->getName()) ->setDisplayName($application->getName()) ->setDisplayType($application->getShortDescription()) ->setImageuRI($application->getIconURI()) ->setPriorityType('apps'); } } if ($need_symbols) { $symbols = id(new DiffusionSymbolQuery()) ->setNamePrefix($query) ->setLimit(15) ->needArcanistProjects(true) ->needRepositories(true) ->needPaths(true) ->execute(); foreach ($symbols as $symbol) { $lang = $symbol->getSymbolLanguage(); $name = $symbol->getSymbolName(); $type = $symbol->getSymbolType(); $proj = $symbol->getArcanistProject()->getName(); $results[] = id(new PhabricatorTypeaheadResult()) ->setName($name) ->setURI($symbol->getURI()) ->setPHID(md5($symbol->getURI())) // Just needs to be unique. ->setDisplayName($name) ->setDisplayType(strtoupper($lang).' '.ucwords($type).' ('.$proj.')') ->setPriorityType('symb'); } } if ($need_jump_objects) { $objects = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withNames(array($raw_query)) ->execute(); if ($objects) { $handles = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs(mpull($objects, 'getPHID')) ->execute(); $handle = head($handles); if ($handle) { $results[] = id(new PhabricatorTypeaheadResult()) ->setName($handle->getFullName()) ->setDisplayType($handle->getTypeName()) ->setURI($handle->getURI()) ->setPHID($handle->getPHID()) ->setPriorityType('jump'); } } } $content = mpull($results, 'getWireFormat'); if ($request->isAjax()) { return id(new AphrontAjaxResponse())->setContent($content); } // If there's a non-Ajax request to this endpoint, show results in a tabular // format to make it easier to debug typeahead output. $rows = array(); foreach ($results as $result) { $wire = $result->getWireFormat(); $rows[] = $wire; } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Name', 'URI', 'PHID', 'Priority', 'Display Name', 'Display Type', 'Image URI', 'Priority Type', )); $panel = new AphrontPanelView(); $panel->setHeader('Typeahead Results'); $panel->appendChild($table); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Typeahead Results', )); } }