diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php index 45e2f193af..7c344b0b8e 100644 --- a/src/applications/project/controller/PhabricatorProjectController.php +++ b/src/applications/project/controller/PhabricatorProjectController.php @@ -1,169 +1,177 @@ project = $project; return $this; } protected function getProject() { return $this->project; } protected function loadProject() { $viewer = $this->getViewer(); $request = $this->getRequest(); $id = nonempty( $request->getURIData('projectID'), $request->getURIData('id')); $slug = $request->getURIData('slug'); if ($slug) { $normal_slug = PhabricatorSlug::normalizeProjectSlug($slug); $is_abnormal = ($slug !== $normal_slug); $normal_uri = "/tag/{$normal_slug}/"; } else { $is_abnormal = false; } $query = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->needMembers(true) ->needWatchers(true) ->needImages(true) ->needSlugs(true); if ($slug) { $query->withSlugs(array($slug)); } else { $query->withIDs(array($id)); } $policy_exception = null; try { $project = $query->executeOne(); } catch (PhabricatorPolicyException $ex) { $policy_exception = $ex; $project = null; } if (!$project) { // This project legitimately does not exist, so just 404 the user. if (!$policy_exception) { return new Aphront404Response(); } // Here, the project exists but the user can't see it. If they are // using a non-canonical slug to view the project, redirect to the // canonical slug. If they're already using the canonical slug, rethrow // the exception to give them the policy error. if ($is_abnormal) { return id(new AphrontRedirectResponse())->setURI($normal_uri); } else { throw $policy_exception; } } // The user can view the project, but is using a noncanonical slug. // Redirect to the canonical slug. $primary_slug = $project->getPrimarySlug(); if ($slug && ($slug !== $primary_slug)) { $primary_uri = "/tag/{$primary_slug}/"; return id(new AphrontRedirectResponse())->setURI($primary_uri); } $this->setProject($project); return null; } public function buildApplicationMenu() { $menu = $this->newApplicationMenu(); $profile_menu = $this->getProfileMenu(); if ($profile_menu) { $menu->setProfileMenu($profile_menu); } $menu->setSearchEngine(new PhabricatorProjectSearchEngine()); return $menu; } protected function getProfileMenu() { if (!$this->profileMenu) { $engine = $this->getProfileMenuEngine(); if ($engine) { $this->profileMenu = $engine->buildNavigation(); } } return $this->profileMenu; } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $project = $this->getProject(); if ($project) { $ancestors = $project->getAncestorProjects(); $ancestors = array_reverse($ancestors); $ancestors[] = $project; foreach ($ancestors as $ancestor) { $crumbs->addTextCrumb( $ancestor->getName(), $ancestor->getProfileURI() ); } } return $crumbs; } protected function getProfileMenuEngine() { if (!$this->profileMenuEngine) { $viewer = $this->getViewer(); $project = $this->getProject(); if ($project) { $engine = id(new PhabricatorProjectProfileMenuEngine()) ->setViewer($viewer) ->setController($this) ->setProfileObject($project); $this->profileMenuEngine = $engine; } } return $this->profileMenuEngine; } protected function setProfileMenuEngine( PhabricatorProjectProfileMenuEngine $engine) { $this->profileMenuEngine = $engine; return $this; } protected function newCardResponse($board_phid, $object_phid) { $viewer = $this->getViewer(); $request = $this->getRequest(); $visible_phids = $request->getStrList('visiblePHIDs'); if (!$visible_phids) { $visible_phids = array(); } return id(new PhabricatorBoardResponseEngine()) ->setViewer($viewer) ->setBoardPHID($board_phid) ->setObjectPHID($object_phid) ->setVisiblePHIDs($visible_phids) ->buildResponse(); } + public function renderHashtags(array $tags) { + $result = array(); + foreach ($tags as $key => $tag) { + $result[] = '#'.$tag; + } + return implode(', ', $result); + } + } diff --git a/src/applications/project/controller/PhabricatorProjectManageController.php b/src/applications/project/controller/PhabricatorProjectManageController.php index 17b9030658..2c76c63606 100644 --- a/src/applications/project/controller/PhabricatorProjectManageController.php +++ b/src/applications/project/controller/PhabricatorProjectManageController.php @@ -1,151 +1,156 @@ loadProject(); if ($response) { return $response; } $viewer = $request->getUser(); $project = $this->getProject(); $id = $project->getID(); $picture = $project->getProfileImageURI(); $header = id(new PHUIHeaderView()) ->setHeader(pht('Project History')) ->setUser($viewer) ->setPolicyObject($project); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } else { $header->setStatus('fa-ban', 'red', pht('Archived')); } $curtain = $this->buildCurtain($project); $properties = $this->buildPropertyListView($project); $timeline = $this->buildTransactionTimeline( $project, new PhabricatorProjectTransactionQuery()); $timeline->setShouldTerminate(true); $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::ITEM_MANAGE); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Manage')); $crumbs->setBorder(true); require_celerity_resource('project-view-css'); $manage = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->addPropertySection(pht('Details'), $properties) ->addClass('project-view-home') ->addClass('project-view-people-home') ->setMainColumn( array( $timeline, )); return $this->newPage() ->setNavigation($nav) ->setCrumbs($crumbs) ->setTitle( array( $project->getDisplayName(), pht('Manage'), )) ->appendChild( array( $manage, )); } private function buildCurtain(PhabricatorProject $project) { $viewer = $this->getViewer(); $id = $project->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); $curtain = $this->newCurtainView($project); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Details')) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI("edit/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Menu')) ->setIcon('fa-th-list') ->setHref($this->getApplicationURI("{$id}/item/configure/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Picture')) ->setIcon('fa-picture-o') ->setHref($this->getApplicationURI("picture/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); if ($project->isArchived()) { $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Activate Project')) ->setIcon('fa-check') ->setHref($this->getApplicationURI("archive/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(true)); } else { $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Archive Project')) ->setIcon('fa-ban') ->setHref($this->getApplicationURI("archive/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(true)); } return $curtain; } private function buildPropertyListView( PhabricatorProject $project) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer); $view->addProperty( pht('Looks Like'), $viewer->renderHandle($project->getPHID())->setAsTag(true)); + $slugs = $project->getSlugs(); + $tags = mpull($slugs, 'getSlug'); + + $view->addProperty( + pht('Hashtags'), + $this->renderHashtags($tags)); $field_list = PhabricatorCustomField::getObjectFields( $project, PhabricatorCustomField::ROLE_VIEW); $field_list->appendFieldsToPropertyList($project, $viewer, $view); return $view; } - } diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 32dbec3c0f..e47cc678bc 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -1,296 +1,296 @@ loadProject(); if ($response) { return $response; } $viewer = $request->getUser(); $project = $this->getProject(); $id = $project->getID(); $picture = $project->getProfileImageURI(); $icon = $project->getDisplayIconIcon(); $icon_name = $project->getDisplayIconName(); $tag = id(new PHUITagView()) ->setIcon($icon) ->setName($icon_name) ->addClass('project-view-header-tag') ->setType(PHUITagView::TYPE_SHADE); $header = id(new PHUIHeaderView()) ->setHeader(array($project->getDisplayName(), $tag)) ->setUser($viewer) ->setPolicyObject($project) ->setProfileHeader(true); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } else { $header->setStatus('fa-ban', 'red', pht('Archived')); } $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); if ($can_edit) { $header->setImageEditURL($this->getApplicationURI("picture/{$id}/")); } $properties = $this->buildPropertyListView($project); $watch_action = $this->renderWatchAction($project); $header->addActionLink($watch_action); $milestone_list = $this->buildMilestoneList($project); $subproject_list = $this->buildSubprojectList($project); $member_list = id(new PhabricatorProjectMemberListView()) ->setUser($viewer) ->setProject($project) ->setLimit(5) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setUserPHIDs($project->getMemberPHIDs()); $watcher_list = id(new PhabricatorProjectWatcherListView()) ->setUser($viewer) ->setProject($project) ->setLimit(5) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setUserPHIDs($project->getWatcherPHIDs()); $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::ITEM_PROFILE); $stories = id(new PhabricatorFeedQuery()) ->setViewer($viewer) ->setFilterPHIDs( array( $project->getPHID(), )) ->setLimit(50) ->execute(); $feed = $this->renderStories($stories); $feed = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Recent Activity')) ->addClass('project-view-feed') ->appendChild($feed); require_celerity_resource('project-view-css'); $home = id(new PHUITwoColumnView()) ->setHeader($header) ->addClass('project-view-home') ->addClass('project-view-people-home') ->setMainColumn( array( $properties, $feed, )) ->setSideColumn( array( $milestone_list, $subproject_list, $member_list, $watcher_list, )); $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); return $this->newPage() ->setNavigation($nav) ->setCrumbs($crumbs) ->setTitle($project->getDisplayName()) ->setPageObjectPHIDs(array($project->getPHID())) ->appendChild($home); } private function buildPropertyListView( PhabricatorProject $project) { $request = $this->getRequest(); $viewer = $request->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($project); $field_list = PhabricatorCustomField::getObjectFields( $project, PhabricatorCustomField::ROLE_VIEW); $field_list->appendFieldsToPropertyList($project, $viewer, $view); if (!$view->hasAnyProperties()) { return null; } $header = id(new PHUIHeaderView()) - ->setHeader(pht('Properties')); + ->setHeader(pht('Details')); $view = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($view) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addClass('project-view-properties'); return $view; } private function renderStories(array $stories) { assert_instances_of($stories, 'PhabricatorFeedStory'); $builder = new PhabricatorFeedBuilder($stories); $builder->setUser($this->getRequest()->getUser()); $builder->setShowHovercards(true); $view = $builder->buildView(); return $view; } private function renderWatchAction(PhabricatorProject $project) { $viewer = $this->getViewer(); $id = $project->getID(); if (!$viewer->isLoggedIn()) { $is_watcher = false; $is_ancestor = false; } else { $viewer_phid = $viewer->getPHID(); $is_watcher = $project->isUserWatcher($viewer_phid); $is_ancestor = $project->isUserAncestorWatcher($viewer_phid); } if ($is_ancestor && !$is_watcher) { $watch_icon = 'fa-eye'; $watch_text = pht('Watching Ancestor'); $watch_href = "/project/watch/{$id}/?via=profile"; $watch_disabled = true; } else if (!$is_watcher) { $watch_icon = 'fa-eye'; $watch_text = pht('Watch Project'); $watch_href = "/project/watch/{$id}/?via=profile"; $watch_disabled = false; } else { $watch_icon = 'fa-eye-slash'; $watch_text = pht('Unwatch Project'); $watch_href = "/project/unwatch/{$id}/?via=profile"; $watch_disabled = false; } $watch_icon = id(new PHUIIconView()) ->setIcon($watch_icon); return id(new PHUIButtonView()) ->setTag('a') ->setWorkflow(true) ->setIcon($watch_icon) ->setText($watch_text) ->setHref($watch_href) ->setDisabled($watch_disabled); } private function buildMilestoneList(PhabricatorProject $project) { if (!$project->getHasMilestones()) { return null; } $viewer = $this->getViewer(); $id = $project->getID(); $milestones = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) ->withIsMilestone(true) ->withStatuses( array( PhabricatorProjectStatus::STATUS_ACTIVE, )) ->setOrderVector(array('milestoneNumber', 'id')) ->execute(); if (!$milestones) { return null; } $milestone_list = id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($milestones) ->renderList(); $view_all = id(new PHUIButtonView()) ->setTag('a') ->setIcon( id(new PHUIIconView()) ->setIcon('fa-list-ul')) ->setText(pht('View All')) ->setHref("/project/subprojects/{$id}/"); $header = id(new PHUIHeaderView()) ->setHeader(pht('Milestones')) ->addActionLink($view_all); return id(new PHUIObjectBoxView()) ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($milestone_list); } private function buildSubprojectList(PhabricatorProject $project) { if (!$project->getHasSubprojects()) { return null; } $viewer = $this->getViewer(); $id = $project->getID(); $limit = 25; $subprojects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) ->withStatuses( array( PhabricatorProjectStatus::STATUS_ACTIVE, )) ->withIsMilestone(false) ->setLimit($limit) ->execute(); if (!$subprojects) { return null; } $subproject_list = id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($subprojects) ->renderList(); $view_all = id(new PHUIButtonView()) ->setTag('a') ->setIcon( id(new PHUIIconView()) ->setIcon('fa-list-ul')) ->setText(pht('View All')) ->setHref("/project/subprojects/{$id}/"); $header = id(new PHUIHeaderView()) ->setHeader(pht('Subprojects')) ->addActionLink($view_all); return id(new PHUIObjectBoxView()) ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($subproject_list); } }