Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2893389
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
23 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/phame/controller/post/PhamePostArchiveController.php b/src/applications/phame/controller/post/PhamePostArchiveController.php
index b8647121ef..093e7019bf 100644
--- a/src/applications/phame/controller/post/PhamePostArchiveController.php
+++ b/src/applications/phame/controller/post/PhamePostArchiveController.php
@@ -1,56 +1,57 @@
<?php
final class PhamePostArchiveController extends PhamePostController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$post = id(new PhamePostQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$post) {
return new Aphront404Response();
}
$cancel_uri = $post->getViewURI();
if ($request->isFormPost()) {
$xactions = array();
$new_value = PhameConstants::VISIBILITY_ARCHIVED;
$xactions[] = id(new PhamePostTransaction())
->setTransactionType(PhamePostVisibilityTransaction::TRANSACTIONTYPE)
->setNewValue($new_value);
id(new PhamePostEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($post, $xactions);
return id(new AphrontRedirectResponse())
->setURI($cancel_uri);
}
$title = pht('Archive Post');
$body = pht(
- 'This post will revert to archived status and no longer be visible '.
- 'to other users or members of this blog.');
+ 'If you archive this post, it will only be visible to users who can '.
+ 'edit %s.',
+ $viewer->renderHandle($post->getBlogPHID()));
$button = pht('Archive Post');
return $this->newDialog()
->setTitle($title)
->appendParagraph($body)
->addSubmitButton($button)
->addCancelButton($cancel_uri);
}
}
diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php
index a73876a197..63adedb7ae 100644
--- a/src/applications/phame/controller/post/PhamePostViewController.php
+++ b/src/applications/phame/controller/post/PhamePostViewController.php
@@ -1,349 +1,353 @@
<?php
final class PhamePostViewController
extends PhameLiveController {
public function handleRequest(AphrontRequest $request) {
$response = $this->setupLiveEnvironment();
if ($response) {
return $response;
}
$viewer = $request->getViewer();
$moved = $request->getStr('moved');
$post = $this->getPost();
$blog = $this->getBlog();
$is_live = $this->getIsLive();
$is_external = $this->getIsExternal();
$header = id(new PHUIHeaderView())
->addClass('phame-header-bar')
->setUser($viewer);
$hero = $this->buildPhamePostHeader($post);
if (!$is_external) {
$actions = $this->renderActions($post);
$header->setPolicyObject($post);
$header->setActionList($actions);
}
$document = id(new PHUIDocumentViewPro())
->setHeader($header);
if ($moved) {
$document->appendChild(
id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->appendChild(pht('Post moved successfully.')));
}
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 "Publish" to publish this post.')));
+ pht(
+ 'This is a draft, and is only visible to you and other users '.
+ 'who can edit %s. Use "Publish" to publish this post.',
+ $viewer->renderHandle($post->getBlogPHID()))));
}
if ($post->isArchived()) {
$document->appendChild(
id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_ERROR)
->setTitle(pht('Archived Post'))
->appendChild(
- pht('Only you can see this archived post until you publish it. '.
- 'Use "Publish" to publish this post.')));
+ pht(
+ 'This post has been archived, and is only visible to you and '.
+ 'other users who can edit %s.',
+ $viewer->renderHandle($post->getBlogPHID()))));
}
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)));
$blogger = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($post->getBloggerPHID()))
->needProfileImage(true)
->executeOne();
$blogger_profile = $blogger->loadUserProfile();
$author_uri = '/p/'.$blogger->getUsername().'/';
$author_uri = PhabricatorEnv::getURI($author_uri);
$author = phutil_tag(
'a',
array(
'href' => $author_uri,
),
$blogger->getUsername());
$date = phabricator_datetime($post->getDatePublished(), $viewer);
if ($post->isDraft()) {
$subtitle = pht('Unpublished draft by %s.', $author);
} else if ($post->isArchived()) {
$subtitle = pht('Archived post by %s.', $author);
} else {
$subtitle = pht('Written by %s on %s.', $author, $date);
}
$user_icon = $blogger_profile->getIcon();
$user_icon = PhabricatorPeopleIconSet::getIconIcon($user_icon);
$user_icon = id(new PHUIIconView())->setIcon($user_icon);
$about = id(new PhameDescriptionView())
->setTitle($subtitle)
->setDescription(
array(
$user_icon,
' ',
$blogger_profile->getDisplayTitle(),
))
->setImage($blogger->getProfileImageURI())
->setImageHref($author_uri);
$monogram = $post->getMonogram();
$timeline = $this->buildTransactionTimeline(
$post,
id(new PhamePostTransactionQuery())
->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT)));
$timeline->setQuoteRef($monogram);
if ($is_external) {
$add_comment = null;
} else {
$add_comment = $this->buildCommentForm($post, $timeline);
$add_comment = phutil_tag_div('mlb mlt phame-comment-view', $add_comment);
}
$timeline = phutil_tag_div('phui-document-view-pro-box', $timeline);
list($prev, $next) = $this->loadAdjacentPosts($post);
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($post);
$is_live = $this->getIsLive();
$is_external = $this->getIsExternal();
$next_view = new PhameNextPostView();
if ($next) {
$next_view->setNext($next->getTitle(),
$next->getBestURI($is_live, $is_external));
}
if ($prev) {
$next_view->setPrevious($prev->getTitle(),
$prev->getBestURI($is_live, $is_external));
}
$document->setFoot($next_view);
$crumbs = $this->buildApplicationCrumbs();
$properties = phutil_tag_div('phui-document-view-pro-box', $properties);
$page = $this->newPage()
->setTitle($post->getTitle())
->setPageObjectPHIDs(array($post->getPHID()))
->setCrumbs($crumbs)
->appendChild(
array(
$hero,
$document,
$about,
$properties,
$timeline,
$add_comment,
));
if ($is_live) {
$page
->setShowChrome(false)
->setShowFooter(false);
}
return $page;
}
private function renderActions(PhamePost $post) {
$viewer = $this->getViewer();
$actions = id(new PhabricatorActionListView())
->setObject($post)
->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));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('fa-camera-retro')
->setHref($this->getApplicationURI('post/header/'.$id.'/'))
->setName(pht('Edit Header Image'))
->setDisabled(!$can_edit));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('fa-arrows')
->setHref($this->getApplicationURI('post/move/'.$id.'/'))
->setName(pht('Move Post'))
->setDisabled(!$can_edit)
->setWorkflow(true));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('fa-history')
->setHref($this->getApplicationURI('post/history/'.$id.'/'))
->setName(pht('View History')));
if ($post->isDraft()) {
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('fa-eye')
->setHref($this->getApplicationURI('post/publish/'.$id.'/'))
->setName(pht('Publish'))
->setDisabled(!$can_edit)
->setWorkflow(true));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('fa-ban')
->setHref($this->getApplicationURI('post/archive/'.$id.'/'))
->setName(pht('Archive'))
->setDisabled(!$can_edit)
->setWorkflow(true));
} else if ($post->isArchived()) {
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('fa-eye')
->setHref($this->getApplicationURI('post/publish/'.$id.'/'))
->setName(pht('Publish'))
->setDisabled(!$can_edit)
->setWorkflow(true));
} 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-ban')
->setHref($this->getApplicationURI('post/archive/'.$id.'/'))
->setName(pht('Archive'))
->setDisabled(!$can_edit)
->setWorkflow(true));
}
if ($post->isDraft()) {
$live_name = pht('Preview');
} else {
$live_name = pht('View Live');
}
if (!$post->isArchived()) {
$actions->addAction(
id(new PhabricatorActionView())
->setUser($viewer)
->setIcon('fa-globe')
->setHref($post->getLiveURI())
->setName($live_name));
}
return $actions;
}
private function buildCommentForm(PhamePost $post, $timeline) {
$viewer = $this->getViewer();
$box = id(new PhamePostEditEngine())
->setViewer($viewer)
->buildEditEngineCommentView($post)
->setTransactionTimeline($timeline);
return phutil_tag_div('phui-document-view-pro-box', $box);
}
private function loadAdjacentPosts(PhamePost $post) {
$viewer = $this->getViewer();
$query = id(new PhamePostQuery())
->setViewer($viewer)
->withVisibility(array(PhameConstants::VISIBILITY_PUBLISHED))
->withBlogPHIDs(array($post->getBlog()->getPHID()))
->setLimit(1);
$prev = id(clone $query)
->setAfterID($post->getID())
->execute();
$next = id(clone $query)
->setBeforeID($post->getID())
->execute();
return array(head($prev), head($next));
}
private function buildPhamePostHeader(
PhamePost $post) {
$image = null;
if ($post->getHeaderImagePHID()) {
$image = phutil_tag(
'div',
array(
'class' => 'phame-header-hero',
),
phutil_tag(
'img',
array(
'src' => $post->getHeaderImageURI(),
'class' => 'phame-header-image',
)));
}
$title = phutil_tag_div('phame-header-title', $post->getTitle());
$subtitle = null;
if ($post->getSubtitle()) {
$subtitle = phutil_tag_div('phame-header-subtitle', $post->getSubtitle());
}
return phutil_tag_div(
'phame-mega-header', array($image, $title, $subtitle));
}
}
diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php
index f87a37e7a4..a9525e0be7 100644
--- a/src/applications/phame/storage/PhamePost.php
+++ b/src/applications/phame/storage/PhamePost.php
@@ -1,389 +1,390 @@
<?php
final class PhamePost extends PhameDAO
implements
PhabricatorPolicyInterface,
PhabricatorMarkupInterface,
PhabricatorFlaggableInterface,
PhabricatorProjectInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorSubscribableInterface,
PhabricatorDestructibleInterface,
PhabricatorTokenReceiverInterface,
PhabricatorConduitResultInterface,
PhabricatorFulltextInterface {
const MARKUP_FIELD_BODY = 'markup:body';
const MARKUP_FIELD_SUMMARY = 'markup:summary';
protected $bloggerPHID;
protected $title;
protected $subtitle;
protected $phameTitle;
protected $body;
protected $visibility;
protected $configData;
protected $datePublished;
protected $blogPHID;
protected $mailKey;
protected $headerImagePHID;
private $blog = self::ATTACHABLE;
private $headerImageFile = self::ATTACHABLE;
public static function initializePost(
PhabricatorUser $blogger,
PhameBlog $blog) {
$post = id(new PhamePost())
->setBloggerPHID($blogger->getPHID())
->setBlogPHID($blog->getPHID())
->attachBlog($blog)
->setDatePublished(PhabricatorTime::getNow())
->setVisibility(PhameConstants::VISIBILITY_PUBLISHED);
return $post;
}
public function attachBlog(PhameBlog $blog) {
$this->blog = $blog;
return $this;
}
public function getBlog() {
return $this->assertAttached($this->blog);
}
public function getMonogram() {
return 'J'.$this->getID();
}
public function getLiveURI() {
$blog = $this->getBlog();
$is_draft = $this->isDraft();
$is_archived = $this->isArchived();
if (strlen($blog->getDomain()) && !$is_draft && !$is_archived) {
return $this->getExternalLiveURI();
} else {
return $this->getInternalLiveURI();
}
}
public function getExternalLiveURI() {
$id = $this->getID();
$slug = $this->getSlug();
$path = "/post/{$id}/{$slug}/";
$domain = $this->getBlog()->getDomain();
return (string)id(new PhutilURI('http://'.$domain))
->setPath($path);
}
public function getInternalLiveURI() {
$id = $this->getID();
$slug = $this->getSlug();
$blog_id = $this->getBlog()->getID();
return "/phame/live/{$blog_id}/post/{$id}/{$slug}/";
}
public function getViewURI() {
$id = $this->getID();
$slug = $this->getSlug();
return "/phame/post/view/{$id}/{$slug}/";
}
public function getBestURI($is_live, $is_external) {
if ($is_live) {
if ($is_external) {
return $this->getExternalLiveURI();
} else {
return $this->getInternalLiveURI();
}
} else {
return $this->getViewURI();
}
}
public function getEditURI() {
return '/phame/post/edit/'.$this->getID().'/';
}
public function isDraft() {
return ($this->getVisibility() == PhameConstants::VISIBILITY_DRAFT);
}
public function isArchived() {
return ($this->getVisibility() == PhameConstants::VISIBILITY_ARCHIVED);
}
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',
'subtitle' => 'text64',
'phameTitle' => 'sort64?',
'visibility' => 'uint32',
'mailKey' => 'bytes20',
'headerImagePHID' => 'phid?',
// 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,
),
'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 getSlug() {
return PhabricatorSlug::normalizeProjectSlug($this->getTitle());
}
public function getHeaderImageURI() {
return $this->getHeaderImageFile()->getBestURI();
}
public function attachHeaderImageFile(PhabricatorFile $file) {
$this->headerImageFile = $file;
return $this;
}
public function getHeaderImageFile() {
return $this->assertAttached($this->headerImageFile);
}
/* -( 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.
+ // Draft and archived posts are visible only to the author and other
+ // users who can edit the blog. Published posts are visible to whoever
+ // the blog is visible to.
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
if (!$this->isDraft() && !$this->isArchived() && $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) {
$content = $this->getMarkupText($field);
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
}
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);
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('title')
->setType('string')
->setDescription(pht('Title of the post.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('slug')
->setType('string')
->setDescription(pht('Slug for the post.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('blogPHID')
->setType('phid')
->setDescription(pht('PHID of the blog that the post belongs to.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('authorPHID')
->setType('phid')
->setDescription(pht('PHID of the author of the post.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('body')
->setType('string')
->setDescription(pht('Body of the post.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('datePublished')
->setType('epoch?')
->setDescription(pht('Publish date, if the post has been published.')),
);
}
public function getFieldValuesForConduit() {
if ($this->isDraft()) {
$date_published = null;
} else if ($this->isArchived()) {
$date_published = null;
} else {
$date_published = (int)$this->getDatePublished();
}
return array(
'title' => $this->getTitle(),
'slug' => $this->getSlug(),
'blogPHID' => $this->getBlogPHID(),
'authorPHID' => $this->getBloggerPHID(),
'body' => $this->getBody(),
'datePublished' => $date_published,
);
}
public function getConduitSearchAttachments() {
return array();
}
/* -( PhabricatorFulltextInterface )--------------------------------------- */
public function newFulltextEngine() {
return new PhamePostFulltextEngine();
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 18:18 (1 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127368
Default Alt Text
(23 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment