diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php
index e39176955a..1240351f11 100644
--- a/src/applications/phame/controller/post/PhamePostViewController.php
+++ b/src/applications/phame/controller/post/PhamePostViewController.php
@@ -1,206 +1,210 @@
id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$post = id(new PhamePostQuery())
->setViewer($user)
->withIDs(array($this->id))
->executeOne();
if (!$post) {
return new Aphront404Response();
}
$nav = $this->renderSideNavFilterView();
$this->loadHandles(
array(
$post->getBlogPHID(),
$post->getBloggerPHID(),
));
$actions = $this->renderActions($post, $user);
$properties = $this->renderProperties($post, $user);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($post->getTitle())
->setHref($this->getApplicationURI('post/view/'.$post->getID().'/')));
$nav->appendChild($crumbs);
$nav->appendChild(
id(new PhabricatorHeaderView())
->setHeader($post->getTitle()));
if ($post->isDraft()) {
$nav->appendChild(
id(new AphrontErrorView())
->setSeverity(AphrontErrorView::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()) {
$nav->appendChild(
id(new AphrontErrorView())
->setSeverity(AphrontErrorView::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.')));
}
$nav->appendChild(
array(
$actions,
$properties,
));
return $this->buildApplicationPage(
$nav,
array(
'title' => $post->getTitle(),
'device' => true,
'dust' => true,
));
}
private function renderActions(
PhamePost $post,
PhabricatorUser $user) {
$actions = id(new PhabricatorActionListView())
->setObject($post)
->setUser($user);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$post,
PhabricatorPolicyCapability::CAN_EDIT);
$id = $post->getID();
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setHref($this->getApplicationURI('post/edit/'.$id.'/'))
->setName(pht('Edit Post'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('move')
->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('preview')
->setHref($this->getApplicationURI('post/publish/'.$id.'/'))
->setName(pht('Preview / Publish')));
} else {
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('unpublish')
->setHref($this->getApplicationURI('post/unpublish/'.$id.'/'))
->setName(pht('Unpublish'))
->setWorkflow(true));
}
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('delete')
->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 = 'live/'.$blog->getID().'/post/'.$post->getPhameTitle();
} else {
$live_uri = 'post/notlive/'.$post->getID().'/';
}
$live_uri = $this->getApplicationURI($live_uri);
$actions->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setIcon('world')
->setHref($live_uri)
->setName(pht('View Live'))
->setRenderAsForm(true)
->setDisabled(!$can_view_live)
->setWorkflow(!$can_view_live));
return $actions;
}
private function renderProperties(
PhamePost $post,
PhabricatorUser $user) {
- $properties = new PhabricatorPropertyListView();
+ $properties = id(new PhabricatorPropertyListView())
+ ->setUser($user)
+ ->setObject($post);
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$post);
$properties->addProperty(
pht('Blog'),
$post->getBlogPHID()
? $this->getHandle($post->getBlogPHID())->renderLink()
: null);
$properties->addProperty(
pht('Blogger'),
$this->getHandle($post->getBloggerPHID())->renderLink());
$properties->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
$properties->addProperty(
pht('Published'),
$post->isDraft()
? pht('Draft')
: phabricator_datetime($post->getDatePublished(), $user));
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user)
->addObject($post, PhamePost::MARKUP_FIELD_BODY)
->process();
+ $properties->invokeWillRenderEvent();
+
$properties->addTextContent(
phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$engine->getOutput($post, PhamePost::MARKUP_FIELD_BODY)));
return $properties;
}
}
diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php
index 62414a60ec..3809679c94 100644
--- a/src/applications/phame/storage/PhamePost.php
+++ b/src/applications/phame/storage/PhamePost.php
@@ -1,185 +1,196 @@
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 isDraft() {
return $this->getVisibility() == self::VISIBILITY_DRAFT;
}
public function getHumanName() {
if ($this->isDraft()) {
$name = 'draft';
} else {
$name = 'post';
}
return $name;
}
public function getCommentsWidget() {
$config_data = $this->getConfigData();
if (empty($config_data)) {
return 'none';
}
return idx($config_data, 'comments_widget', 'none');
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'configData' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_POST);
}
public static function getVisibilityOptionsForSelect() {
return array(
self::VISIBILITY_DRAFT => 'Draft: visible only to me.',
self::VISIBILITY_PUBLISHED => 'Published: visible to the whole world.',
);
}
public function getCommentsWidgetOptionsForSelect() {
$current = $this->getCommentsWidget();
$options = array();
if ($current == 'facebook' ||
PhabricatorEnv::getEnvConfig('facebook.application-id')) {
$options['facebook'] = 'Facebook';
}
if ($current == 'disqus' ||
PhabricatorEnv::getEnvConfig('disqus.shortname')) {
$options['disqus'] = 'Disqus';
}
$options['none'] = '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;
}
break;
case PhabricatorPolicyCapability::CAN_EDIT:
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.
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
case PhabricatorPolicyCapability::CAN_EDIT:
return ($user->getPHID() == $this->getBloggerPHID());
}
}
/* -( 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();
}
+/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
+
+ public function getUsersToNotifyOfTokenGiven() {
+ return array(
+ $this->getBloggerPHID(),
+ );
+ }
+
}
diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php
index 3cb431dbee..58e1252dab 100644
--- a/src/applications/phriction/controller/PhrictionDocumentController.php
+++ b/src/applications/phriction/controller/PhrictionDocumentController.php
@@ -1,462 +1,454 @@
slug = $data['slug'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$slug = PhabricatorSlug::normalize($this->slug);
if ($slug != $this->slug) {
$uri = PhrictionDocument::getSlugURI($slug);
// Canonicalize pages to their one true URI.
return id(new AphrontRedirectResponse())->setURI($uri);
}
require_celerity_resource('phriction-document-css');
$document = id(new PhrictionDocument())->loadOneWhere(
'slug = %s',
$slug);
$version_note = null;
$core_content = '';
- $byline = '';
$move_notice = '';
+ $properties = null;
if (!$document) {
$document = new PhrictionDocument();
if (PhrictionDocument::isProjectSlug($slug)) {
$project = id(new PhabricatorProject())->loadOneWhere(
'phrictionSlug = %s',
PhrictionDocument::getProjectSlugIdentifier($slug));
if (!$project) {
return new Aphront404Response();
}
}
$create_uri = '/phriction/edit/?slug='.$slug;
- $no_content_head = pht('No content here!');
- $no_content_body = pht(
- 'No document found at %s. You can '.
- 'create a new document here.',
- phutil_tag('tt', array(), $slug),
- $create_uri);
-
- $no_content_text = hsprintf(
- '%s
%s',
- $no_content_head,
- $no_content_body);
-
- $page_content = phutil_tag(
- 'div',
- array('class' => 'phriction-content'),
- $no_content_text);
+ $notice = new AphrontErrorView();
+ $notice->setSeverity(AphrontErrorView::SEVERITY_NODATA);
+ $notice->setTitle(pht('No content here!'));
+ $notice->appendChild(
+ pht(
+ 'No document found at %s. You can '.
+ 'create a new document here.',
+ phutil_tag('tt', array(), $slug),
+ $create_uri));
+ $core_content = $notice;
+
$page_title = pht('Page Not Found');
} else {
$version = $request->getInt('v');
if ($version) {
$content = id(new PhrictionContent())->loadOneWhere(
'documentID = %d AND version = %d',
$document->getID(),
$version);
if (!$content) {
return new Aphront404Response();
}
if ($content->getID() != $document->getContentID()) {
$vdate = phabricator_datetime($content->getDateCreated(), $user);
$version_note = new AphrontErrorView();
$version_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$version_note->setTitle('Older Version');
$version_note->appendChild(
pht('You are viewing an older version of this document, as it '.
'appeared on %s.', $vdate));
}
} else {
$content = id(new PhrictionContent())->load($document->getContentID());
}
$page_title = $content->getTitle();
- $project_phid = null;
- if (PhrictionDocument::isProjectSlug($slug)) {
- $project = id(new PhabricatorProject())->loadOneWhere(
- 'phrictionSlug = %s',
- PhrictionDocument::getProjectSlugIdentifier($slug));
- if ($project) {
- $project_phid = $project->getPHID();
- }
- }
-
$subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$document->getPHID());
-
- $phids = array_filter(
- array(
- $content->getAuthorPHID(),
- $project_phid,
- ));
-
- if ($subscribers) {
- $phids = array_merge($phids, $subscribers);
- }
-
- $handles = $this->loadViewerHandles($phids);
-
- $age = time() - $content->getDateCreated();
- $age = floor($age / (60 * 60 * 24));
-
- if ($age < 1) {
- $when = pht('today');
- } else if ($age == 1) {
- $when = pht('yesterday');
- } else {
- $when = pht("%d days ago", $age);
- }
-
-
- $project_info = null;
- if ($project_phid) {
- $project_info = hsprintf(
- '
%s',
- pht('This document is about the project %s.',
- $handles[$project_phid]->renderLink()));
- }
-
- $subscriber_view = null;
- if ($subscribers) {
- $subcriber_list = array();
- foreach ($subscribers as $subscriber) {
- $subcriber_list[] = $handles[$subscriber];
- }
-
- $subcriber_list = phutil_implode_html(', ',
- mpull($subcriber_list, 'renderLink'));
-
- $subscriber_view = array(
- hsprintf('
Subscribers: '),
- $subcriber_list,
- );
- }
-
- $byline = hsprintf(
- '
%s%s%s
',
- pht('Last updated %s by %s.',
- $when,
- $handles[$content->getAuthorPHID()]->renderLink()),
- $project_info,
- $subscriber_view);
-
+ $properties = $this
+ ->buildPropertyListView($document, $content, $slug, $subscribers);
$doc_status = $document->getStatus();
$current_status = $content->getChangeType();
if ($current_status == PhrictionChangeType::CHANGE_EDIT ||
$current_status == PhrictionChangeType::CHANGE_MOVE_HERE) {
$core_content = $content->renderContent($user);
} else if ($current_status == PhrictionChangeType::CHANGE_DELETE) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('Document Deleted'));
$notice->appendChild(
pht('This document has been deleted. You can edit it to put new '.
'content here, or use history to revert to an earlier version.'));
$core_content = $notice->render();
} else if ($current_status == PhrictionChangeType::CHANGE_STUB) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('Empty Document'));
$notice->appendChild(
pht('This document is empty. You can edit it to put some proper '.
'content here.'));
$core_content = $notice->render();
} else if ($current_status == PhrictionChangeType::CHANGE_MOVE_AWAY) {
$new_doc_id = $content->getChangeRef();
$new_doc = new PhrictionDocument();
$new_doc->load($new_doc_id);
$slug_uri = PhrictionDocument::getSlugURI($new_doc->getSlug());
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('Document Moved'));
$notice->appendChild(phutil_tag('p', array(),
pht('This document has been moved to %s. You can edit it to put new '.
'content here, or use history to revert to an earlier version.',
phutil_tag('a', array('href' => $slug_uri), $slug_uri))));
$core_content = $notice->render();
} else {
throw new Exception("Unknown document status '{$doc_status}'!");
}
$move_notice = null;
if ($current_status == PhrictionChangeType::CHANGE_MOVE_HERE) {
$from_doc_id = $content->getChangeRef();
$from_doc = id(new PhrictionDocument())->load($from_doc_id);
$slug_uri = PhrictionDocument::getSlugURI($from_doc->getSlug());
$move_notice = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->appendChild(pht('This document was moved from %s',
phutil_tag('a', array('href' => $slug_uri), $slug_uri)))
->render();
}
-
}
if ($version_note) {
$version_note = $version_note->render();
}
$children = $this->renderDocumentChildren($slug);
$actions = $this->buildActionView($user, $document);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumb_views = $this->renderBreadcrumbs($slug);
foreach ($crumb_views as $view) {
$crumbs->addCrumb($view);
}
$header = id(new PhabricatorHeaderView())
->setHeader($page_title);
- $page_content = hsprintf(
- '',
- $header,
- $actions,
- $byline,
- $move_notice,
- $core_content);
+ $page_content = hsprintf(
+ '',
+ $header,
+ $actions,
+ $properties,
+ $move_notice,
+ $core_content);
$core_page = phutil_tag(
'div',
array(
'class' => 'phriction-offset'
),
array(
$page_content,
$children,
));
return $this->buildApplicationPage(
array(
$crumbs->render(),
$core_page,
),
array(
'title' => $page_title,
'device' => true,
'dust' => true,
));
}
+ private function buildPropertyListView(
+ PhrictionDocument $document,
+ PhrictionContent $content,
+ $slug,
+ array $subscribers) {
+
+ $viewer = $this->getRequest()->getUser();
+ $view = id(new PhabricatorPropertyListView())
+ ->setUser($viewer)
+ ->setObject($document);
+
+ $project_phid = null;
+ if (PhrictionDocument::isProjectSlug($slug)) {
+ $project = id(new PhabricatorProject())->loadOneWhere(
+ 'phrictionSlug = %s',
+ PhrictionDocument::getProjectSlugIdentifier($slug));
+ if ($project) {
+ $project_phid = $project->getPHID();
+ }
+ }
+
+ $phids = array_filter(
+ array(
+ $content->getAuthorPHID(),
+ $project_phid,
+ ));
+
+ if ($subscribers) {
+ $phids = array_merge($phids, $subscribers);
+ }
+
+ $this->loadHandles($phids);
+
+ $project_info = null;
+ if ($project_phid) {
+ $view->addProperty(
+ pht('Project Info'),
+ $this->getHandle($project_phid)->renderLink());
+ }
+
+ $view->addProperty(
+ pht('Last Author'),
+ $this->getHandle($content->getAuthorPHID())->renderLink());
+
+ $age = time() - $content->getDateCreated();
+ $age = floor($age / (60 * 60 * 24));
+ if ($age < 1) {
+ $when = pht('Today');
+ } else if ($age == 1) {
+ $when = pht('Yesterday');
+ } else {
+ $when = pht("%d Days Ago", $age);
+ }
+ $view->addProperty(pht('Last Updated'), $when);
+
+ if ($subscribers) {
+ $subscribers = $this->renderHandlesForPHIDs($subscribers);
+ $view->addProperty(pht('Subscribers'), $subscribers);
+ }
+
+ return $view;
+ }
+
private function buildActionView(
PhabricatorUser $user,
PhrictionDocument $document) {
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$document,
PhabricatorPolicyCapability::CAN_EDIT);
$slug = PhabricatorSlug::normalize($this->slug);
$action_view = id(new PhabricatorActionListView())
->setUser($user)
->setObject($document);
if (!$document->getID()) {
return $action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Create This Document'))
->setIcon('create')
->setHref('/phriction/edit/?slug='.$slug));
}
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Document'))
->setIcon('edit')
->setHref('/phriction/edit/'.$document->getID().'/'));
if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) {
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Move Document'))
->setIcon('move')
->setHref('/phriction/move/'.$document->getID().'/')
->setWorkflow(true));
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Delete Document'))
->setIcon('delete')
->setHref('/phriction/delete/'.$document->getID().'/')
->setWorkflow(true));
}
return
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('View History'))
->setIcon('history')
->setHref(PhrictionDocument::getSlugURI($slug, 'history')));
}
private function renderDocumentChildren($slug) {
$document_dao = new PhrictionDocument();
$content_dao = new PhrictionContent();
$conn = $document_dao->establishConnection('r');
$limit = 50;
$d_child = PhabricatorSlug::getDepth($slug) + 1;
$d_grandchild = PhabricatorSlug::getDepth($slug) + 2;
// Select children and grandchildren.
$children = queryfx_all(
$conn,
'SELECT d.slug, d.depth, c.title FROM %T d JOIN %T c
ON d.contentID = c.id
WHERE d.slug LIKE %> AND d.depth IN (%d, %d)
AND d.status IN (%Ld)
ORDER BY d.depth, c.title LIMIT %d',
$document_dao->getTableName(),
$content_dao->getTableName(),
($slug == '/' ? '' : $slug),
$d_child,
$d_grandchild,
array(
PhrictionDocumentStatus::STATUS_EXISTS,
PhrictionDocumentStatus::STATUS_STUB,
),
$limit);
if (!$children) {
return;
}
// We're going to render in one of three modes to try to accommodate
// different information scales:
//
// - If we found fewer than $limit rows, we know we have all the children
// and grandchildren and there aren't all that many. We can just render
// everything.
// - If we found $limit rows but the results included some grandchildren,
// we just throw them out and render only the children, as we know we
// have them all.
// - If we found $limit rows and the results have no grandchildren, we
// have a ton of children. Render them and then let the user know that
// this is not an exhaustive list.
if (count($children) == $limit) {
$more_children = true;
foreach ($children as $child) {
if ($child['depth'] == $d_grandchild) {
$more_children = false;
}
}
$show_grandchildren = false;
} else {
$show_grandchildren = true;
$more_children = false;
}
$grandchildren = array();
foreach ($children as $key => $child) {
if ($child['depth'] == $d_child) {
continue;
} else {
unset($children[$key]);
if ($show_grandchildren) {
$ancestors = PhabricatorSlug::getAncestry($child['slug']);
$grandchildren[end($ancestors)][] = $child;
}
}
}
// Fill in any missing children.
$known_slugs = ipull($children, null, 'slug');
foreach ($grandchildren as $slug => $ignored) {
if (empty($known_slugs[$slug])) {
$children[] = array(
'slug' => $slug,
'depth' => $d_child,
'title' => PhabricatorSlug::getDefaultTitle($slug),
'empty' => true,
);
}
}
$children = isort($children, 'title');
$list = array();
foreach ($children as $child) {
$list[] = hsprintf('');
$list[] = $this->renderChildDocumentLink($child);
$grand = idx($grandchildren, $child['slug'], array());
if ($grand) {
$list[] = hsprintf('');
foreach ($grand as $grandchild) {
$list[] = hsprintf('- ');
$list[] = $this->renderChildDocumentLink($grandchild);
$list[] = hsprintf('
');
}
$list[] = hsprintf('
');
}
$list[] = hsprintf('');
}
if ($more_children) {
$list[] = phutil_tag('li', array(), pht('More...'));
}
return hsprintf(
'',
pht('Document Hierarchy'),
phutil_tag('ul', array(), $list));
}
private function renderChildDocumentLink(array $info) {
$title = nonempty($info['title'], pht('(Untitled Document)'));
$item = phutil_tag(
'a',
array(
'href' => PhrictionDocument::getSlugURI($info['slug']),
),
$title);
if (isset($info['empty'])) {
$item = phutil_tag('em', array(), $item);
}
return $item;
}
protected function getDocumentSlug() {
return $this->slug;
}
}
diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php
index f3bc38a705..af74769f29 100644
--- a/src/applications/phriction/storage/PhrictionDocument.php
+++ b/src/applications/phriction/storage/PhrictionDocument.php
@@ -1,132 +1,141 @@
true,
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_WIKI);
}
public static function getSlugURI($slug, $type = 'document') {
static $types = array(
'document' => '/w/',
'history' => '/phriction/history/',
);
if (empty($types[$type])) {
throw new Exception("Unknown URI type '{$type}'!");
}
$prefix = $types[$type];
if ($slug == '/') {
return $prefix;
} else {
// NOTE: The effect here is to escape non-latin characters, since modern
// browsers deal with escaped UTF8 characters in a reasonable way (showing
// the user a readable URI) but older programs may not.
$slug = phutil_escape_uri($slug);
return $prefix.$slug;
}
}
public function setSlug($slug) {
$this->slug = PhabricatorSlug::normalize($slug);
$this->depth = PhabricatorSlug::getDepth($slug);
return $this;
}
public function attachContent(PhrictionContent $content) {
$this->contentObject = $content;
return $this;
}
public function getContent() {
if (!$this->contentObject) {
throw new Exception("Attach content with attachContent() first.");
}
return $this->contentObject;
}
public function getProject() {
if ($this->project === null) {
throw new Exception("Call attachProject() before getProject().");
}
return $this->project;
}
public function attachProject(PhabricatorProject $project) {
$this->project = $project;
return $this;
}
public function hasProject() {
return (bool)$this->project;
}
public static function isProjectSlug($slug) {
$slug = PhabricatorSlug::normalize($slug);
$prefix = 'projects/';
if ($slug == $prefix) {
// The 'projects/' document is not itself a project slug.
return false;
}
return !strncmp($slug, $prefix, strlen($prefix));
}
public static function getProjectSlugIdentifier($slug) {
if (!self::isProjectSlug($slug)) {
throw new Exception("Slug '{$slug}' is not a project slug!");
}
$slug = PhabricatorSlug::normalize($slug);
$parts = explode('/', $slug);
return $parts[1].'/';
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
if ($this->hasProject()) {
return $this->getProject()->getPolicy($capability);
}
return PhabricatorPolicies::POLICY_USER;
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
if ($this->hasProject()) {
return $this->getProject()->hasAutomaticCapability($capability, $user);
}
return false;
}
public function isAutomaticallySubscribed($phid) {
return false;
}
+
+/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
+
+ public function getUsersToNotifyOfTokenGiven() {
+ return PhabricatorSubscribersQuery::loadSubscribersForPHID($this->phid);
+ }
}
diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php
index 758a0a6673..29ad9532c1 100644
--- a/src/applications/ponder/controller/PonderQuestionViewController.php
+++ b/src/applications/ponder/controller/PonderQuestionViewController.php
@@ -1,136 +1,137 @@
questionID = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$question = PonderQuestionQuery::loadSingle($user, $this->questionID);
if (!$question) {
return new Aphront404Response();
}
$question->attachRelated();
$question->attachVotes($user->getPHID());
$object_phids = array($user->getPHID(), $question->getAuthorPHID());
$answers = $question->getAnswers();
$comments = $question->getComments();
foreach ($comments as $comment) {
$object_phids[] = $comment->getAuthorPHID();
}
foreach ($answers as $answer) {
$object_phids[] = $answer->getAuthorPHID();
$comments = $answer->getComments();
foreach ($comments as $comment) {
$object_phids[] = $comment->getAuthorPHID();
}
}
$subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$question->getPHID());
$object_phids = array_merge($object_phids, $subscribers);
$this->loadHandles($object_phids);
$handles = $this->getLoadedHandles();
$detail_panel = new PonderQuestionDetailView();
$detail_panel
->setQuestion($question)
->setUser($user)
->setHandles($handles);
$responses_panel = new PonderAnswerListView();
$responses_panel
->setQuestion($question)
->setHandles($handles)
->setUser($user)
->setAnswers($answers);
$answer_add_panel = new PonderAddAnswerView();
$answer_add_panel
->setQuestion($question)
->setUser($user)
->setActionURI("/ponder/answer/add/");
$header = id(new PhabricatorHeaderView())
->setHeader($question->getTitle());
$actions = $this->buildActionListView($question);
$properties = $this->buildPropertyListView($question, $subscribers);
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('Q'.$this->questionID)
->setHref('/Q'.$this->questionID));
$nav = $this->buildSideNavView($question);
$nav->appendChild(
array(
$crumbs,
$header,
$actions,
$properties,
$detail_panel,
$responses_panel,
$answer_add_panel
));
$nav->selectFilter(null);
return $this->buildApplicationPage(
$nav,
array(
'device' => true,
'title' => 'Q'.$question->getID().' '.$question->getTitle()
));
}
private function buildActionListView(PonderQuestion $question) {
$viewer = $this->getRequest()->getUser();
$view = new PhabricatorActionListView();
$view->setUser($viewer);
$view->setObject($question);
return $view;
}
private function buildPropertyListView(
PonderQuestion $question,
array $subscribers) {
$viewer = $this->getRequest()->getUser();
- $view = new PhabricatorPropertyListView();
-
+ $view = id(new PhabricatorPropertyListView())
+ ->setUser($viewer)
+ ->setObject($question);
$view->addProperty(
pht('Author'),
$this->getHandle($question->getAuthorPHID())->renderLink());
$view->addProperty(
pht('Created'),
phabricator_datetime($question->getDateCreated(), $viewer));
if ($subscribers) {
$subscribers = $this->renderHandlesForPHIDs($subscribers);
}
$view->addProperty(
pht('Subscribers'),
nonempty($subscribers, phutil_tag('em', array(), pht('None'))));
return $view;
}
}
diff --git a/src/applications/ponder/storage/PonderQuestion.php b/src/applications/ponder/storage/PonderQuestion.php
index 9028681059..88160a35a1 100644
--- a/src/applications/ponder/storage/PonderQuestion.php
+++ b/src/applications/ponder/storage/PonderQuestion.php
@@ -1,193 +1,202 @@
true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_QUES);
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source->serialize();
return $this;
}
public function getContentSource() {
return PhabricatorContentSource::newFromSerialized($this->contentSource);
}
public function attachRelated() {
$this->answers = $this->loadRelatives(new PonderAnswer(), "questionID");
$qa_phids = mpull($this->answers, 'getPHID') + array($this->getPHID());
if ($qa_phids) {
$comments = id(new PonderCommentQuery())
->withTargetPHIDs($qa_phids)
->execute();
$comments = mgroup($comments, 'getTargetPHID');
} else {
$comments = array();
}
$this->setComments(idx($comments, $this->getPHID(), array()));
foreach ($this->answers as $answer) {
$answer->setQuestion($this);
$answer->setComments(idx($comments, $answer->getPHID(), array()));
}
}
public function attachVotes($user_phid) {
$qa_phids = mpull($this->answers, 'getPHID') + array($this->getPHID());
$edges = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($user_phid))
->withDestinationPHIDs($qa_phids)
->withEdgeTypes(
array(
PhabricatorEdgeConfig::TYPE_VOTING_USER_HAS_QUESTION,
PhabricatorEdgeConfig::TYPE_VOTING_USER_HAS_ANSWER
))
->needEdgeData(true)
->execute();
$question_edge =
$edges[$user_phid][PhabricatorEdgeConfig::TYPE_VOTING_USER_HAS_QUESTION];
$answer_edges =
$edges[$user_phid][PhabricatorEdgeConfig::TYPE_VOTING_USER_HAS_ANSWER];
$edges = null;
$this->setUserVote(idx($question_edge, $this->getPHID()));
foreach ($this->answers as $answer) {
$answer->setUserVote(idx($answer_edges, $answer->getPHID()));
}
}
public function setUserVote($vote) {
$this->vote = $vote['data'];
if (!$this->vote) {
$this->vote = PonderConstants::NONE_VOTE;
}
return $this;
}
public function getUserVote() {
return $this->vote;
}
public function setComments($comments) {
$this->comments = $comments;
return $this;
}
public function getComments() {
return $this->comments;
}
public function getAnswers() {
return $this->answers;
}
public function getMarkupField() {
return self::MARKUP_FIELD_CONTENT;
}
// Markup interface
public function getMarkupFieldKey($field) {
$hash = PhabricatorHash::digest($this->getMarkupText($field));
$id = $this->getID();
return "ponder:Q{$id}:{$field}:{$hash}";
}
public function getMarkupText($field) {
return $this->getContent();
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newPonderMarkupEngine();
}
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
return (bool)$this->getID();
}
// votable interface
public function getUserVoteEdgeType() {
return PhabricatorEdgeConfig::TYPE_VOTING_USER_HAS_QUESTION;
}
public function getVotablePHID() {
return $this->getPHID();
}
public function isAutomaticallySubscribed($phid) {
return false;
}
public function save() {
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
$policy = PhabricatorPolicies::POLICY_NOONE;
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$policy = PhabricatorPolicies::POLICY_USER;
break;
}
return $policy;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
+/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
+
+ public function getUsersToNotifyOfTokenGiven() {
+ return array(
+ $this->getAuthorPHID(),
+ );
+ }
+
}
diff --git a/src/applications/subscriptions/query/PhabricatorSubscribersQuery.php b/src/applications/subscriptions/query/PhabricatorSubscribersQuery.php
index 0870fe27ee..73a5bf5a4b 100644
--- a/src/applications/subscriptions/query/PhabricatorSubscribersQuery.php
+++ b/src/applications/subscriptions/query/PhabricatorSubscribersQuery.php
@@ -1,54 +1,52 @@
withObjectPHIDs(array($phid))
->execute();
return $subscribers[$phid];
}
public function withObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function withSubscriberPHIDs(array $subscriber_phids) {
$this->subscriberPHIDs = $subscriber_phids;
return $this;
}
public function execute() {
$query = new PhabricatorEdgeQuery();
$edge_type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_SUBSCRIBER;
$query->withSourcePHIDs($this->objectPHIDs);
$query->withEdgeTypes(array($edge_type));
if ($this->subscriberPHIDs) {
$query->withDestinationPHIDs($this->subscriberPHIDs);
}
$edges = $query->execute();
$results = array_fill_keys($this->objectPHIDs, array());
foreach ($edges as $src => $edge_types) {
foreach ($edge_types[$edge_type] as $dst => $data) {
$results[$src][] = $dst;
}
}
return $results;
}
-
-
}
diff --git a/webroot/rsrc/css/application/phriction/phriction-document-css.css b/webroot/rsrc/css/application/phriction/phriction-document-css.css
index f2ea174839..899ae2b476 100644
--- a/webroot/rsrc/css/application/phriction/phriction-document-css.css
+++ b/webroot/rsrc/css/application/phriction/phriction-document-css.css
@@ -1,163 +1,152 @@
/**
* @provides phriction-document-css
*/
.phriction-header {
background: #eeeeee;
border-bottom: 1px solid #dddddd;
padding: 1em;
}
.phriction-header a.button {
float: right;
margin: 0em 0em 0em 1%;
}
.phriction-header h1 {
margin: .25em 0;
}
.device-desktop .phriction-offset {
padding-right: 160px;
}
.phriction-wrap {
margin-bottom: 20px;
}
.device-desktop .phriction-wrap {
position: relative;
border-left: 1px solid #e7e7e7;
border-right: 1px solid #e7e7e7;
border-bottom: 1px solid #c0c5d1;
max-width: 800px;
margin: 20px auto;
}
.phriction-content {
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
min-height: 240px;
background: #fff;
}
.device-desktop .phriction-wrap {
}
.device-desktop .phriction-content .phabricator-action-list-view {
margin: 10px 10px 0 0;
background: #f7f7f7;
}
.device-phone .phriction-content .phabricator-action-list-view {
margin: 0;
border-bottom: 1px solid #c0c5d1;
background: #f7f7f7;
}
.phriction-content .phabricator-header-shell {
border-top: none;
}
.phriction-content .phabricator-remarkup {
padding: 20px;
}
-.phriction-byline {
- padding: 10px 20px;
- color: #777;
- font-size: 11px;
- background: #f7f7f7;
-}
-
-.device-phone .phriction-byline {
- padding: 10px;
-}
-
.device-phone .phriction-content .phabricator-remarkup {
padding: 10px;
}
.phriction-content .phriction-link {
font-weight: bold;
}
.phriction-breadcrumbs {
font-size: 12px;
color: #666666;
}
.phriction-document-crumbs a {
font-weight: bold;
}
.phriction-children {
max-width: 800px;
background: #fff;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.phriction-children ul {
margin-left: 30px;
padding-bottom: 10px;
list-style: circle;
color: #999999;
}
.phriction-children-header {
background: #f7f7f7;
padding: 10px 0 10px 20px;
font-weight: bold;
margin-bottom: 15px;
}
.device-desktop .phriction-content .phabricator-action-list-view {
position: absolute;
top: 50px;
right: -172px;
float: none;
background: #fff;
border-radius: 0;
box-shadow: none;
border: none;
border-top: 1px solid #e7e7e7;
border-bottom: 1px solid #e7e7e7;
border-right: 1px solid #e7e7e7;
width: 160px;
}
.phriction-document-preview-header {
color: #666666;
margin-bottom: 1em;
font-size: 11px;
}
.phriction-document-history-diff {
padding: 0 2em 2em;
}
.phriction-revert-table {
width: 100%;
}
.phriction-revert-table td {
text-align: center;
width: 50%;
padding: .5em 0;
}
.phriction-history-nav-table {
width: 100%;
}
.phriction-history-nav-table td {
width: 50%;
color: #444444;
}
.phriction-history-nav-table td.nav-next {
text-align: right;
}
.device-phone .phriction-content .phabricator-remarkup-toc {
width: 120px;
}