diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index 1304e906cd..6b414a9fe7 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -1,234 +1,240 @@ getViewer(); $post = id(new PhamePostQuery()) ->setViewer($viewer) ->withIDs(array($request->getURIData('id'))) ->executeOne(); if (!$post) { return new Aphront404Response(); } $blog = $post->getBlog(); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb( - $blog->getName(), - $this->getApplicationURI('blog/view/'.$blog->getID().'/')); + if ($blog) { + $crumbs->addTextCrumb( + $blog->getName(), + $this->getApplicationURI('blog/view/'.$blog->getID().'/')); + } else { + $crumbs->addTextCrumb( + pht('[No Blog]'), + null); + } $crumbs->addTextCrumb( $post->getTitle(), $this->getApplicationURI('post/view/'.$post->getID().'/')); $crumbs->setBorder(true); $actions = $this->renderActions($post, $viewer); $properties = $this->renderProperties($post, $viewer); $action_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Actions')) ->setHref('#') ->setIconFont('fa-bars') ->addClass('phui-mobile-menu') ->setDropdownMenu($actions); $header = id(new PHUIHeaderView()) ->setHeader($post->getTitle()) ->setUser($viewer) ->setPolicyObject($post) ->addActionLink($action_button); $document = id(new PHUIDocumentViewPro()) ->setHeader($header) ->setPropertyList($properties); if ($post->isDraft()) { $document->appendChild( id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setTitle(pht('Draft Post')) ->appendChild( pht( 'Only you can see this draft until you publish it. '. 'Use "Preview / Publish" to publish this post.'))); } if (!$post->getBlog()) { $document->appendChild( id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setTitle(pht('Not On A Blog')) ->appendChild( pht( 'This post is not associated with a blog (the blog may have '. 'been deleted). Use "Move Post" to move it to a new blog.'))); } $engine = id(new PhabricatorMarkupEngine()) ->setViewer($viewer) ->addObject($post, PhamePost::MARKUP_FIELD_BODY) ->process(); $document->appendChild( phutil_tag( 'div', array( 'class' => 'phabricator-remarkup', ), $engine->getOutput($post, PhamePost::MARKUP_FIELD_BODY))); $timeline = $this->buildTransactionTimeline( $post, id(new PhamePostTransactionQuery()) ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))); $timeline = phutil_tag_div('phui-document-view-pro-box', $timeline); $add_comment = $this->buildCommentForm($post); return $this->newPage() ->setTitle($post->getTitle()) ->addClass('pro-white-background') ->setPageObjectPHIDs(array($post->getPHID())) ->setCrumbs($crumbs) ->appendChild( array( $document, $timeline, $add_comment, )); } private function renderActions( PhamePost $post, PhabricatorUser $viewer) { $actions = id(new PhabricatorActionListView()) ->setObject($post) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $post, PhabricatorPolicyCapability::CAN_EDIT); $id = $post->getID(); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI('post/edit/'.$id.'/')) ->setName(pht('Edit Post')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-arrows') ->setHref($this->getApplicationURI('post/move/'.$id.'/')) ->setName(pht('Move Post')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); if ($post->isDraft()) { $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-eye') ->setHref($this->getApplicationURI('post/publish/'.$id.'/')) ->setDisabled(!$can_edit) ->setName(pht('Preview / Publish'))); } else { $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-eye-slash') ->setHref($this->getApplicationURI('post/unpublish/'.$id.'/')) ->setName(pht('Unpublish')) ->setDisabled(!$can_edit) ->setWorkflow(true)); } $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-times') ->setHref($this->getApplicationURI('post/delete/'.$id.'/')) ->setName(pht('Delete Post')) ->setDisabled(!$can_edit) ->setWorkflow(true)); $blog = $post->getBlog(); $can_view_live = $blog && !$post->isDraft(); if ($can_view_live) { $live_uri = $blog->getLiveURI($post); } else { $live_uri = 'post/notlive/'.$post->getID().'/'; $live_uri = $this->getApplicationURI($live_uri); } $actions->addAction( id(new PhabricatorActionView()) ->setUser($viewer) ->setIcon('fa-globe') ->setHref($live_uri) ->setName(pht('View Live')) ->setDisabled(!$can_view_live) ->setWorkflow(!$can_view_live)); return $actions; } private function renderProperties( PhamePost $post, PhabricatorUser $viewer) { $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($post); $properties->addProperty( pht('Blog'), $viewer->renderHandle($post->getBlogPHID())); $properties->addProperty( pht('Blogger'), $viewer->renderHandle($post->getBloggerPHID())); $properties->addProperty( pht('Published'), $post->isDraft() ? pht('Draft') : phabricator_datetime($post->getDatePublished(), $viewer)); $properties->invokeWillRenderEvent(); return $properties; } private function buildCommentForm(PhamePost $post) { $viewer = $this->getViewer(); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $add_comment_header = $is_serious ? pht('Add Comment') : pht('Derp Text'); $draft = PhabricatorDraft::newFromUserAndKey( $viewer, $post->getPHID()); $box = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setObjectPHID($post->getPHID()) ->setDraft($draft) ->setHeaderText($add_comment_header) ->setAction($this->getApplicationURI('post/comment/'.$post->getID().'/')) ->setSubmitButtonName(pht('Add Comment')); return phutil_tag_div('phui-document-view-pro-box', $box); } } diff --git a/src/applications/phame/query/PhamePostSearchEngine.php b/src/applications/phame/query/PhamePostSearchEngine.php index 84fec54df0..68c94552bb 100644 --- a/src/applications/phame/query/PhamePostSearchEngine.php +++ b/src/applications/phame/query/PhamePostSearchEngine.php @@ -1,111 +1,116 @@ newQuery(); if (strlen($map['visibility'])) { $query->withVisibility($map['visibility']); } return $query; } protected function buildCustomSearchFields() { return array( id(new PhabricatorSearchSelectField()) ->setKey('visibility') ->setLabel(pht('Visibility')) ->setOptions(array( '' => pht('All'), PhameConstants::VISIBILITY_PUBLISHED => pht('Published'), PhameConstants::VISIBILITY_DRAFT => pht('Draft'), )), ); } protected function getURI($path) { return '/phame/post/'.$path; } protected function getBuiltinQueryNames() { $names = array( 'all' => pht('All Posts'), 'live' => pht('Published Posts'), 'draft' => pht('Draft Posts'), ); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; case 'live': return $query->setParameter( 'visibility', PhameConstants::VISIBILITY_PUBLISHED); case 'draft': return $query->setParameter( 'visibility', PhameConstants::VISIBILITY_DRAFT); } return parent::buildSavedQueryFromBuiltin($query_key); } protected function renderResultList( array $posts, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($posts, 'PhamePost'); $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); $list->setUser($viewer); foreach ($posts as $post) { $id = $post->getID(); - $blog = $viewer->renderHandle($post->getBlogPHID())->render(); + $blog = $post->getBlog(); + if ($blog) { + $blog_name = $viewer->renderHandle($post->getBlogPHID())->render(); + $blog_name = pht('Blog: %s', $blog_name); + } else { + $blog_name = pht('[No Blog]'); + } $item = id(new PHUIObjectItemView()) ->setUser($viewer) ->setObject($post) ->setHeader($post->getTitle()) ->setStatusIcon('fa-star') ->setHref($this->getApplicationURI("/post/view/{$id}/")) - ->addAttribute( - pht('Blog: %s', $blog)); + ->addAttribute($blog_name); if ($post->isDraft()) { $item->setStatusIcon('fa-star-o grey'); $item->setDisabled(true); $item->addIcon('none', pht('Draft Post')); } else { $date = $post->getDatePublished(); $item->setEpoch($date); } $list->addItem($item); } $result = new PhabricatorApplicationSearchResultView(); $result->setObjectList($list); $result->setNoDataString(pht('No blogs posts found.')); return $result; } } diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index da464a4a46..eb04679e1f 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -1,279 +1,281 @@ setBloggerPHID($blogger->getPHID()) ->setBlogPHID($blog->getPHID()) ->setBlog($blog) ->setDatePublished(0) ->setVisibility(PhameConstants::VISIBILITY_PUBLISHED); return $post; } public function setBlog(PhameBlog $blog) { $this->blog = $blog; return $this; } public function getBlog() { return $this->blog; } public function getViewURI() { // go for the pretty uri if we can $domain = ($this->blog ? $this->blog->getDomain() : ''); if ($domain) { $phame_title = PhabricatorSlug::normalize($this->getPhameTitle()); return 'http://'.$domain.'/post/'.$phame_title; } $uri = '/phame/post/view/'.$this->getID().'/'; return PhabricatorEnv::getProductionURI($uri); } public function getEditURI() { return '/phame/post/edit/'.$this->getID().'/'; } public function isDraft() { return $this->getVisibility() == PhameConstants::VISIBILITY_DRAFT; } public function getHumanName() { if ($this->isDraft()) { $name = 'draft'; } else { $name = 'post'; } return $name; } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'configData' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'title' => 'text255', 'phameTitle' => 'sort64', 'visibility' => 'uint32', 'mailKey' => 'bytes20', // T6203/NULLABILITY // These seem like they should always be non-null? 'blogPHID' => 'phid?', 'body' => 'text?', 'configData' => 'text?', // T6203/NULLABILITY // This one probably should be nullable? 'datePublished' => 'epoch', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'phameTitle' => array( 'columns' => array('bloggerPHID', 'phameTitle'), 'unique' => true, ), 'bloggerPosts' => array( 'columns' => array( 'bloggerPHID', 'visibility', 'datePublished', 'id', ), ), ), ) + parent::getConfiguration(); } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPhamePostPHIDType::TYPECONST); } public function toDictionary() { return array( 'id' => $this->getID(), 'phid' => $this->getPHID(), 'blogPHID' => $this->getBlogPHID(), 'bloggerPHID' => $this->getBloggerPHID(), 'viewURI' => $this->getViewURI(), 'title' => $this->getTitle(), 'phameTitle' => $this->getPhameTitle(), 'body' => $this->getBody(), 'summary' => PhabricatorMarkupEngine::summarize($this->getBody()), 'datePublished' => $this->getDatePublished(), 'published' => !$this->isDraft(), ); } /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { // Draft posts are visible only to the author. Published posts are visible // to whoever the blog is visible to. switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: if (!$this->isDraft() && $this->getBlog()) { return $this->getBlog()->getViewPolicy(); } else if ($this->getBlog()) { return $this->getBlog()->getEditPolicy(); + } else { + return PhabricatorPolicies::POLICY_NOONE; } break; case PhabricatorPolicyCapability::CAN_EDIT: if ($this->getBlog()) { return $this->getBlog()->getEditPolicy(); } else { return PhabricatorPolicies::POLICY_NOONE; } } } public function hasAutomaticCapability($capability, PhabricatorUser $user) { // A blog post's author can always view it. switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: case PhabricatorPolicyCapability::CAN_EDIT: return ($user->getPHID() == $this->getBloggerPHID()); } } public function describeAutomaticCapability($capability) { return pht('The author of a blog post can always view and edit it.'); } /* -( PhabricatorMarkupInterface Implementation )-------------------------- */ public function getMarkupFieldKey($field) { $hash = PhabricatorHash::digest($this->getMarkupText($field)); return $this->getPHID().':'.$field.':'.$hash; } public function newMarkupEngine($field) { return PhabricatorMarkupEngine::newPhameMarkupEngine(); } public function getMarkupText($field) { switch ($field) { case self::MARKUP_FIELD_BODY: return $this->getBody(); case self::MARKUP_FIELD_SUMMARY: return PhabricatorMarkupEngine::summarize($this->getBody()); } } public function didMarkupText( $field, $output, PhutilMarkupEngine $engine) { return $output; } public function shouldUseMarkupCache($field) { return (bool)$this->getPHID(); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhamePostEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PhamePostTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { return array( $this->getBloggerPHID(), ); } /* -( PhabricatorSubscribableInterface Implementation )-------------------- */ public function isAutomaticallySubscribed($phid) { return ($this->bloggerPHID == $phid); } public function shouldShowSubscribersProperty() { return true; } public function shouldAllowSubscription($phid) { return true; } }