diff --git a/src/applications/phame/controller/post/PhamePostEditController.php b/src/applications/phame/controller/post/PhamePostEditController.php index 145c291efb..2f7d8572de 100644 --- a/src/applications/phame/controller/post/PhamePostEditController.php +++ b/src/applications/phame/controller/post/PhamePostEditController.php @@ -1,211 +1,211 @@ <?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_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(); $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'); $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(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) + ->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 AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($submit_button)); $preview = id(new PHUIRemarkupPreviewPanel()) ->setHeader($post->getTitle()) ->setPreviewURI($this->getApplicationURI('post/preview/')) ->setControlID('post-body') ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT); Javelin::initBehavior( 'phame-post-preview', array( 'title' => 'post-title', 'phame_title' => 'post-phame-title', )); $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, )); } } diff --git a/src/applications/phame/editor/PhamePostEditor.php b/src/applications/phame/editor/PhamePostEditor.php index ce00e5ed81..e3cd1a4417 100644 --- a/src/applications/phame/editor/PhamePostEditor.php +++ b/src/applications/phame/editor/PhamePostEditor.php @@ -1,255 +1,255 @@ <?php final class PhamePostEditor extends PhabricatorApplicationTransactionEditor { public function getEditorApplicationClass() { return 'PhabricatorPhameApplication'; } public function getEditorObjectsDescription() { return pht('Phame Posts'); } public function getTransactionTypes() { $types = parent::getTransactionTypes(); $types[] = PhamePostTransaction::TYPE_TITLE; $types[] = PhamePostTransaction::TYPE_PHAME_TITLE; $types[] = PhamePostTransaction::TYPE_BODY; $types[] = PhamePostTransaction::TYPE_VISIBILITY; $types[] = PhabricatorTransactions::TYPE_COMMENT; return $types; } protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhamePostTransaction::TYPE_TITLE: return $object->getTitle(); case PhamePostTransaction::TYPE_PHAME_TITLE: return $object->getPhameTitle(); case PhamePostTransaction::TYPE_BODY: return $object->getBody(); case PhamePostTransaction::TYPE_VISIBILITY: return $object->getVisibility(); } } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhamePostTransaction::TYPE_TITLE: case PhamePostTransaction::TYPE_PHAME_TITLE: case PhamePostTransaction::TYPE_BODY: case PhamePostTransaction::TYPE_VISIBILITY: return $xaction->getNewValue(); } } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhamePostTransaction::TYPE_TITLE: return $object->setTitle($xaction->getNewValue()); case PhamePostTransaction::TYPE_PHAME_TITLE: return $object->setPhameTitle($xaction->getNewValue()); case PhamePostTransaction::TYPE_BODY: return $object->setBody($xaction->getNewValue()); case PhamePostTransaction::TYPE_VISIBILITY: if ($xaction->getNewValue() == PhameConstants::VISIBILITY_DRAFT) { $object->setDatePublished(0); } else { - $object->setDatePublished(time()); + $object->setDatePublished(PhabricatorTime::getNow()); } return $object->setVisibility($xaction->getNewValue()); } return parent::applyCustomInternalTransaction($object, $xaction); } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhamePostTransaction::TYPE_TITLE: case PhamePostTransaction::TYPE_PHAME_TITLE: case PhamePostTransaction::TYPE_BODY: case PhamePostTransaction::TYPE_VISIBILITY: return; } return parent::applyCustomExternalTransaction($object, $xaction); } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhamePostTransaction::TYPE_TITLE: $missing = $this->validateIsEmptyTextField( $object->getTitle(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('Title is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } break; case PhamePostTransaction::TYPE_PHAME_TITLE: if (!$xactions) { continue; } $missing = $this->validateIsEmptyTextField( $object->getPhameTitle(), $xactions); $phame_title = last($xactions)->getNewValue(); if ($missing || $phame_title == '/') { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('Phame title is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } $duplicate_post = id(new PhamePostQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPhameTitles(array($phame_title)) ->executeOne(); if ($duplicate_post && $duplicate_post->getID() != $object->getID()) { $error_text = pht( 'Phame title must be unique; another post already has this phame '. 'title.'); $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Not Unique'), $error_text, nonempty(last($xactions), null)); $errors[] = $error; } break; } return $errors; } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { if ($object->isDraft()) { return false; } return true; } protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { if ($object->isDraft()) { return false; } return true; } protected function getMailTo(PhabricatorLiskDAO $object) { $phids = array(); $phids[] = $object->getBloggerPHID(); $phids[] = $this->requireActor()->getPHID(); $blog_phid = $object->getBlogPHID(); if ($blog_phid) { $cc_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( $blog_phid); foreach ($cc_phids as $cc) { $phids[] = $cc; } } return $phids; } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $phid = $object->getPHID(); $title = $object->getTitle(); return id(new PhabricatorMetaMTAMail()) ->setSubject($title) ->addHeader('Thread-Topic', $phid); } protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new PhamePostReplyHandler()) ->setMailReceiver($object); } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $body = parent::buildMailBody($object, $xactions); // We don't send mail if the object is a draft, and we only want // to include the full body of the post on the either the // first creation or if it was created as a draft, once it goes live. if ($this->getIsNewObject()) { $body->addRemarkupSection(null, $object->getBody()); } else { foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PhamePostTransaction::TYPE_VISIBILITY: if (!$object->isDraft()) { $body->addRemarkupSection(null, $object->getBody()); } break; } } } $body->addLinkSection( pht('POST DETAIL'), PhabricatorEnv::getProductionURI($object->getViewURI())); return $body; } public function getMailTagsMap() { return array( PhamePostTransaction::MAILTAG_CONTENT => pht("A post's content changes."), PhamePostTransaction::MAILTAG_COMMENT => pht('Someone comments on a post.'), PhamePostTransaction::MAILTAG_OTHER => pht('Other post activity not listed above occurs.'), ); } protected function getMailSubjectPrefix() { return '[Phame]'; } protected function supportsSearch() { return false; } } diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index 3bc03df949..3212f128b2 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -1,294 +1,294 @@ <?php final class PhamePost extends PhameDAO implements PhabricatorPolicyInterface, PhabricatorMarkupInterface, PhabricatorFlaggableInterface, PhabricatorProjectInterface, PhabricatorApplicationTransactionInterface, PhabricatorSubscribableInterface, PhabricatorDestructibleInterface, 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) + ->setDatePublished(PhabricatorTime::getNow()) ->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; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $this->delete(); $this->saveTransaction(); } /* -( 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; } }