diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index ac634b9ccc..a0508de6c3 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -1,129 +1,130 @@ <?php final class DrydockLeaseViewController extends DrydockController { private $id; public function willProcessRequest(array $data) { $this->id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $lease = id(new DrydockLease())->load($this->id); if (!$lease) { return new Aphront404Response(); } $lease_uri = $this->getApplicationURI('lease/'.$lease->getID().'/'); $title = pht('Lease %d', $lease->getID()); $header = id(new PhabricatorHeaderView()) ->setHeader($title); $actions = $this->buildActionListView($lease); $properties = $this->buildPropertyListView($lease); $pager = new AphrontPagerView(); $pager->setURI(new PhutilURI($lease_uri), 'offset'); $pager->setOffset($request->getInt('offset')); $logs = id(new DrydockLogQuery()) ->withLeaseIDs(array($lease->getID())) ->executeWithOffsetPager($pager); $log_table = $this->buildLogTableView($logs); $log_table->appendChild($pager); $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setActionList($actions); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName($title) ->setHref($lease_uri)); return $this->buildApplicationPage( array( $crumbs, $header, $actions, $properties, $log_table, ), array( 'device' => true, 'title' => $title, )); } private function buildActionListView(DrydockLease $lease) { $view = id(new PhabricatorActionListView()) ->setUser($this->getRequest()->getUser()) ->setObject($lease); $id = $lease->getID(); $can_release = ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Release Lease')) ->setIcon('delete') ->setHref($this->getApplicationURI("/lease/{$id}/release/")) ->setWorkflow(true) ->setDisabled(!$can_release)); return $view; } private function buildPropertyListView(DrydockLease $lease) { $view = new PhabricatorPropertyListView(); switch ($lease->getStatus()) { case DrydockLeaseStatus::STATUS_ACTIVE: $status = pht('Active'); break; case DrydockLeaseStatus::STATUS_RELEASED: $status = pht('Released'); break; case DrydockLeaseStatus::STATUS_EXPIRED: $status = pht('Expired'); break; case DrydockLeaseStatus::STATUS_PENDING: $status = pht('Pending'); break; case DrydockLeaseStatus::STATUS_BROKEN: $status = pht('Broken'); break; default: $status = pht('Unknown'); break; } $view->addProperty( pht('Status'), $status); $view->addProperty( pht('Resource Type'), $lease->getResourceType()); $view->addProperty( pht('Resource'), $lease->getResourceID()); $attributes = $lease->getAttributes(); if ($attributes) { $view->addSectionHeader(pht('Attributes')); foreach ($attributes as $key => $value) { $view->addProperty($key, $value); } } return $view; } } diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 33dcf38514..45ed050c58 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -1,120 +1,121 @@ <?php final class DrydockResourceViewController extends DrydockController { private $id; public function willProcessRequest(array $data) { $this->id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $resource = id(new DrydockResource())->load($this->id); if (!$resource) { return new Aphront404Response(); } $title = 'Resource '.$resource->getID().' '.$resource->getName(); $header = id(new PhabricatorHeaderView()) ->setHeader($title); $actions = $this->buildActionListView($resource); $properties = $this->buildPropertyListView($resource); $resource_uri = 'resource/'.$resource->getID().'/'; $resource_uri = $this->getApplicationURI($resource_uri); $leases = id(new DrydockLeaseQuery()) ->withResourceIDs(array($resource->getID())) ->needResources(true) ->execute(); $lease_header = id(new PhabricatorHeaderView()) ->setHeader(pht('Leases')); $lease_list = $this->buildLeaseListView($leases); $lease_list->setNoDataString(pht('This resource has no leases.')); $pager = new AphrontPagerView(); $pager->setURI(new PhutilURI($resource_uri), 'offset'); $pager->setOffset($request->getInt('offset')); $logs = id(new DrydockLogQuery()) ->withResourceIDs(array($resource->getID())) ->executeWithOffsetPager($pager); $log_table = $this->buildLogTableView($logs); $log_table->appendChild($pager); $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setActionList($actions); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Resource %d', $resource->getID()))); return $this->buildApplicationPage( array( $crumbs, $header, $actions, $properties, $lease_header, $lease_list, $log_table, ), array( 'device' => true, 'title' => $title, )); } private function buildActionListView(DrydockResource $resource) { $view = id(new PhabricatorActionListView()) ->setUser($this->getRequest()->getUser()) ->setObject($resource); $can_close = ($resource->getStatus() == DrydockResourceStatus::STATUS_OPEN); $uri = '/resource/'.$resource->getID().'/close/'; $uri = $this->getApplicationURI($uri); $view->addAction( id(new PhabricatorActionView()) ->setHref($uri) ->setName(pht('Close Resource')) ->setIcon('delete') ->setWorkflow(true) ->setDisabled(!$can_close)); return $view; } private function buildPropertyListView(DrydockResource $resource) { $view = new PhabricatorPropertyListView(); $status = $resource->getStatus(); $status = DrydockResourceStatus::getNameForStatus($status); $view->addProperty( pht('Status'), $status); $view->addProperty( pht('Resource Type'), $resource->getType()); $attributes = $resource->getAttributes(); if ($attributes) { $view->addSectionHeader(pht('Attributes')); foreach ($attributes as $key => $value) { $view->addProperty($key, $value); } } return $view; } } diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index 14957f0314..20126841b4 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -1,169 +1,168 @@ <?php final class PhabricatorFileInfoController extends PhabricatorFileController { private $phid; public function willProcessRequest(array $data) { $this->phid = $data['phid']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $file = id(new PhabricatorFileQuery()) ->setViewer($user) ->withPHIDs(array($this->phid)) ->executeOne(); if (!$file) { return new Aphront404Response(); } $this->loadHandles(array($file->getAuthorPHID())); - $phid = $file->getPHID(); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addCrumb( - id(new PhabricatorCrumbView()) - ->setName('F'.$file->getID()) - ->setHref($this->getApplicationURI("/info/{$phid}/"))); - $header = id(new PhabricatorHeaderView()) ->setHeader($file->getName()); $ttl = $file->getTTL(); if ($ttl !== null) { $ttl_tag = id(new PhabricatorTagView()) ->setType(PhabricatorTagView::TYPE_OBJECT) ->setName(pht("Temporary")); $header->addTag($ttl_tag); } $actions = $this->buildActionView($file); $properties = $this->buildPropertyView($file); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setActionList($actions); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName('F'.$file->getID()) + ->setHref($this->getApplicationURI("/info/{$phid}/"))); + return $this->buildApplicationPage( array( $crumbs, $header, $actions, $properties, ), array( 'title' => $file->getName(), 'device' => true, )); } private function buildActionView(PhabricatorFile $file) { $request = $this->getRequest(); $user = $request->getUser(); $id = $file->getID(); $view = id(new PhabricatorActionListView()) ->setUser($user) ->setObject($file); if ($file->isViewableInBrowser()) { $view->addAction( id(new PhabricatorActionView()) ->setName(pht('View File')) ->setIcon('preview') ->setHref($file->getViewURI())); } else { $view->addAction( id(new PhabricatorActionView()) ->setUser($user) ->setRenderAsForm(true) ->setDownload(true) ->setName(pht('Download File')) ->setIcon('download') ->setHref($file->getViewURI())); } $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete File')) ->setIcon('delete') ->setHref($this->getApplicationURI("/delete/{$id}/")) ->setWorkflow(true)); return $view; } private function buildPropertyView(PhabricatorFile $file) { $request = $this->getRequest(); $user = $request->getUser(); $view = id(new PhabricatorPropertyListView()); if ($file->getAuthorPHID()) { $view->addProperty( pht('Author'), $this->getHandle($file->getAuthorPHID())->renderLink()); } $view->addProperty( pht('Created'), phabricator_datetime($file->getDateCreated(), $user)); $view->addProperty( pht('Size'), phabricator_format_bytes($file->getByteSize())); $view->addSectionHeader(pht('Technical Details')); $view->addProperty( pht('Mime Type'), $file->getMimeType()); $view->addProperty( pht('Engine'), $file->getStorageEngine()); $view->addProperty( pht('Format'), $file->getStorageFormat()); $view->addProperty( pht('Handle'), $file->getStorageHandle()); $metadata = $file->getMetadata(); if (!empty($metadata)) { $view->addSectionHeader(pht('Metadata')); foreach ($metadata as $key => $value) { $view->addProperty( PhabricatorFile::getMetadataName($key), $value); } } if ($file->isViewableImage()) { $image = phutil_tag( 'img', array( 'src' => $file->getViewURI(), 'class' => 'phabricator-property-list-image', )); $linked_image = phutil_tag( 'a', array( 'href' => $file->getViewURI(), ), $image); $view->addImageContent($linked_image); } return $view; } } diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index 4b9e9b569b..15c6d52163 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -1,173 +1,174 @@ <?php final class PhabricatorMacroViewController extends PhabricatorMacroController { private $id; public function willProcessRequest(array $data) { $this->id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $macro = id(new PhabricatorMacroQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->executeOne(); if (!$macro) { return new Aphront404Response(); } $file = $macro->getFile(); $title_short = pht('Macro "%s"', $macro->getName()); $title_long = pht('Image Macro "%s"', $macro->getName()); $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $macro->getPHID()); $this->loadHandles($subscribers); + $actions = $this->buildActionView($macro); $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setActionList($actions); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setHref($this->getApplicationURI('/view/'.$macro->getID().'/')) ->setName($title_short)); - $actions = $this->buildActionView($macro); $properties = $this->buildPropertyView($macro, $file, $subscribers); $xactions = id(new PhabricatorMacroTransactionQuery()) ->setViewer($request->getUser()) ->withObjectPHIDs(array($macro->getPHID())) ->execute(); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($user); foreach ($xactions as $xaction) { if ($xaction->getComment()) { $engine->addObject( $xaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } } $engine->process(); $timeline = id(new PhabricatorApplicationTransactionView()) ->setUser($user) ->setTransactions($xactions) ->setMarkupEngine($engine); $header = id(new PhabricatorHeaderView()) ->setHeader($title_long); if ($macro->getIsDisabled()) { $header->addTag( id(new PhabricatorTagView()) ->setType(PhabricatorTagView::TYPE_STATE) ->setName(pht('Macro Disabled')) ->setBackgroundColor(PhabricatorTagView::COLOR_BLACK)); } $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $add_comment_header = id(new PhabricatorHeaderView()) ->setHeader( $is_serious ? pht('Add Comment') : pht('Grovel in Awe')); $submit_button_name = $is_serious ? pht('Add Comment') : pht('Lavish Praise'); $draft = PhabricatorDraft::newFromUserAndKey($user, $macro->getPHID()); $add_comment_form = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($user) ->setDraft($draft) ->setAction($this->getApplicationURI('/comment/'.$macro->getID().'/')) ->setSubmitButtonName($submit_button_name); return $this->buildApplicationPage( array( $crumbs, $header, $actions, $properties, $timeline, $add_comment_header, $add_comment_form, ), array( 'title' => $title_short, )); } private function buildActionView(PhabricatorFileImageMacro $macro) { $view = new PhabricatorActionListView(); $view->setUser($this->getRequest()->getUser()); $view->setObject($macro); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Macro')) ->setHref($this->getApplicationURI('/edit/'.$macro->getID().'/')) ->setIcon('edit')); if ($macro->getIsDisabled()) { $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Restore Macro')) ->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/')) ->setWorkflow(true) ->setIcon('undo')); } else { $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Disable Macro')) ->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/')) ->setWorkflow(true) ->setIcon('delete')); } return $view; } private function buildPropertyView( PhabricatorFileImageMacro $macro, PhabricatorFile $file = null, array $subscribers) { $view = new PhabricatorPropertyListView(); if ($subscribers) { $sub_view = array(); foreach ($subscribers as $subscriber) { $sub_view[] = $this->getHandle($subscriber)->renderLink(); } $sub_view = phutil_implode_html(', ', $sub_view); } else { $sub_view = phutil_tag('em', array(), pht('None')); } $view->addProperty( pht('Subscribers'), $sub_view); if ($file) { $view->addImageContent( phutil_tag( 'img', array( 'src' => $file->getViewURI(), 'class' => 'phabricator-image-macro-hero', ))); } return $view; } } diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index 2a12939c4e..85db9938df 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -1,155 +1,156 @@ <?php final class PhabricatorPasteViewController extends PhabricatorPasteController { public function shouldAllowPublic() { return true; } private $id; private $handles; public function willProcessRequest(array $data) { $this->id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $paste = id(new PhabricatorPasteQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->needContent(true) ->executeOne(); if (!$paste) { return new Aphront404Response(); } $file = id(new PhabricatorFile())->loadOneWhere( 'phid = %s', $paste->getFilePHID()); if (!$file) { return new Aphront400Response(); } $forks = id(new PhabricatorPasteQuery()) ->setViewer($user) ->withParentPHIDs(array($paste->getPHID())) ->execute(); $fork_phids = mpull($forks, 'getPHID'); $this->loadHandles( array_merge( array( $paste->getAuthorPHID(), $paste->getParentPHID(), ), $fork_phids)); $header = $this->buildHeaderView($paste); $actions = $this->buildActionView($user, $paste, $file); $properties = $this->buildPropertyView($paste, $fork_phids); $source_code = $this->buildSourceCodeView($paste); $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()) + ->setActionList($actions) ->addCrumb( id(new PhabricatorCrumbView()) ->setName('P'.$paste->getID()) ->setHref('/P'.$paste->getID())); return $this->buildApplicationPage( array( $crumbs, $header, $actions, $properties, $source_code, ), array( 'title' => $paste->getFullName(), 'device' => true, )); } private function buildHeaderView(PhabricatorPaste $paste) { return id(new PhabricatorHeaderView()) ->setHeader($paste->getTitle()); } private function buildActionView( PhabricatorUser $user, PhabricatorPaste $paste, PhabricatorFile $file) { $can_edit = PhabricatorPolicyFilter::hasCapability( $user, $paste, PhabricatorPolicyCapability::CAN_EDIT); $can_fork = $user->isLoggedIn(); $fork_uri = $this->getApplicationURI('/create/?parent='.$paste->getID()); return id(new PhabricatorActionListView()) ->setUser($user) ->setObject($paste) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Fork This Paste')) ->setIcon('fork') ->setDisabled(!$can_fork) ->setWorkflow(!$can_fork) ->setHref($fork_uri)) ->addAction( id(new PhabricatorActionView()) ->setName(pht('View Raw File')) ->setIcon('file') ->setHref($file->getBestURI())) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Paste')) ->setIcon('edit') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setHref($this->getApplicationURI('/edit/'.$paste->getID().'/'))); } private function buildPropertyView( PhabricatorPaste $paste, array $child_phids) { $user = $this->getRequest()->getUser(); $properties = new PhabricatorPropertyListView(); $properties->addProperty( pht('Author'), $this->getHandle($paste->getAuthorPHID())->renderLink()); $properties->addProperty( pht('Created'), phabricator_datetime($paste->getDateCreated(), $user)); if ($paste->getParentPHID()) { $properties->addProperty( pht('Forked From'), $this->getHandle($paste->getParentPHID())->renderLink()); } if ($child_phids) { $properties->addProperty( pht('Forks'), $this->renderHandlesForPHIDs($child_phids)); } $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $user, $paste); $properties->addProperty( pht('Visible To'), $descriptions[PhabricatorPolicyCapability::CAN_VIEW]); return $properties; } } diff --git a/src/applications/pholio/controller/PholioMockViewController.php b/src/applications/pholio/controller/PholioMockViewController.php index d64e5b4efb..b34d48087e 100644 --- a/src/applications/pholio/controller/PholioMockViewController.php +++ b/src/applications/pholio/controller/PholioMockViewController.php @@ -1,219 +1,220 @@ <?php /** * @group pholio */ final class PholioMockViewController extends PholioController { private $id; private $imageID; public function shouldAllowPublic() { return true; } public function willProcessRequest(array $data) { $this->id = $data['id']; $this->imageID = idx($data, 'imageID'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $mock = id(new PholioMockQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->needImages(true) ->needCoverFiles(true) ->executeOne(); if (!$mock) { return new Aphront404Response(); } $xactions = id(new PholioTransactionQuery()) ->setViewer($user) ->withObjectPHIDs(array($mock->getPHID())) ->execute(); $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $mock->getPHID()); $phids = array(); $phids[] = $mock->getAuthorPHID(); foreach ($subscribers as $subscriber) { $phids[] = $subscriber; } $this->loadHandles($phids); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($user); $engine->addObject($mock, PholioMock::MARKUP_FIELD_DESCRIPTION); foreach ($xactions as $xaction) { if ($xaction->getComment()) { $engine->addObject( $xaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } } $engine->process(); $title = $mock->getName(); $header = id(new PhabricatorHeaderView()) ->setHeader($title); $actions = $this->buildActionView($mock); $properties = $this->buildPropertyView($mock, $engine, $subscribers); require_celerity_resource('pholio-css'); require_celerity_resource('pholio-inline-comments-css'); $output = id(new PholioMockImagesView()) ->setRequestURI($request->getRequestURI()) ->setUser($user) ->setMock($mock) ->setImageID($this->imageID); $xaction_view = id(new PholioTransactionView()) ->setUser($this->getRequest()->getUser()) ->setTransactions($xactions) ->setMarkupEngine($engine); $add_comment = $this->buildAddCommentView($mock); $crumbs = $this->buildApplicationCrumbs($this->buildSideNav()); + $crumbs->setActionList($actions); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName('M'.$mock->getID()) ->setHref('/M'.$mock->getID())); $content = array( $crumbs, $header, $actions, $properties, $output->render(), $xaction_view, $add_comment, ); PhabricatorFeedStoryNotification::updateObjectNotificationViews( $user, $mock->getPHID()); return $this->buildApplicationPage( $content, array( 'title' => 'M'.$mock->getID().' '.$title, 'device' => true, 'pageObjects' => array($mock->getPHID()), )); } private function buildActionView(PholioMock $mock) { $user = $this->getRequest()->getUser(); $actions = id(new PhabricatorActionListView()) ->setUser($user) ->setObject($mock); $can_edit = PhabricatorPolicyFilter::hasCapability( $user, $mock, PhabricatorPolicyCapability::CAN_EDIT); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('edit') ->setName(pht('Edit Mock')) ->setHref($this->getApplicationURI('/edit/'.$mock->getID().'/')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); return $actions; } private function buildPropertyView( PholioMock $mock, PhabricatorMarkupEngine $engine, array $subscribers) { $user = $this->getRequest()->getUser(); $properties = id(new PhabricatorPropertyListView()) ->setUser($user) ->setObject($mock); $properties->addProperty( pht('Author'), $this->getHandle($mock->getAuthorPHID())->renderLink()); $properties->addProperty( pht('Created'), phabricator_datetime($mock->getDateCreated(), $user)); $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $user, $mock); $properties->addProperty( pht('Visible To'), $descriptions[PhabricatorPolicyCapability::CAN_VIEW]); if ($subscribers) { $sub_view = array(); foreach ($subscribers as $subscriber) { $sub_view[] = $this->getHandle($subscriber)->renderLink(); } $sub_view = phutil_implode_html(', ', $sub_view); } else { $sub_view = phutil_tag('em', array(), pht('None')); } $properties->addProperty( pht('Subscribers'), $sub_view); $properties->invokeWillRenderEvent(); $properties->addImageContent( $engine->getOutput($mock, PholioMock::MARKUP_FIELD_DESCRIPTION)); return $properties; } private function buildAddCommentView(PholioMock $mock) { $user = $this->getRequest()->getUser(); $draft = PhabricatorDraft::newFromUserAndKey($user, $mock->getPHID()); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $title = $is_serious ? pht('Add Comment') : pht('History Beckons'); $header = id(new PhabricatorHeaderView()) ->setHeader($title); $button_name = $is_serious ? pht('Add Comment') : pht('Answer The Call'); $form = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($user) ->setDraft($draft) ->setSubmitButtonName($button_name) ->setAction($this->getApplicationURI('/comment/'.$mock->getID().'/')) ->setRequestURI($this->getRequest()->getRequestURI()); return array( $header, $form, ); } } diff --git a/src/applications/phortune/controller/PhortuneAccountViewController.php b/src/applications/phortune/controller/PhortuneAccountViewController.php index 41ece1b6d7..63cad02280 100644 --- a/src/applications/phortune/controller/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/PhortuneAccountViewController.php @@ -1,180 +1,182 @@ <?php final class PhortuneAccountViewController extends PhortuneController { private $accountID; public function willProcessRequest(array $data) { $this->accountID = $data['accountID']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $account = id(new PhortuneAccountQuery()) ->setViewer($user) ->withIDs(array($this->accountID)) ->executeOne(); if (!$account) { return new Aphront404Response(); } $title = $account->getName(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Account')) ->setHref($request->getRequestURI())); $header = id(new PhabricatorHeaderView()) ->setHeader($title); $actions = id(new PhabricatorActionListView()) ->setUser($user) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Account')) ->setIcon('edit') ->setHref('#') ->setDisabled(true)) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Members')) ->setIcon('transcript') ->setHref('#') ->setDisabled(true)); + $crumbs->setActionList($actions); + $properties = id(new PhabricatorPropertyListView()) ->setObject($account) ->setUser($user); $properties->addProperty(pht('Balance'), $account->getBalanceInCents()); $payment_methods = $this->buildPaymentMethodsSection($account); $purchase_history = $this->buildPurchaseHistorySection($account); $account_history = $this->buildAccountHistorySection($account); return $this->buildApplicationPage( array( $crumbs, $header, $actions, $properties, $payment_methods, $purchase_history, $account_history, ), array( 'title' => $title, 'device' => true, 'dust' => true, )); } private function buildPaymentMethodsSection(PhortuneAccount $account) { $request = $this->getRequest(); $user = $request->getUser(); $header = id(new PhabricatorHeaderView()) ->setHeader(pht('Payment Methods')); $id = $account->getID(); $add_uri = $this->getApplicationURI($id.'/paymentmethod/edit/'); $actions = id(new PhabricatorActionListView()) ->setUser($user) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Add Payment Method')) ->setIcon('new') ->setHref($add_uri)); $list = id(new PhabricatorObjectItemListView()) ->setUser($user) ->setNoDataString( pht('No payment methods associated with this account.')); $methods = id(new PhortunePaymentMethodQuery()) ->setViewer($user) ->withAccountPHIDs(array($account->getPHID())) ->withStatus(PhortunePaymentMethodQuery::STATUS_OPEN) ->execute(); if ($methods) { $this->loadHandles(mpull($methods, 'getAuthorPHID')); } foreach ($methods as $method) { $item = new PhabricatorObjectItemView(); $item->setHeader($method->getName()); switch ($method->getStatus()) { case PhortunePaymentMethod::STATUS_ACTIVE: $item->addAttribute(pht('Active')); $item->setBarColor('green'); break; } $item->addAttribute( pht( 'Added %s by %s', phabricator_datetime($method->getDateCreated(), $user), $this->getHandle($method->getAuthorPHID())->renderLink())); if ($method->getExpiresEpoch() < time() + (60 * 60 * 24 * 30)) { $item->addAttribute(pht('Expires Soon!')); } $list->addItem($item); } return array( $header, $actions, $list, ); } private function buildPurchaseHistorySection(PhortuneAccount $account) { $request = $this->getRequest(); $user = $request->getUser(); $header = id(new PhabricatorHeaderView()) ->setHeader(pht('Purchase History')); return array( $header, ); } private function buildAccountHistorySection(PhortuneAccount $account) { $request = $this->getRequest(); $user = $request->getUser(); $header = id(new PhabricatorHeaderView()) ->setHeader(pht('Account History')); $xactions = id(new PhortuneAccountTransactionQuery()) ->setViewer($user) ->withObjectPHIDs(array($account->getPHID())) ->execute(); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($user); $xaction_view = id(new PhabricatorApplicationTransactionView()) ->setUser($user) ->setTransactions($xactions) ->setMarkupEngine($engine); return array( $header, $xaction_view, ); } } diff --git a/src/applications/phortune/controller/PhortuneProductViewController.php b/src/applications/phortune/controller/PhortuneProductViewController.php index 907ea25802..dbabf8303d 100644 --- a/src/applications/phortune/controller/PhortuneProductViewController.php +++ b/src/applications/phortune/controller/PhortuneProductViewController.php @@ -1,84 +1,85 @@ <?php final class PhortuneProductViewController extends PhabricatorController { private $productID; public function willProcessRequest(array $data) { $this->productID = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $product = id(new PhortuneProductQuery()) ->setViewer($user) ->withIDs(array($this->productID)) ->executeOne(); if (!$product) { return new Aphront404Response(); } $title = pht('Product: %s', $product->getProductName()); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addCrumb( - id(new PhabricatorCrumbView()) - ->setName(pht('Products')) - ->setHref($this->getApplicationURI('product/'))); - $crumbs->addCrumb( - id(new PhabricatorCrumbView()) - ->setName(pht('#%d', $product->getID())) - ->setHref($request->getRequestURI())); - $header = id(new PhabricatorHeaderView()) ->setHeader($product->getProductName()); $edit_uri = $this->getApplicationURI('product/edit/'.$product->getID().'/'); $actions = id(new PhabricatorActionListView()) ->setUser($user) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Product')) ->setHref($edit_uri) ->setIcon('edit')); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setActionList($actions); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Products')) + ->setHref($this->getApplicationURI('product/'))); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('#%d', $product->getID())) + ->setHref($request->getRequestURI())); + $properties = id(new PhabricatorPropertyListView()) ->setUser($user) ->addProperty(pht('Type'), $product->getTypeName()) ->addProperty( pht('Price'), PhortuneUtil::formatCurrency($product->getPriceInCents())); $xactions = id(new PhortuneProductTransactionQuery()) ->setViewer($user) ->withObjectPHIDs(array($product->getPHID())) ->execute(); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($user); $xaction_view = id(new PhabricatorApplicationTransactionView()) ->setUser($user) ->setTransactions($xactions) ->setMarkupEngine($engine); return $this->buildApplicationPage( array( $crumbs, $header, $actions, $properties, $xaction_view, ), array( 'title' => $title, 'device' => true, 'dust' => true, )); } } diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php index 3c206e0527..633c87638b 100644 --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -1,441 +1,442 @@ <?php /** * @group phriction */ final class PhrictionDocumentController extends PhrictionController { private $slug; public function willProcessRequest(array $data) { $this->slug = $data['slug']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $slug = PhabricatorSlug::normalize($this->slug); if ($slug != $this->slug) { $uri = PhrictionDocument::getSlugURI($slug); // Canonicalize pages to their one true URI. return id(new AphrontRedirectResponse())->setURI($uri); } require_celerity_resource('phriction-document-css'); $document = id(new PhrictionDocument())->loadOneWhere( 'slug = %s', $slug); $version_note = null; if (!$document) { $document = new PhrictionDocument(); if (PhrictionDocument::isProjectSlug($slug)) { $project = id(new PhabricatorProject())->loadOneWhere( 'phrictionSlug = %s', PhrictionDocument::getProjectSlugIdentifier($slug)); if (!$project) { return new Aphront404Response(); } } $create_uri = '/phriction/edit/?slug='.$slug; $no_content_head = pht('No content here!'); $no_content_body = pht( 'No document found at %s. You can <strong>'. '<a href="%s">create a new document here</a></strong>.', phutil_tag('tt', array(), $slug), $create_uri); $no_content_text = hsprintf( '<em>%s</em><br />%s', $no_content_head, $no_content_body); $page_content = phutil_tag( 'div', array('class' => 'phriction-content'), $no_content_text); $page_title = pht('Page Not Found'); } else { $version = $request->getInt('v'); if ($version) { $content = id(new PhrictionContent())->loadOneWhere( 'documentID = %d AND version = %d', $document->getID(), $version); if (!$content) { return new Aphront404Response(); } if ($content->getID() != $document->getContentID()) { $vdate = phabricator_datetime($content->getDateCreated(), $user); $version_note = new AphrontErrorView(); $version_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $version_note->setTitle('Older Version'); $version_note->appendChild( pht('You are viewing an older version of this document, as it '. 'appeared on %s.', $vdate)); } } else { $content = id(new PhrictionContent())->load($document->getContentID()); } $page_title = $content->getTitle(); $project_phid = null; if (PhrictionDocument::isProjectSlug($slug)) { $project = id(new PhabricatorProject())->loadOneWhere( 'phrictionSlug = %s', PhrictionDocument::getProjectSlugIdentifier($slug)); if ($project) { $project_phid = $project->getPHID(); } } $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $document->getPHID()); $phids = array_filter( array( $content->getAuthorPHID(), $project_phid, )); if ($subscribers) { $phids = array_merge($phids, $subscribers); } $handles = $this->loadViewerHandles($phids); $age = time() - $content->getDateCreated(); $age = floor($age / (60 * 60 * 24)); if ($age < 1) { $when = pht('today'); } else if ($age == 1) { $when = pht('yesterday'); } else { $when = pht("%d days ago", $age); } $project_info = null; if ($project_phid) { $project_info = hsprintf( '<br />%s', pht('This document is about the project %s.', $handles[$project_phid]->renderLink())); } $subscriber_view = null; if ($subscribers) { $subcriber_list = array(); foreach ($subscribers as $subscriber) { $subcriber_list[] = $handles[$subscriber]; } $subcriber_list = phutil_implode_html(', ', mpull($subcriber_list, 'renderLink')); $subscriber_view = array( hsprintf('<br />Subscribers: '), $subcriber_list, ); } $byline = hsprintf( '<div class="phriction-byline">%s%s%s</div>', pht('Last updated %s by %s.', $when, $handles[$content->getAuthorPHID()]->renderLink()), $project_info, $subscriber_view); $doc_status = $document->getStatus(); $current_status = $content->getChangeType(); if ($current_status == PhrictionChangeType::CHANGE_EDIT || $current_status == PhrictionChangeType::CHANGE_MOVE_HERE) { $core_content = $content->renderContent($user); } else if ($current_status == PhrictionChangeType::CHANGE_DELETE) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle(pht('Document Deleted')); $notice->appendChild( pht('This document has been deleted. You can edit it to put new '. 'content here, or use history to revert to an earlier version.')); $core_content = $notice->render(); } else if ($current_status == PhrictionChangeType::CHANGE_STUB) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle(pht('Empty Document')); $notice->appendChild( pht('This document is empty. You can edit it to put some proper '. 'content here.')); $core_content = $notice->render(); } else if ($current_status == PhrictionChangeType::CHANGE_MOVE_AWAY) { $new_doc_id = $content->getChangeRef(); $new_doc = new PhrictionDocument(); $new_doc->load($new_doc_id); $slug_uri = PhrictionDocument::getSlugURI($new_doc->getSlug()); $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle(pht('Document Moved')); $notice->appendChild(phutil_tag('p', array(), pht('This document has been moved to %s. You can edit it to put new '. 'content here, or use history to revert to an earlier version.', phutil_tag('a', array('href' => $slug_uri), $slug_uri)))); $core_content = $notice->render(); } else { throw new Exception("Unknown document status '{$doc_status}'!"); } $move_notice = null; if ($current_status == PhrictionChangeType::CHANGE_MOVE_HERE) { $from_doc_id = $content->getChangeRef(); $from_doc = id(new PhrictionDocument())->load($from_doc_id); $slug_uri = PhrictionDocument::getSlugURI($from_doc->getSlug()); $move_notice = id(new AphrontErrorView()) ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) ->appendChild(pht('This document was moved from %s', phutil_tag('a', array('href' => $slug_uri), $slug_uri))) ->render(); } $page_content = hsprintf( '<div class="phriction-content">%s%s%s</div>', $byline, $move_notice, $core_content); } if ($version_note) { $version_note = $version_note->render(); } $children = $this->renderDocumentChildren($slug); + $actions = $this->buildActionView($user, $document); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setActionList($actions); $crumb_views = $this->renderBreadcrumbs($slug); foreach ($crumb_views as $view) { $crumbs->addCrumb($view); } - $actions = $this->buildActionView($user, $document); - $header = id(new PhabricatorHeaderView()) ->setHeader($page_title); return $this->buildApplicationPage( array( $crumbs->render(), $header->render(), $actions->render(), $version_note, $page_content, $children, ), array( 'title' => $page_title, 'device' => true, )); } private function buildActionView( PhabricatorUser $user, PhrictionDocument $document) { $can_edit = PhabricatorPolicyFilter::hasCapability( $user, $document, PhabricatorPolicyCapability::CAN_EDIT); $slug = PhabricatorSlug::normalize($this->slug); $action_view = id(new PhabricatorActionListView()) ->setUser($user) ->setObject($document); if (!$document->getID()) { return $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('Create This Document')) ->setIcon('create') ->setHref('/phriction/edit/?slug='.$slug)); } $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Document')) ->setIcon('edit') ->setHref('/phriction/edit/'.$document->getID().'/')); if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) { $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('Move Document')) ->setIcon('move') ->setHref('/phriction/move/'.$document->getID().'/') ->setWorkflow(true)); $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Document')) ->setIcon('delete') ->setHref('/phriction/delete/'.$document->getID().'/') ->setWorkflow(true)); } return $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('View History')) ->setIcon('history') ->setHref(PhrictionDocument::getSlugURI($slug, 'history'))); } private function renderDocumentChildren($slug) { $document_dao = new PhrictionDocument(); $content_dao = new PhrictionContent(); $conn = $document_dao->establishConnection('r'); $limit = 50; $d_child = PhabricatorSlug::getDepth($slug) + 1; $d_grandchild = PhabricatorSlug::getDepth($slug) + 2; // Select children and grandchildren. $children = queryfx_all( $conn, 'SELECT d.slug, d.depth, c.title FROM %T d JOIN %T c ON d.contentID = c.id WHERE d.slug LIKE %> AND d.depth IN (%d, %d) AND d.status IN (%Ld) ORDER BY d.depth, c.title LIMIT %d', $document_dao->getTableName(), $content_dao->getTableName(), ($slug == '/' ? '' : $slug), $d_child, $d_grandchild, array( PhrictionDocumentStatus::STATUS_EXISTS, PhrictionDocumentStatus::STATUS_STUB, ), $limit); if (!$children) { return; } // We're going to render in one of three modes to try to accommodate // different information scales: // // - If we found fewer than $limit rows, we know we have all the children // and grandchildren and there aren't all that many. We can just render // everything. // - If we found $limit rows but the results included some grandchildren, // we just throw them out and render only the children, as we know we // have them all. // - If we found $limit rows and the results have no grandchildren, we // have a ton of children. Render them and then let the user know that // this is not an exhaustive list. if (count($children) == $limit) { $more_children = true; foreach ($children as $child) { if ($child['depth'] == $d_grandchild) { $more_children = false; } } $show_grandchildren = false; } else { $show_grandchildren = true; $more_children = false; } $grandchildren = array(); foreach ($children as $key => $child) { if ($child['depth'] == $d_child) { continue; } else { unset($children[$key]); if ($show_grandchildren) { $ancestors = PhabricatorSlug::getAncestry($child['slug']); $grandchildren[end($ancestors)][] = $child; } } } // Fill in any missing children. $known_slugs = ipull($children, null, 'slug'); foreach ($grandchildren as $slug => $ignored) { if (empty($known_slugs[$slug])) { $children[] = array( 'slug' => $slug, 'depth' => $d_child, 'title' => PhabricatorSlug::getDefaultTitle($slug), 'empty' => true, ); } } $children = isort($children, 'title'); $list = array(); foreach ($children as $child) { $list[] = hsprintf('<li>'); $list[] = $this->renderChildDocumentLink($child); $grand = idx($grandchildren, $child['slug'], array()); if ($grand) { $list[] = hsprintf('<ul>'); foreach ($grand as $grandchild) { $list[] = hsprintf('<li>'); $list[] = $this->renderChildDocumentLink($grandchild); $list[] = hsprintf('</li>'); } $list[] = hsprintf('</ul>'); } $list[] = hsprintf('</li>'); } if ($more_children) { $list[] = phutil_tag('li', array(), pht('More...')); } return hsprintf( '<div class="phriction-children">'. '<div class="phriction-children-header">%s</div>'. '%s'. '</div>', pht('Document Hierarchy'), phutil_tag('ul', array(), $list)); } private function renderChildDocumentLink(array $info) { $title = nonempty($info['title'], pht('(Untitled Document)')); $item = phutil_tag( 'a', array( 'href' => PhrictionDocument::getSlugURI($info['slug']), ), $title); if (isset($info['empty'])) { $item = phutil_tag('em', array(), $item); } return $item; } protected function getDocumentSlug() { return $this->slug; } } diff --git a/src/applications/ponder/controller/PonderController.php b/src/applications/ponder/controller/PonderController.php index 7b29d98955..037cf0bcbd 100644 --- a/src/applications/ponder/controller/PonderController.php +++ b/src/applications/ponder/controller/PonderController.php @@ -1,43 +1,43 @@ <?php abstract class PonderController extends PhabricatorController { public function buildStandardPageResponse($view, array $data) { $page = $this->buildStandardPageView(); - $page->setApplicationName('Ponder!'); + $page->setApplicationName(pht('Ponder!')); $page->setBaseURI('/ponder/'); $page->setTitle(idx($data, 'title')); $page->setGlyph("\xE2\x97\xB3"); $page->appendChild($view); $page->setSearchDefaultScope(PhabricatorSearchScope::SCOPE_QUESTIONS); $response = new AphrontWebpageResponse(); return $response->setContent($page->render()); } protected function buildSideNavView(PonderQuestion $question = null) { $side_nav = new AphrontSideNavFilterView(); $side_nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - if ($question && $question->getID()) { - $side_nav->addFilter( - null, - 'Q'.$question->getID(), - 'Q'.$question->getID()); - } + $side_nav->addLabel(pht('Questions')); + $side_nav->addFilter('feed', pht('All Questions')); + $side_nav->addFilter('questions', pht('Your Questions')); + $side_nav->addFilter('answers', pht('Your Answers')); - $side_nav->addLabel('Create'); - $side_nav->addFilter('question/ask', 'Ask a Question'); - - $side_nav->addLabel('Questions'); - $side_nav->addFilter('feed', 'All Questions'); + return $side_nav; + } - $side_nav->addLabel('User'); - $side_nav->addFilter('questions', 'Your Questions'); - $side_nav->addFilter('answers', 'Your Answers'); + public function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + $crumbs + ->addAction( + id(new PhabricatorMenuItemView()) + ->setName(pht('New Question')) + ->setHref('question/ask') + ->setIcon('create')); - return $side_nav; + return $crumbs; } } diff --git a/src/applications/ponder/controller/PonderFeedController.php b/src/applications/ponder/controller/PonderFeedController.php index e3f468f500..e669e05a54 100644 --- a/src/applications/ponder/controller/PonderFeedController.php +++ b/src/applications/ponder/controller/PonderFeedController.php @@ -1,115 +1,116 @@ <?php final class PonderFeedController extends PonderController { private $page; private $answerOffset; const PROFILE_ANSWER_PAGE_SIZE = 10; public function willProcessRequest(array $data) { $this->page = idx($data, 'page'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $this->answerOffset = $request->getInt('aoff'); $pages = array( 'feed' => 'All Questions', 'questions' => 'Your Questions', 'answers' => 'Your Answers', ); $side_nav = $this->buildSideNavView(); $this->page = $side_nav->selectFilter($this->page, 'feed'); $title = $pages[$this->page]; switch ($this->page) { case 'feed': case 'questions': $pager = new AphrontPagerView(); $pager->setOffset($request->getStr('offset')); $pager->setURI($request->getRequestURI(), 'offset'); $query = id(new PonderQuestionQuery()) ->setViewer($user); if ($this->page == 'feed') { $query ->setOrder(PonderQuestionQuery::ORDER_HOTTEST); } else { $query ->setOrder(PonderQuestionQuery::ORDER_CREATED) ->withAuthorPHIDs(array($user->getPHID())); } $questions = $query->executeWithOffsetPager($pager); $this->loadHandles(mpull($questions, 'getAuthorPHID')); $view = $this->buildQuestionListView($questions); $view->setPager($pager); $side_nav->appendChild( id(new PhabricatorHeaderView())->setHeader($title)); $side_nav->appendChild($view); break; case 'answers': $answers = PonderAnswerQuery::loadByAuthorWithQuestions( $user, $user->getPHID(), $this->answerOffset, self::PROFILE_ANSWER_PAGE_SIZE + 1); $side_nav->appendChild( id(new PonderUserProfileView()) ->setUser($user) ->setAnswers($answers) ->setAnswerOffset($this->answerOffset) ->setPageSize(self::PROFILE_ANSWER_PAGE_SIZE) ->setURI(new PhutilURI("/ponder/profile/"), "aoff")); break; } return $this->buildApplicationPage( $side_nav, array( 'device' => true, 'title' => $title, + 'dust' => true, )); } private function buildQuestionListView(array $questions) { assert_instances_of($questions, 'PonderQuestion'); $user = $this->getRequest()->getUser(); $view = new PhabricatorObjectItemListView(); $view->setUser($user); $view->setNoDataString(pht('No matching questions.')); foreach ($questions as $question) { $item = new PhabricatorObjectItemView(); $item->setObjectName('Q'.$question->getID()); $item->setHeader($question->getTitle()); $item->setHref('/Q'.$question->getID()); $item->setObject($question); $item->addAttribute( pht( 'Asked by %s on %s', $this->getHandle($question->getAuthorPHID())->renderLink(), phabricator_date($question->getDateCreated(), $user))); $item->addAttribute( pht('%d Answer(s)', $question->getAnswerCount())); $view->addItem($item); } return $view; } } diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 0526d30d7b..758a0a6673 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -1,129 +1,136 @@ <?php final class PonderQuestionViewController extends PonderController { private $questionID; public function willProcessRequest(array $data) { $this->questionID = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $question = PonderQuestionQuery::loadSingle($user, $this->questionID); if (!$question) { return new Aphront404Response(); } $question->attachRelated(); $question->attachVotes($user->getPHID()); $object_phids = array($user->getPHID(), $question->getAuthorPHID()); $answers = $question->getAnswers(); $comments = $question->getComments(); foreach ($comments as $comment) { $object_phids[] = $comment->getAuthorPHID(); } foreach ($answers as $answer) { $object_phids[] = $answer->getAuthorPHID(); $comments = $answer->getComments(); foreach ($comments as $comment) { $object_phids[] = $comment->getAuthorPHID(); } } $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $question->getPHID()); $object_phids = array_merge($object_phids, $subscribers); $this->loadHandles($object_phids); $handles = $this->getLoadedHandles(); $detail_panel = new PonderQuestionDetailView(); $detail_panel ->setQuestion($question) ->setUser($user) ->setHandles($handles); $responses_panel = new PonderAnswerListView(); $responses_panel ->setQuestion($question) ->setHandles($handles) ->setUser($user) ->setAnswers($answers); $answer_add_panel = new PonderAddAnswerView(); $answer_add_panel ->setQuestion($question) ->setUser($user) ->setActionURI("/ponder/answer/add/"); $header = id(new PhabricatorHeaderView()) - ->setObjectName('Q'.$question->getID()) ->setHeader($question->getTitle()); $actions = $this->buildActionListView($question); $properties = $this->buildPropertyListView($question, $subscribers); + $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); + $crumbs->setActionList($actions); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName('Q'.$this->questionID) + ->setHref('/Q'.$this->questionID)); + $nav = $this->buildSideNavView($question); $nav->appendChild( array( + $crumbs, $header, $actions, $properties, $detail_panel, $responses_panel, $answer_add_panel )); $nav->selectFilter(null); return $this->buildApplicationPage( $nav, array( 'device' => true, 'title' => 'Q'.$question->getID().' '.$question->getTitle() )); } private function buildActionListView(PonderQuestion $question) { $viewer = $this->getRequest()->getUser(); $view = new PhabricatorActionListView(); $view->setUser($viewer); $view->setObject($question); return $view; } private function buildPropertyListView( PonderQuestion $question, array $subscribers) { $viewer = $this->getRequest()->getUser(); $view = new PhabricatorPropertyListView(); $view->addProperty( pht('Author'), $this->getHandle($question->getAuthorPHID())->renderLink()); $view->addProperty( pht('Created'), phabricator_datetime($question->getDateCreated(), $viewer)); if ($subscribers) { $subscribers = $this->renderHandlesForPHIDs($subscribers); } $view->addProperty( pht('Subscribers'), nonempty($subscribers, phutil_tag('em', array(), pht('None')))); return $view; } } diff --git a/src/view/layout/PhabricatorCrumbsView.php b/src/view/layout/PhabricatorCrumbsView.php index 95441c8282..c3ad77a222 100644 --- a/src/view/layout/PhabricatorCrumbsView.php +++ b/src/view/layout/PhabricatorCrumbsView.php @@ -1,126 +1,126 @@ <?php final class PhabricatorCrumbsView extends AphrontView { private $crumbs = array(); private $actions = array(); private $actionListID = null; protected function canAppendChild() { return false; } public function addCrumb(PhabricatorCrumbView $crumb) { $this->crumbs[] = $crumb; return $this; } public function addAction(PhabricatorMenuItemView $action) { $this->actions[] = $action; return $this; } public function setActionList(PhabricatorActionListView $list) { $this->actionListID = celerity_generate_unique_node_id(); $list->setId($this->actionListID); return $this; } public function render() { require_celerity_resource('phabricator-crumbs-view-css'); $action_view = null; - if ($this->actions) { + if (($this->actions) || ($this->actionListID)) { $actions = array(); foreach ($this->actions as $action) { $icon = null; if ($action->getIcon()) { $icon = phutil_tag( 'span', array( 'class' => 'sprite-icon action-'.$action->getIcon(), ), ''); } $name = phutil_tag( 'span', array( 'class' => 'phabricator-crumbs-action-name' ), $action->getName() ); $actions[] = javelin_tag( 'a', array( 'href' => $action->getHref(), 'class' => 'phabricator-crumbs-action', 'sigil' => $action->getWorkflow() ? 'workflow' : null, ), array( $icon, $name, )); } if ($this->actionListID) { $icon_id = celerity_generate_unique_node_id(); $icon = phutil_tag( 'span', array( 'class' => 'sprite-icon action-action-menu' ), ''); $name = phutil_tag( 'span', array( 'class' => 'phabricator-crumbs-action-name' ), pht('Actions')); $actions[] = javelin_tag( 'a', array( 'href' => '#', 'class' => 'phabricator-crumbs-action phabricator-crumbs-action-menu', 'sigil' => 'jx-toggle-class', 'id' => $icon_id, 'meta' => array( 'map' => array( $this->actionListID => 'phabricator-action-list-toggle', $icon_id => 'phabricator-crumbs-action-menu-open' ), ), ), array( $icon, $name, )); } $action_view = phutil_tag( 'div', array( 'class' => 'phabricator-crumbs-actions', ), $actions); } if ($this->crumbs) { last($this->crumbs)->setIsLastCrumb(true); } return phutil_tag( 'div', array( 'class' => 'phabricator-crumbs-view '. 'sprite-gradient gradient-breadcrumbs', ), array( $action_view, $this->crumbs, )); } }