diff --git a/resources/sql/autopatches/20151108.phame.blog.joinpolicy.sql b/resources/sql/autopatches/20151108.phame.blog.joinpolicy.sql new file mode 100644 index 000000000..54aea48bb --- /dev/null +++ b/resources/sql/autopatches/20151108.phame.blog.joinpolicy.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phame.phame_blog + DROP joinPolicy; diff --git a/src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php b/src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php index 425bf1735..1f569d3a3 100644 --- a/src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php +++ b/src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php @@ -1,103 +1,104 @@ <?php final class PhameCreatePostConduitAPIMethod extends PhameConduitAPIMethod { public function getAPIMethodName() { return 'phame.createpost'; } public function getMethodDescription() { return pht('Create a phame post.'); } public function getMethodStatus() { return self::METHOD_STATUS_UNSTABLE; } protected function defineParamTypes() { return array( 'blogPHID' => 'required phid', 'title' => 'required string', 'body' => 'required string', 'phameTitle' => 'optional string', 'bloggerPHID' => 'optional phid', 'isDraft' => 'optional bool', ); } protected function defineReturnType() { return 'list<dict>'; } protected function defineErrorTypes() { return array( 'ERR-INVALID-PARAMETER' => pht('Missing or malformed parameter.'), 'ERR-INVALID-BLOG' => pht('Invalid blog PHID or user can not post to blog.'), ); } protected function execute(ConduitAPIRequest $request) { $user = $request->getUser(); $blog_phid = $request->getValue('blogPHID'); $title = $request->getValue('title'); $body = $request->getValue('body'); $exception_description = array(); if (!$blog_phid) { $exception_description[] = pht('No blog phid.'); } if (!strlen($title)) { $exception_description[] = pht('No post title.'); } if (!strlen($body)) { $exception_description[] = pht('No post body.'); } if ($exception_description) { throw id(new ConduitException('ERR-INVALID-PARAMETER')) ->setErrorDescription(implode("\n", $exception_description)); } $blogger_phid = $request->getValue('bloggerPHID'); if ($blogger_phid) { $blogger = id(new PhabricatorPeopleQuery()) ->setViewer($user) ->withPHIDs(array($blogger_phid)) ->executeOne(); } else { $blogger = $user; } $blog = id(new PhameBlogQuery()) ->setViewer($blogger) ->withPHIDs(array($blog_phid)) ->requireCapabilities( array( - PhabricatorPolicyCapability::CAN_JOIN, + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$blog) { throw new ConduitException('ERR-INVALID-BLOG'); } $post = PhamePost::initializePost($blogger, $blog); $is_draft = $request->getValue('isDraft', false); if (!$is_draft) { $post->setDatePublished(time()); $post->setVisibility(PhameConstants::VISIBILITY_PUBLISHED); } $post->setTitle($title); $phame_title = $request->getValue( 'phameTitle', id(new PhutilUTF8StringTruncator()) ->setMaximumBytes(64) ->truncateString($title)); $post->setPhameTitle(PhabricatorSlug::normalize($phame_title)); $post->setBody($body); $post->save(); return $post->toDictionary(); } } diff --git a/src/applications/phame/controller/blog/PhameBlogEditController.php b/src/applications/phame/controller/blog/PhameBlogEditController.php index b59e5e01a..0c2bb324d 100644 --- a/src/applications/phame/controller/blog/PhameBlogEditController.php +++ b/src/applications/phame/controller/blog/PhameBlogEditController.php @@ -1,223 +1,210 @@ <?php final class PhameBlogEditController extends PhameBlogController { public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); if ($id) { $blog = id(new PhameBlogQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$blog) { return new Aphront404Response(); } $submit_button = pht('Save Changes'); $page_title = pht('Edit Blog'); $cancel_uri = $this->getApplicationURI('blog/view/'.$blog->getID().'/'); $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( $blog->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $v_projects = array_reverse($v_projects); $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID( $blog->getPHID()); } else { $this->requireApplicationCapability( PhameBlogCreateCapability::CAPABILITY); $blog = PhameBlog::initializeNewBlog($viewer); $submit_button = pht('Create Blog'); $page_title = pht('Create Blog'); $cancel_uri = $this->getApplicationURI(); $v_projects = array(); $v_cc = array(); } $name = $blog->getName(); $description = $blog->getDescription(); $custom_domain = $blog->getDomain(); $skin = $blog->getSkin(); $can_view = $blog->getViewPolicy(); $can_edit = $blog->getEditPolicy(); - $can_join = $blog->getJoinPolicy(); $e_name = true; $e_custom_domain = null; $e_view_policy = null; $validation_exception = null; if ($request->isFormPost()) { $name = $request->getStr('name'); $description = $request->getStr('description'); $custom_domain = nonempty($request->getStr('custom_domain'), null); $skin = $request->getStr('skin'); $can_view = $request->getStr('can_view'); $can_edit = $request->getStr('can_edit'); - $can_join = $request->getStr('can_join'); $v_projects = $request->getArr('projects'); $v_cc = $request->getArr('cc'); $xactions = array( id(new PhameBlogTransaction()) ->setTransactionType(PhameBlogTransaction::TYPE_NAME) ->setNewValue($name), id(new PhameBlogTransaction()) ->setTransactionType(PhameBlogTransaction::TYPE_DESCRIPTION) ->setNewValue($description), id(new PhameBlogTransaction()) ->setTransactionType(PhameBlogTransaction::TYPE_DOMAIN) ->setNewValue($custom_domain), id(new PhameBlogTransaction()) ->setTransactionType(PhameBlogTransaction::TYPE_SKIN) ->setNewValue($skin), id(new PhameBlogTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) ->setNewValue($can_view), id(new PhameBlogTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) ->setNewValue($can_edit), - id(new PhameBlogTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_JOIN_POLICY) - ->setNewValue($can_join), id(new PhameBlogTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) ->setNewValue(array('=' => $v_cc)), ); $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new PhameBlogTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $proj_edge_type) ->setNewValue(array('=' => array_fuse($v_projects))); $editor = id(new PhameBlogEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($blog, $xactions); return id(new AphrontRedirectResponse()) ->setURI($this->getApplicationURI('blog/view/'.$blog->getID().'/')); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_name = $validation_exception->getShortMessage( PhameBlogTransaction::TYPE_NAME); $e_custom_domain = $validation_exception->getShortMessage( PhameBlogTransaction::TYPE_DOMAIN); $e_view_policy = $validation_exception->getShortMessage( PhabricatorTransactions::TYPE_VIEW_POLICY); } } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($blog) ->execute(); $skins = PhameSkinSpecification::loadAllSkinSpecifications(); $skins = mpull($skins, 'getName'); $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') ->setValue($name) ->setID('blog-name') ->setError($e_name)) ->appendChild( id(new PhabricatorRemarkupControl()) ->setUser($viewer) ->setLabel(pht('Description')) ->setName('description') ->setValue($description) ->setID('blog-description') ->setUser($viewer) ->setDisableMacros(true)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Subscribers')) ->setName('cc') ->setValue($v_cc) ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource())) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($blog) ->setPolicies($policies) ->setError($e_view_policy) ->setValue($can_view) ->setName('can_view')) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($blog) ->setPolicies($policies) ->setValue($can_edit) ->setName('can_edit')) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_JOIN) - ->setPolicyObject($blog) - ->setPolicies($policies) - ->setValue($can_join) - ->setName('can_join')) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setValue($v_projects) ->setDatasource(new PhabricatorProjectDatasource())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Custom Domain')) ->setName('custom_domain') ->setValue($custom_domain) ->setCaption( pht('Must include at least one dot (.), e.g. %s', 'blog.example.com')) ->setError($e_custom_domain)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Skin')) ->setName('skin') ->setValue($skin) ->setOptions($skins)) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($submit_button)); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setValidationException($validation_exception) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Blogs'), $this->getApplicationURI('blog/')); $crumbs->addTextCrumb($page_title, $this->getApplicationURI('blog/new')); return $this->newPage() ->setTitle($page_title) ->setCrumbs($crumbs) ->appendChild( array( $form_box, )); } } diff --git a/src/applications/phame/controller/blog/PhameBlogViewController.php b/src/applications/phame/controller/blog/PhameBlogViewController.php index 03dfc8c1c..249bd5506 100644 --- a/src/applications/phame/controller/blog/PhameBlogViewController.php +++ b/src/applications/phame/controller/blog/PhameBlogViewController.php @@ -1,178 +1,169 @@ <?php final class PhameBlogViewController extends PhameBlogController { public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); $blog = id(new PhameBlogQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$blog) { return new Aphront404Response(); } $pager = id(new AphrontCursorPagerView()) ->readFromRequest($request); $posts = id(new PhamePostQuery()) ->setViewer($viewer) ->withBlogPHIDs(array($blog->getPHID())) ->executeWithCursorPager($pager); $header = id(new PHUIHeaderView()) ->setHeader($blog->getName()) ->setUser($viewer) ->setPolicyObject($blog); $actions = $this->renderActions($blog, $viewer); $properties = $this->renderProperties($blog, $viewer, $actions); $post_list = $this->renderPostList( $posts, $viewer, pht('This blog has no visible posts.')); $post_list = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Latest Posts')) ->appendChild($post_list); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Blogs'), $this->getApplicationURI('blog/')); $crumbs->addTextCrumb($blog->getName(), $this->getApplicationURI()); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); return $this->newPage() ->setTitle($blog->getName()) ->setCrumbs($crumbs) ->appendChild( array( $object_box, $post_list, )); } private function renderProperties( PhameBlog $blog, PhabricatorUser $viewer, PhabricatorActionListView $actions) { require_celerity_resource('aphront-tooltip-css'); Javelin::initBehavior('phabricator-tooltips'); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($blog) ->setActionList($actions); $properties->addProperty( pht('Skin'), $blog->getSkin()); $properties->addProperty( pht('Domain'), $blog->getDomain()); $feed_uri = PhabricatorEnv::getProductionURI( $this->getApplicationURI('blog/feed/'.$blog->getID().'/')); $properties->addProperty( pht('Atom URI'), javelin_tag('a', array( 'href' => $feed_uri, 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => pht('Atom URI does not support custom domains.'), 'size' => 320, ), ), $feed_uri)); $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, $blog); $properties->addProperty( pht('Editable By'), $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); - $properties->addProperty( - pht('Joinable By'), - $descriptions[PhabricatorPolicyCapability::CAN_JOIN]); - $engine = id(new PhabricatorMarkupEngine()) ->setViewer($viewer) ->addObject($blog, PhameBlog::MARKUP_FIELD_DESCRIPTION) ->process(); $properties->invokeWillRenderEvent(); if (strlen($blog->getDescription())) { $description = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($blog->getDescription()), 'default', $viewer); $properties->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $properties->addTextContent($description); } return $properties; } private function renderActions(PhameBlog $blog, PhabricatorUser $viewer) { $actions = id(new PhabricatorActionListView()) ->setObject($blog) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $blog, PhabricatorPolicyCapability::CAN_EDIT); - $can_join = PhabricatorPolicyFilter::hasCapability( - $viewer, - $blog, - PhabricatorPolicyCapability::CAN_JOIN); - $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-plus') ->setHref($this->getApplicationURI('post/edit/?blog='.$blog->getID())) ->setName(pht('Write Post')) - ->setDisabled(!$can_join) - ->setWorkflow(!$can_join)); + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); $actions->addAction( id(new PhabricatorActionView()) ->setUser($viewer) ->setIcon('fa-globe') ->setHref($blog->getLiveURI()) ->setName(pht('View Live'))); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI('blog/edit/'.$blog->getID().'/')) ->setName(pht('Edit Blog')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-times') ->setHref($this->getApplicationURI('blog/delete/'.$blog->getID().'/')) ->setName(pht('Delete Blog')) ->setDisabled(!$can_edit) ->setWorkflow(true)); return $actions; } } diff --git a/src/applications/phame/controller/post/PhamePostEditController.php b/src/applications/phame/controller/post/PhamePostEditController.php index 99e6df390..55ce9e176 100644 --- a/src/applications/phame/controller/post/PhamePostEditController.php +++ b/src/applications/phame/controller/post/PhamePostEditController.php @@ -1,232 +1,232 @@ <?php final class PhamePostEditController extends PhamePostController { public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); if ($id) { $post = id(new PhamePostQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$post) { return new Aphront404Response(); } $cancel_uri = $this->getApplicationURI('/post/view/'.$id.'/'); $submit_button = pht('Save Changes'); $page_title = pht('Edit Post'); $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( $post->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $v_projects = array_reverse($v_projects); $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID( $post->getPHID()); } else { $blog = id(new PhameBlogQuery()) ->setViewer($viewer) ->withIDs(array($request->getInt('blog'))) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_JOIN, + PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$blog) { return new Aphront404Response(); } $v_projects = array(); $v_cc = array(); $post = PhamePost::initializePost($viewer, $blog); $cancel_uri = $this->getApplicationURI('/blog/view/'.$blog->getID().'/'); $submit_button = pht('Create Post'); $page_title = pht('Create Post'); } $title = $post->getTitle(); $phame_title = $post->getPhameTitle(); $body = $post->getBody(); $comments_widget = $post->getCommentsWidget(); $visibility = $post->getVisibility(); $e_title = true; $e_phame_title = true; $validation_exception = null; if ($request->isFormPost()) { $title = $request->getStr('title'); $phame_title = $request->getStr('phame_title'); $phame_title = PhabricatorSlug::normalize($phame_title); $body = $request->getStr('body'); $comments_widget = $request->getStr('comments_widget'); $v_projects = $request->getArr('projects'); $v_cc = $request->getArr('cc'); $visibility = $request->getInt('visibility'); $xactions = array( id(new PhamePostTransaction()) ->setTransactionType(PhamePostTransaction::TYPE_TITLE) ->setNewValue($title), id(new PhamePostTransaction()) ->setTransactionType(PhamePostTransaction::TYPE_PHAME_TITLE) ->setNewValue($phame_title), id(new PhamePostTransaction()) ->setTransactionType(PhamePostTransaction::TYPE_BODY) ->setNewValue($body), id(new PhamePostTransaction()) ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) ->setNewValue($visibility), id(new PhamePostTransaction()) ->setTransactionType(PhamePostTransaction::TYPE_COMMENTS_WIDGET) ->setNewValue($comments_widget), id(new PhamePostTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) ->setNewValue(array('=' => $v_cc)), ); $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new PhamePostTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $proj_edge_type) ->setNewValue(array('=' => array_fuse($v_projects))); $editor = id(new PhamePostEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($post, $xactions); $uri = $this->getApplicationURI('/post/view/'.$post->getID().'/'); return id(new AphrontRedirectResponse())->setURI($uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_title = $validation_exception->getShortMessage( PhamePostTransaction::TYPE_TITLE); $e_phame_title = $validation_exception->getShortMessage( PhamePostTransaction::TYPE_PHAME_TITLE); } } $handle = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs(array($post->getBlogPHID())) ->executeOne(); $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('blog', $request->getInt('blog')) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Blog')) ->setValue($handle->renderLink())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Title')) ->setName('title') ->setValue($title) ->setID('post-title') ->setError($e_title)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Phame Title')) ->setName('phame_title') ->setValue(rtrim($phame_title, '/')) ->setID('post-phame-title') ->setCaption(pht('Up to 64 alphanumeric characters '. 'with underscores for spaces. '. 'Formatting is enforced.')) ->setError($e_phame_title)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Visibility')) ->setName('visibility') ->setvalue($visibility) ->setOptions(PhameConstants::getPhamePostStatusMap())) ->appendChild( id(new PhabricatorRemarkupControl()) ->setLabel(pht('Body')) ->setName('body') ->setValue($body) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setID('post-body') ->setUser($viewer) ->setDisableMacros(true)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Subscribers')) ->setName('cc') ->setValue($v_cc) ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource())) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setValue($v_projects) ->setDatasource(new PhabricatorProjectDatasource())) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Comments Widget')) ->setName('comments_widget') ->setvalue($comments_widget) ->setOptions($post->getCommentsWidgetOptionsForSelect())) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($submit_button)); $header = id(new PHUIHeaderView()) ->setHeader(pht('%s (Post Preview)', $title)); $container = id(new PHUIBoxView()) ->setID('post-preview'); $document = id(new PHUIDocumentViewPro()) ->setHeader($header) ->appendChild($container); $preview_panel = id(new PHUIObjectBoxView()) ->appendChild($document); Javelin::initBehavior( 'phame-post-preview', array( 'preview' => 'post-preview', 'body' => 'post-body', 'title' => 'post-title', 'phame_title' => 'post-phame-title', 'uri' => '/phame/post/preview/', )); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setValidationException($validation_exception) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( $page_title, $this->getApplicationURI('/post/view/'.$id.'/')); return $this->newPage() ->setTitle($page_title) ->setCrumbs($crumbs) ->appendChild( array( $form_box, $preview_panel, )); } } diff --git a/src/applications/phame/controller/post/PhamePostNewController.php b/src/applications/phame/controller/post/PhamePostNewController.php index 709840174..8dd2e0d3c 100644 --- a/src/applications/phame/controller/post/PhamePostNewController.php +++ b/src/applications/phame/controller/post/PhamePostNewController.php @@ -1,120 +1,120 @@ <?php final class PhamePostNewController extends PhamePostController { public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); $post = null; $view_uri = null; if ($id) { $post = id(new PhamePostQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$post) { return new Aphront404Response(); } $view_uri = '/post/view/'.$post->getID().'/'; $view_uri = $this->getApplicationURI($view_uri); if ($request->isFormPost()) { $blog = id(new PhameBlogQuery()) ->setViewer($viewer) ->withIDs(array($request->getInt('blog'))) ->requireCapabilities( array( - PhabricatorPolicyCapability::CAN_JOIN, + PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if ($blog) { $post->setBlogPHID($blog->getPHID()); $post->save(); return id(new AphrontRedirectResponse())->setURI($view_uri); } } $title = pht('Move Post'); } else { $title = pht('Create Post'); $view_uri = $this->getApplicationURI('/post/new'); } $blogs = id(new PhameBlogQuery()) ->setViewer($viewer) ->requireCapabilities( array( - PhabricatorPolicyCapability::CAN_JOIN, + PhabricatorPolicyCapability::CAN_EDIT, )) ->execute(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title, $view_uri); $notification = null; $form_box = null; if (!$blogs) { $notification = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NODATA) ->appendChild( - pht('You do not have permission to join any blogs. Create a blog '. + pht('You do not have permission to post to any blogs. Create a blog '. 'first, then you can post to it.')); } else { $options = mpull($blogs, 'getName', 'getID'); asort($options); $selected_value = null; if ($post && $post->getBlog()) { $selected_value = $post->getBlog()->getID(); } $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Blog')) ->setName('blog') ->setOptions($options) ->setValue($selected_value)); if ($post) { $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Move Post')) ->addCancelButton($view_uri)); } else { $form ->setAction($this->getApplicationURI('post/edit/')) ->setMethod('GET') ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Continue'))); } $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setForm($form); } return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( $notification, $form_box, )); } } diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index 3afdf5401..ca48af103 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -1,193 +1,195 @@ <?php final class PhamePostViewController extends PhamePostController { public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $post = id(new PhamePostQuery()) ->setViewer($viewer) ->withIDs(array($request->getURIData('id'))) ->executeOne(); if (!$post) { return new Aphront404Response(); } $crumbs = $this->buildApplicationCrumbs(); $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))); return $this->newPage() ->setTitle($post->getTitle()) ->addClass('pro-white-background') ->setCrumbs($crumbs) ->appendChild( array( $document, )); } 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; } } diff --git a/src/applications/phame/editor/PhameBlogEditor.php b/src/applications/phame/editor/PhameBlogEditor.php index 7c793aff6..712c4ddd5 100644 --- a/src/applications/phame/editor/PhameBlogEditor.php +++ b/src/applications/phame/editor/PhameBlogEditor.php @@ -1,229 +1,228 @@ <?php final class PhameBlogEditor extends PhabricatorApplicationTransactionEditor { public function getEditorApplicationClass() { return 'PhabricatorPhameApplication'; } public function getEditorObjectsDescription() { return pht('Phame Blogs'); } public function getTransactionTypes() { $types = parent::getTransactionTypes(); $types[] = PhameBlogTransaction::TYPE_NAME; $types[] = PhameBlogTransaction::TYPE_DESCRIPTION; $types[] = PhameBlogTransaction::TYPE_DOMAIN; $types[] = PhameBlogTransaction::TYPE_SKIN; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY; return $types; } protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhameBlogTransaction::TYPE_NAME: return $object->getName(); case PhameBlogTransaction::TYPE_DESCRIPTION: return $object->getDescription(); case PhameBlogTransaction::TYPE_DOMAIN: return $object->getDomain(); case PhameBlogTransaction::TYPE_SKIN: return $object->getSkin(); } } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhameBlogTransaction::TYPE_NAME: case PhameBlogTransaction::TYPE_DESCRIPTION: case PhameBlogTransaction::TYPE_DOMAIN: case PhameBlogTransaction::TYPE_SKIN: return $xaction->getNewValue(); } } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhameBlogTransaction::TYPE_NAME: return $object->setName($xaction->getNewValue()); case PhameBlogTransaction::TYPE_DESCRIPTION: return $object->setDescription($xaction->getNewValue()); case PhameBlogTransaction::TYPE_DOMAIN: return $object->setDomain($xaction->getNewValue()); case PhameBlogTransaction::TYPE_SKIN: return $object->setSkin($xaction->getNewValue()); } return parent::applyCustomInternalTransaction($object, $xaction); } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhameBlogTransaction::TYPE_NAME: case PhameBlogTransaction::TYPE_DESCRIPTION: case PhameBlogTransaction::TYPE_DOMAIN: case PhameBlogTransaction::TYPE_SKIN: return; } return parent::applyCustomExternalTransaction($object, $xaction); } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhameBlogTransaction::TYPE_NAME: $missing = $this->validateIsEmptyTextField( $object->getName(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('Name is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } break; case PhameBlogTransaction::TYPE_DOMAIN: if (!$xactions) { continue; } $custom_domain = last($xactions)->getNewValue(); if (empty($custom_domain)) { continue; } list($error_label, $error_text) = $object->validateCustomDomain($custom_domain); if ($error_label) { $error = new PhabricatorApplicationTransactionValidationError( $type, $error_label, $error_text, nonempty(last($xactions), null)); $errors[] = $error; } if ($object->getViewPolicy() != PhabricatorPolicies::POLICY_PUBLIC) { $error_text = pht( 'For custom domains to work, the blog must have a view policy of '. 'public.'); $error = new PhabricatorApplicationTransactionValidationError( PhabricatorTransactions::TYPE_VIEW_POLICY, pht('Invalid Policy'), $error_text, nonempty(last($xactions), null)); $errors[] = $error; } $duplicate_blog = id(new PhameBlogQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withDomain($custom_domain) ->executeOne(); if ($duplicate_blog && $duplicate_blog->getID() != $object->getID()) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Not Unique'), pht('Domain must be unique; another blog already has this domain.'), nonempty(last($xactions), null)); $errors[] = $error; } break; } return $errors; } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function getMailTo(PhabricatorLiskDAO $object) { $phids = array(); $phids[] = $this->requireActor()->getPHID(); $phids[] = $object->getCreatorPHID(); return $phids; } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $phid = $object->getPHID(); $name = $object->getName(); return id(new PhabricatorMetaMTAMail()) ->setSubject($name) ->addHeader('Thread-Topic', $phid); } protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new PhameBlogReplyHandler()) ->setMailReceiver($object); } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $body = parent::buildMailBody($object, $xactions); $body->addLinkSection( pht('BLOG DETAIL'), PhabricatorEnv::getProductionURI($object->getViewURI())); return $body; } public function getMailTagsMap() { return array( PhameBlogTransaction::MAILTAG_DETAILS => pht("A blog's details change."), PhameBlogTransaction::MAILTAG_SUBSCRIBERS => pht("A blog's subscribers change."), PhameBlogTransaction::MAILTAG_OTHER => pht('Other blog activity not listed above occurs.'), ); } protected function getMailSubjectPrefix() { return '[Phame]'; } protected function supportsSearch() { return false; } } diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index fa9828e5f..3082592a9 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -1,364 +1,345 @@ <?php final class PhameBlog extends PhameDAO implements PhabricatorPolicyInterface, PhabricatorMarkupInterface, PhabricatorSubscribableInterface, PhabricatorFlaggableInterface, PhabricatorProjectInterface, PhabricatorApplicationTransactionInterface { const MARKUP_FIELD_DESCRIPTION = 'markup:description'; const SKIN_DEFAULT = 'oblivious'; protected $name; protected $description; protected $domain; protected $configData; protected $creatorPHID; protected $viewPolicy; protected $editPolicy; - protected $joinPolicy; protected $mailKey; private static $requestBlog; protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'configData' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text64', 'description' => 'text', 'domain' => 'text128?', 'mailKey' => 'bytes20', // T6203/NULLABILITY // These policies should always be non-null. - 'joinPolicy' => 'policy?', 'editPolicy' => 'policy?', 'viewPolicy' => 'policy?', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'domain' => array( 'columns' => array('domain'), 'unique' => true, ), ), ) + parent::getConfiguration(); } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPhameBlogPHIDType::TYPECONST); } public static function initializeNewBlog(PhabricatorUser $actor) { $blog = id(new PhameBlog()) ->setCreatorPHID($actor->getPHID()) ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) - ->setEditPolicy(PhabricatorPolicies::POLICY_USER) - ->setJoinPolicy(PhabricatorPolicies::POLICY_USER); + ->setEditPolicy(PhabricatorPolicies::POLICY_USER); return $blog; } public function getSkinRenderer(AphrontRequest $request) { $spec = PhameSkinSpecification::loadOneSkinSpecification( $this->getSkin()); if (!$spec) { $spec = PhameSkinSpecification::loadOneSkinSpecification( self::SKIN_DEFAULT); } if (!$spec) { throw new Exception( pht( 'This blog has an invalid skin, and the default skin failed to '. 'load.')); } $skin = newv($spec->getSkinClass(), array()); $skin->setRequest($request); $skin->setSpecification($spec); return $skin; } /** * Makes sure a given custom blog uri is properly configured in DNS * to point at this Phabricator instance. If there is an error in * the configuration, return a string describing the error and how * to fix it. If there is no error, return an empty string. * * @return string */ public function validateCustomDomain($custom_domain) { $example_domain = 'blog.example.com'; $label = pht('Invalid'); // note this "uri" should be pretty busted given the desired input // so just use it to test if there's a protocol specified $uri = new PhutilURI($custom_domain); if ($uri->getProtocol()) { return array( $label, pht( 'The custom domain should not include a protocol. Just provide '. 'the bare domain name (for example, "%s").', $example_domain), ); } if ($uri->getPort()) { return array( $label, pht( 'The custom domain should not include a port number. Just provide '. 'the bare domain name (for example, "%s").', $example_domain), ); } if (strpos($custom_domain, '/') !== false) { return array( $label, pht( 'The custom domain should not specify a path (hosting a Phame '. 'blog at a path is currently not supported). Instead, just provide '. 'the bare domain name (for example, "%s").', $example_domain), ); } if (strpos($custom_domain, '.') === false) { return array( $label, pht( 'The custom domain should contain at least one dot (.) because '. 'some browsers fail to set cookies on domains without a dot. '. 'Instead, use a normal looking domain name like "%s".', $example_domain), ); } if (!PhabricatorEnv::getEnvConfig('policy.allow-public')) { $href = PhabricatorEnv::getProductionURI( '/config/edit/policy.allow-public/'); return array( pht('Fix Configuration'), pht( 'For custom domains to work, this Phabricator instance must be '. 'configured to allow the public access policy. Configure this '. 'setting %s, or ask an administrator to configure this setting. '. 'The domain can be specified later once this setting has been '. 'changed.', phutil_tag( 'a', array('href' => $href), pht('here'))), ); } return null; } public function getSkin() { $config = coalesce($this->getConfigData(), array()); return idx($config, 'skin', self::SKIN_DEFAULT); } public function setSkin($skin) { $config = coalesce($this->getConfigData(), array()); $config['skin'] = $skin; return $this->setConfigData($config); } public static function getSkinOptionsForSelect() { $classes = id(new PhutilSymbolLoader()) ->setAncestorClass('PhameBlogSkin') ->setType('class') ->setConcreteOnly(true) ->selectSymbolsWithoutLoading(); return ipull($classes, 'name', 'name'); } public static function setRequestBlog(PhameBlog $blog) { self::$requestBlog = $blog; } public static function getRequestBlog() { return self::$requestBlog; } public function getLiveURI(PhamePost $post = null) { if ($this->getDomain()) { $base = new PhutilURI('http://'.$this->getDomain().'/'); } else { $base = '/phame/live/'.$this->getID().'/'; $base = PhabricatorEnv::getURI($base); } if ($post) { $base .= '/post/'.$post->getPhameTitle(); } return $base; } public function getViewURI() { $uri = '/phame/blog/view/'.$this->getID().'/'; return PhabricatorEnv::getProductionURI($uri); } /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, - PhabricatorPolicyCapability::CAN_JOIN, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); - case PhabricatorPolicyCapability::CAN_JOIN: - return $this->getJoinPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $user) { $can_edit = PhabricatorPolicyCapability::CAN_EDIT; - $can_join = PhabricatorPolicyCapability::CAN_JOIN; switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: // Users who can edit or post to a blog can always view it. if (PhabricatorPolicyFilter::hasCapability($user, $this, $can_edit)) { return true; } - if (PhabricatorPolicyFilter::hasCapability($user, $this, $can_join)) { - return true; - } - break; - case PhabricatorPolicyCapability::CAN_JOIN: - // Users who can edit a blog can always post to it. - if (PhabricatorPolicyFilter::hasCapability($user, $this, $can_edit)) { - return true; - } break; } return false; } public function describeAutomaticCapability($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return pht( - 'Users who can edit or post on a blog can always view it.'); - case PhabricatorPolicyCapability::CAN_JOIN: - return pht( - 'Users who can edit a blog can always post on it.'); + 'Users who can edit a blog can always view it.'); } return null; } /* -( 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) { return $this->getDescription(); } public function didMarkupText( $field, $output, PhutilMarkupEngine $engine) { return $output; } public function shouldUseMarkupCache($field) { return (bool)$this->getPHID(); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhameBlogEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PhameBlogTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorSubscribableInterface Implementation )-------------------- */ public function isAutomaticallySubscribed($phid) { return ($this->creatorPHID == $phid); } public function shouldShowSubscribersProperty() { return true; } public function shouldAllowSubscription($phid) { return true; } } diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index ef39d2054..76681f2f5 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -1,307 +1,310 @@ <?php final class PhamePost extends PhameDAO implements PhabricatorPolicyInterface, PhabricatorMarkupInterface, PhabricatorFlaggableInterface, PhabricatorProjectInterface, PhabricatorApplicationTransactionInterface, PhabricatorSubscribableInterface, PhabricatorTokenReceiverInterface { const MARKUP_FIELD_BODY = 'markup:body'; const MARKUP_FIELD_SUMMARY = 'markup:summary'; protected $bloggerPHID; protected $title; protected $phameTitle; protected $body; protected $visibility; protected $configData; protected $datePublished; protected $blogPHID; protected $mailKey; private $blog; public static function initializePost( PhabricatorUser $blogger, PhameBlog $blog) { $post = id(new PhamePost()) ->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; } public function setCommentsWidget($widget) { $config_data = $this->getConfigData(); $config_data['comments_widget'] = $widget; return $this; } public function getCommentsWidget() { $config_data = $this->getConfigData(); if (empty($config_data)) { return 'none'; } return idx($config_data, 'comments_widget', 'none'); } 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(), ); } public function getCommentsWidgetOptionsForSelect() { $current = $this->getCommentsWidget(); $options = array(); if ($current == 'facebook' || PhabricatorFacebookAuthProvider::getFacebookApplicationID()) { $options['facebook'] = pht('Facebook'); } if ($current == 'disqus' || PhabricatorEnv::getEnvConfig('disqus.shortname')) { $options['disqus'] = pht('Disqus'); } $options['none'] = pht('None'); return $options; } /* -( 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 { - return PhabricatorPolicies::POLICY_NOONE; + } else if ($this->getBlog()) { + return $this->getBlog()->getEditPolicy(); } break; case PhabricatorPolicyCapability::CAN_EDIT: - return PhabricatorPolicies::POLICY_NOONE; + 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, and is the only user allowed - // to edit it. + // 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; } }