mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-26 23:40:57 +01:00
Phame - introduce blogs
Summary: blogs are collections of posts. a blog also has metadata like a name, description and "bloggers" that can edit the metadata of the blog and contribute posts. changes include the post edit flow where bloggers can now select which blogs to publish to. also made various small tweaks throughout the UI to make things sensical and clean as the concept of blogs is introduced. there's edges powering this stuff. bloggers <=> blogs and posts <=> blogs in particular. Test Plan: made blogs, deleted blogs, tried to make blogs with no bloggers. all went well. verified ui to publish only showed up for public posts, published posts to blogs, un-published posts to blogs, re-published posts to blogs, deleted posts and verified they disappeared from blogs. Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T1373 Differential Revision: https://secure.phabricator.com/D3003
This commit is contained in:
parent
ee709a0543
commit
ae13d33859
29 changed files with 1711 additions and 134 deletions
31
resources/sql/patches/phameblog.sql
Normal file
31
resources/sql/patches/phameblog.sql
Normal file
|
@ -0,0 +1,31 @@
|
|||
CREATE TABLE {$NAMESPACE}_phame.phame_blog (
|
||||
`id` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`phid` VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
`name` VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
`description` LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||
`configData` LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||
`creatorPHID` VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
`dateCreated` INT UNSIGNED NOT NULL,
|
||||
`dateModified` INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY (`phid`)
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
||||
|
||||
CREATE TABLE {$NAMESPACE}_phame.edge (
|
||||
src VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
type VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
dst VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
seq INT UNSIGNED NOT NULL,
|
||||
dataID INT UNSIGNED,
|
||||
PRIMARY KEY (src, type, dst),
|
||||
KEY (src, type, dateCreated, seq)
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
||||
|
||||
CREATE TABLE {$NAMESPACE}_phame.edgedata (
|
||||
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
data LONGTEXT NOT NULL COLLATE utf8_bin
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
||||
|
||||
ALTER TABLE {$NAMESPACE}_phame.phame_post
|
||||
ADD KEY `instancePosts` (`visibility`, `datePublished`, `id`);
|
||||
|
|
@ -1424,6 +1424,17 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-watch-anchor.js',
|
||||
),
|
||||
'javelin-behavior-phame-post-blogs' =>
|
||||
array(
|
||||
'uri' => '/res/a7f7756c/rsrc/js/application/phame/phame-post-blogs.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'javelin-dom',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/phame/phame-post-blogs.js',
|
||||
),
|
||||
'javelin-behavior-phame-post-preview' =>
|
||||
array(
|
||||
'uri' => '/res/ac4c503a/rsrc/js/application/phame/phame-post-preview.js',
|
||||
|
@ -2322,7 +2333,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'phabricator-standard-page-view' =>
|
||||
array(
|
||||
'uri' => '/res/ee3cd0f2/rsrc/css/application/base/standard-page-view.css',
|
||||
'uri' => '/res/de559ba2/rsrc/css/application/base/standard-page-view.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -2577,7 +2588,7 @@ celerity_register_resource_map(array(
|
|||
), array(
|
||||
'packages' =>
|
||||
array(
|
||||
'3cdc275f' =>
|
||||
'78836d86' =>
|
||||
array(
|
||||
'name' => 'core.pkg.css',
|
||||
'symbols' =>
|
||||
|
@ -2606,7 +2617,7 @@ celerity_register_resource_map(array(
|
|||
21 => 'phabricator-flag-css',
|
||||
22 => 'aphront-error-view-css',
|
||||
),
|
||||
'uri' => '/res/pkg/3cdc275f/core.pkg.css',
|
||||
'uri' => '/res/pkg/78836d86/core.pkg.css',
|
||||
'type' => 'css',
|
||||
),
|
||||
'f363b322' =>
|
||||
|
@ -2773,20 +2784,20 @@ celerity_register_resource_map(array(
|
|||
'reverse' =>
|
||||
array(
|
||||
'aphront-attached-file-view-css' => '7839ae2d',
|
||||
'aphront-crumbs-view-css' => '3cdc275f',
|
||||
'aphront-dialog-view-css' => '3cdc275f',
|
||||
'aphront-error-view-css' => '3cdc275f',
|
||||
'aphront-form-view-css' => '3cdc275f',
|
||||
'aphront-crumbs-view-css' => '78836d86',
|
||||
'aphront-dialog-view-css' => '78836d86',
|
||||
'aphront-error-view-css' => '78836d86',
|
||||
'aphront-form-view-css' => '78836d86',
|
||||
'aphront-headsup-action-list-view-css' => '96bc37d6',
|
||||
'aphront-headsup-view-css' => '3cdc275f',
|
||||
'aphront-list-filter-view-css' => '3cdc275f',
|
||||
'aphront-pager-view-css' => '3cdc275f',
|
||||
'aphront-panel-view-css' => '3cdc275f',
|
||||
'aphront-side-nav-view-css' => '3cdc275f',
|
||||
'aphront-table-view-css' => '3cdc275f',
|
||||
'aphront-tokenizer-control-css' => '3cdc275f',
|
||||
'aphront-tooltip-css' => '3cdc275f',
|
||||
'aphront-typeahead-control-css' => '3cdc275f',
|
||||
'aphront-headsup-view-css' => '78836d86',
|
||||
'aphront-list-filter-view-css' => '78836d86',
|
||||
'aphront-pager-view-css' => '78836d86',
|
||||
'aphront-panel-view-css' => '78836d86',
|
||||
'aphront-side-nav-view-css' => '78836d86',
|
||||
'aphront-table-view-css' => '78836d86',
|
||||
'aphront-tokenizer-control-css' => '78836d86',
|
||||
'aphront-tooltip-css' => '78836d86',
|
||||
'aphront-typeahead-control-css' => '78836d86',
|
||||
'differential-changeset-view-css' => '96bc37d6',
|
||||
'differential-core-view-css' => '96bc37d6',
|
||||
'differential-inline-comment-editor' => 'f4bbbd84',
|
||||
|
@ -2852,15 +2863,15 @@ celerity_register_resource_map(array(
|
|||
'javelin-workflow' => 'f363b322',
|
||||
'maniphest-task-summary-css' => '7839ae2d',
|
||||
'maniphest-transaction-detail-css' => '7839ae2d',
|
||||
'phabricator-app-buttons-css' => '3cdc275f',
|
||||
'phabricator-app-buttons-css' => '78836d86',
|
||||
'phabricator-content-source-view-css' => '96bc37d6',
|
||||
'phabricator-core-buttons-css' => '3cdc275f',
|
||||
'phabricator-core-css' => '3cdc275f',
|
||||
'phabricator-directory-css' => '3cdc275f',
|
||||
'phabricator-core-buttons-css' => '78836d86',
|
||||
'phabricator-core-css' => '78836d86',
|
||||
'phabricator-directory-css' => '78836d86',
|
||||
'phabricator-drag-and-drop-file-upload' => 'f4bbbd84',
|
||||
'phabricator-dropdown-menu' => 'f363b322',
|
||||
'phabricator-flag-css' => '3cdc275f',
|
||||
'phabricator-jump-nav' => '3cdc275f',
|
||||
'phabricator-flag-css' => '78836d86',
|
||||
'phabricator-jump-nav' => '78836d86',
|
||||
'phabricator-keyboard-shortcut' => 'f363b322',
|
||||
'phabricator-keyboard-shortcut-manager' => 'f363b322',
|
||||
'phabricator-menu-item' => 'f363b322',
|
||||
|
@ -2868,11 +2879,11 @@ celerity_register_resource_map(array(
|
|||
'phabricator-paste-file-upload' => 'f363b322',
|
||||
'phabricator-prefab' => 'f363b322',
|
||||
'phabricator-project-tag-css' => '7839ae2d',
|
||||
'phabricator-remarkup-css' => '3cdc275f',
|
||||
'phabricator-remarkup-css' => '78836d86',
|
||||
'phabricator-shaped-request' => 'f4bbbd84',
|
||||
'phabricator-standard-page-view' => '3cdc275f',
|
||||
'phabricator-standard-page-view' => '78836d86',
|
||||
'phabricator-tooltip' => 'f363b322',
|
||||
'phabricator-transaction-view-css' => '3cdc275f',
|
||||
'syntax-highlighting-css' => '3cdc275f',
|
||||
'phabricator-transaction-view-css' => '78836d86',
|
||||
'syntax-highlighting-css' => '78836d86',
|
||||
),
|
||||
));
|
||||
|
|
|
@ -1051,7 +1051,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/PhabricatorXHProfProfileSymbolView.php',
|
||||
'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/PhabricatorXHProfProfileTopLevelView.php',
|
||||
'PhabricatorXHProfProfileView' => 'applications/xhprof/view/PhabricatorXHProfProfileView.php',
|
||||
'PhameAllBloggersPostListController' => 'applications/phame/controller/post/list/PhameAllBloggersPostListController.php',
|
||||
'PhameAllBlogListController' => 'applications/phame/controller/blog/list/PhameAllBlogListController.php',
|
||||
'PhameAllPostListController' => 'applications/phame/controller/post/list/PhameAllPostListController.php',
|
||||
'PhameBlog' => 'applications/phame/storage/PhameBlog.php',
|
||||
'PhameBlogDeleteController' => 'applications/phame/controller/blog/PhameBlogDeleteController.php',
|
||||
'PhameBlogDetailView' => 'applications/phame/view/PhameBlogDetailView.php',
|
||||
'PhameBlogEditController' => 'applications/phame/controller/blog/PhameBlogEditController.php',
|
||||
'PhameBlogListBaseController' => 'applications/phame/controller/blog/list/PhameBlogListBaseController.php',
|
||||
'PhameBlogListView' => 'applications/phame/view/PhameBlogListView.php',
|
||||
'PhameBlogQuery' => 'applications/phame/query/PhameBlogQuery.php',
|
||||
'PhameBlogViewController' => 'applications/phame/controller/blog/PhameBlogViewController.php',
|
||||
'PhameBloggerPostListController' => 'applications/phame/controller/post/list/PhameBloggerPostListController.php',
|
||||
'PhameController' => 'applications/phame/controller/PhameController.php',
|
||||
'PhameDAO' => 'applications/phame/storage/PhameDAO.php',
|
||||
|
@ -1065,6 +1074,7 @@ phutil_register_library_map(array(
|
|||
'PhamePostPreviewController' => 'applications/phame/controller/post/PhamePostPreviewController.php',
|
||||
'PhamePostQuery' => 'applications/phame/query/PhamePostQuery.php',
|
||||
'PhamePostViewController' => 'applications/phame/controller/post/PhamePostViewController.php',
|
||||
'PhameUserBlogListController' => 'applications/phame/controller/blog/list/PhameUserBlogListController.php',
|
||||
'PhameUserPostListController' => 'applications/phame/controller/post/list/PhameUserPostListController.php',
|
||||
'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php',
|
||||
'PhortuneStripeBaseController' => 'applications/phortune/stripe/controller/PhortuneStripeBaseController.php',
|
||||
|
@ -2031,7 +2041,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorXHProfProfileSymbolView' => 'PhabricatorXHProfProfileView',
|
||||
'PhabricatorXHProfProfileTopLevelView' => 'PhabricatorXHProfProfileView',
|
||||
'PhabricatorXHProfProfileView' => 'AphrontView',
|
||||
'PhameAllBloggersPostListController' => 'PhamePostListBaseController',
|
||||
'PhameAllBlogListController' => 'PhameBlogListBaseController',
|
||||
'PhameAllPostListController' => 'PhamePostListBaseController',
|
||||
'PhameBlog' => 'PhameDAO',
|
||||
'PhameBlogDeleteController' => 'PhameController',
|
||||
'PhameBlogDetailView' => 'AphrontView',
|
||||
'PhameBlogEditController' => 'PhameController',
|
||||
'PhameBlogListBaseController' => 'PhameController',
|
||||
'PhameBlogListView' => 'AphrontView',
|
||||
'PhameBlogQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
'PhameBlogViewController' => 'PhameController',
|
||||
'PhameBloggerPostListController' => 'PhamePostListBaseController',
|
||||
'PhameController' => 'PhabricatorController',
|
||||
'PhameDAO' => 'PhabricatorLiskDAO',
|
||||
|
@ -2045,6 +2064,7 @@ phutil_register_library_map(array(
|
|||
'PhamePostPreviewController' => 'PhameController',
|
||||
'PhamePostQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
'PhamePostViewController' => 'PhameController',
|
||||
'PhameUserBlogListController' => 'PhameBlogListBaseController',
|
||||
'PhameUserPostListController' => 'PhamePostListBaseController',
|
||||
'PhortuneMonthYearExpiryControl' => 'AphrontFormControl',
|
||||
'PhortuneStripeBaseController' => 'PhabricatorController',
|
||||
|
|
|
@ -382,7 +382,7 @@ class AphrontDefaultApplicationConfiguration
|
|||
),
|
||||
|
||||
'/phame/' => array(
|
||||
'' => 'PhameAllBloggersPostListController',
|
||||
'' => 'PhameAllPostListController',
|
||||
'post/' => array(
|
||||
'' => 'PhameUserPostListController',
|
||||
'delete/(?P<phid>[^/]+)/' => 'PhamePostDeleteController',
|
||||
|
@ -395,6 +395,14 @@ class AphrontDefaultApplicationConfiguration
|
|||
'' => 'PhameDraftListController',
|
||||
'new/' => 'PhamePostEditController',
|
||||
),
|
||||
'blog/' => array(
|
||||
'' => 'PhameUserBlogListController',
|
||||
'all/' => 'PhameAllBlogListController',
|
||||
'new/' => 'PhameBlogEditController',
|
||||
'delete/(?P<phid>[^/]+)/' => 'PhameBlogDeleteController',
|
||||
'edit/(?P<phid>[^/]+)/' => 'PhameBlogEditController',
|
||||
'view/(?P<phid>[^/]+)/' => 'PhameBlogViewController',
|
||||
),
|
||||
'posts/' => array(
|
||||
'' => 'PhameUserPostListController',
|
||||
'(?P<bloggername>\w+)/' => 'PhameBloggerPostListController',
|
||||
|
|
|
@ -68,12 +68,18 @@ abstract class PhameController extends PhabricatorController {
|
|||
'New Draft');
|
||||
$nav->addFilter('draft',
|
||||
'My Drafts');
|
||||
foreach ($this->getSideNavExtraDraftFilters() as $draft_filter) {
|
||||
$nav->addFilter($draft_filter['key'],
|
||||
$draft_filter['name'],
|
||||
idx($draft_filter, 'uri'));
|
||||
}
|
||||
|
||||
$nav->addSpacer();
|
||||
$nav->addLabel('Posts');
|
||||
$nav->addFilter('post',
|
||||
'My Posts');
|
||||
$nav->addFilter('everyone',
|
||||
'Everyone',
|
||||
$nav->addFilter('post/all',
|
||||
'All Posts',
|
||||
$base_uri);
|
||||
foreach ($this->getSideNavExtraPostFilters() as $post_filter) {
|
||||
$nav->addFilter($post_filter['key'],
|
||||
|
@ -81,16 +87,59 @@ abstract class PhameController extends PhabricatorController {
|
|||
idx($post_filter, 'uri'));
|
||||
}
|
||||
|
||||
$nav->addSpacer();
|
||||
$nav->addLabel('Blogs');
|
||||
foreach ($this->getSideNavBlogFilters() as $blog_filter) {
|
||||
$nav->addFilter($blog_filter['key'],
|
||||
$blog_filter['name'],
|
||||
idx($blog_filter, 'uri'));
|
||||
}
|
||||
|
||||
$nav->selectFilter($filter);
|
||||
|
||||
return $nav;
|
||||
}
|
||||
|
||||
protected function getSideNavExtraDraftFilters() {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function getSideNavExtraPostFilters() {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function getSideNavBlogFilters() {
|
||||
return array(
|
||||
array(
|
||||
'key' => 'blog',
|
||||
'name' => 'My Blogs',
|
||||
),
|
||||
array(
|
||||
'key' => 'blog/all',
|
||||
'name' => 'All Blogs',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
return 'post';
|
||||
}
|
||||
|
||||
protected function getPager() {
|
||||
$request = $this->getRequest();
|
||||
$pager = new AphrontPagerView();
|
||||
$page_size = 50;
|
||||
$pager->setURI($request->getRequestURI(), 'offset');
|
||||
$pager->setPageSize($page_size);
|
||||
$pager->setOffset($request->getInt('offset'));
|
||||
|
||||
return $pager;
|
||||
}
|
||||
|
||||
protected function buildNoticeView() {
|
||||
$notice_view = id(new AphrontErrorView())
|
||||
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||
->setTitle('Meta thoughts and feelings');
|
||||
return $notice_view;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameBlogDeleteController
|
||||
extends PhameController {
|
||||
|
||||
private $phid;
|
||||
|
||||
private function setBlogPHID($phid) {
|
||||
$this->phid = $phid;
|
||||
return $this;
|
||||
}
|
||||
private function getBlogPHID() {
|
||||
return $this->phid;
|
||||
}
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
return 'blog/delete/'.$this->getBlogPHID();
|
||||
}
|
||||
|
||||
protected function getSideNavExtraBlogFilters() {
|
||||
$filters = array(
|
||||
array('key' => $this->getSideNavFilter(),
|
||||
'name' => 'Delete Blog')
|
||||
);
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$phid = $data['phid'];
|
||||
$this->setBlogPHID($phid);
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$blogger_edge_type = PhabricatorEdgeConfig::TYPE_BLOG_HAS_BLOGGER;
|
||||
$post_edge_type = PhabricatorEdgeConfig::TYPE_BLOG_HAS_POST;
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
$blog_phid = $this->getBlogPHID();
|
||||
$blogs = id(new PhameBlogQuery())
|
||||
->withPHIDs(array($blog_phid))
|
||||
->execute();
|
||||
$blog = reset($blogs);
|
||||
if (empty($blog)) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$phids = array($blog_phid);
|
||||
$edge_types = array(
|
||||
PhabricatorEdgeConfig::TYPE_BLOG_HAS_BLOGGER,
|
||||
PhabricatorEdgeConfig::TYPE_BLOG_HAS_POST,
|
||||
);
|
||||
|
||||
$edges = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs($phids)
|
||||
->withEdgeTypes($edge_types)
|
||||
->execute();
|
||||
|
||||
$blogger_edges = $edges[$blog_phid][$blogger_edge_type];
|
||||
// TODO -- make this check use a policy
|
||||
if (!isset($blogger_edges[$user->getPHID()]) &&
|
||||
!$user->isAdmin()) {
|
||||
return new Aphront403Response();
|
||||
}
|
||||
|
||||
$edit_uri = $blog->getEditURI();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$blogger_phids = array_keys($blogger_edges);
|
||||
$post_edges = $edges[$blog_phid][$post_edge_type];
|
||||
$post_phids = array_keys($post_edges);
|
||||
$editor = id(new PhabricatorEdgeEditor());
|
||||
$editor->setUser($user);
|
||||
foreach ($blogger_phids as $phid) {
|
||||
$editor->removeEdge($blog_phid, $blogger_edge_type, $phid);
|
||||
}
|
||||
foreach ($post_phids as $phid) {
|
||||
$editor->removeEdge($blog_phid, $post_edge_type, $phid);
|
||||
}
|
||||
$editor->save();
|
||||
|
||||
$blog->delete();
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI('/phame/blog/?deleted');
|
||||
}
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($user)
|
||||
->setTitle('Delete blog?')
|
||||
->appendChild('Really delete this blog? It will be gone forever.')
|
||||
->addSubmitButton('Delete')
|
||||
->addCancelButton($edit_uri);
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameBlogEditController
|
||||
extends PhameController {
|
||||
|
||||
private $phid;
|
||||
private $isBlogEdit;
|
||||
|
||||
private function setBlogPHID($phid) {
|
||||
$this->phid = $phid;
|
||||
return $this;
|
||||
}
|
||||
private function getBlogPHID() {
|
||||
return $this->phid;
|
||||
}
|
||||
private function setIsBlogEdit($is_blog_edit) {
|
||||
$this->isBlogEdit = $is_blog_edit;
|
||||
return $this;
|
||||
}
|
||||
private function isBlogEdit() {
|
||||
return $this->isBlogEdit;
|
||||
}
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
if ($this->isBlogEdit()) {
|
||||
$filter = 'blog/edit/'.$this->getBlogPHID();
|
||||
} else {
|
||||
$filter = 'blog/new';
|
||||
}
|
||||
return $filter;
|
||||
}
|
||||
|
||||
protected function getSideNavBlogFilters() {
|
||||
$filters = parent::getSideNavBlogFilters();
|
||||
|
||||
if ($this->isBlogEdit()) {
|
||||
$filter =
|
||||
array('key' => 'blog/edit/'.$this->getBlogPHID(),
|
||||
'name' => 'Edit Blog');
|
||||
$filters[] = $filter;
|
||||
} else {
|
||||
$filter =
|
||||
array('key' => 'blog/new',
|
||||
'name' => 'New Blog');
|
||||
array_unshift($filters, $filter);
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$phid = idx($data, 'phid');
|
||||
$this->setBlogPHID($phid);
|
||||
$this->setIsBlogEdit((bool)$phid);
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
$e_name = null;
|
||||
$e_bloggers = null;
|
||||
$errors = array();
|
||||
|
||||
if ($this->isBlogEdit()) {
|
||||
$blogs = id(new PhameBlogQuery())
|
||||
->withPHIDs(array($this->getBlogPHID()))
|
||||
->execute();
|
||||
$blog = reset($blogs);
|
||||
if (empty($blog)) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$bloggers = $blog->loadBloggers()->getBloggers();
|
||||
|
||||
// TODO -- make this check use a policy
|
||||
if (!isset($bloggers[$user->getPHID()]) &&
|
||||
!$user->isAdmin()) {
|
||||
return new Aphront403Response();
|
||||
}
|
||||
$blogger_tokens = mpull($bloggers, 'getFullName', 'getPHID');
|
||||
$submit_button = 'Save Changes';
|
||||
$delete_button = javelin_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $blog->getDeleteURI(),
|
||||
'class' => 'grey button',
|
||||
'sigil' => 'workflow',
|
||||
),
|
||||
'Delete Blog');
|
||||
$page_title = 'Edit Blog';
|
||||
} else {
|
||||
$blog = id(new PhameBlog())
|
||||
->setCreatorPHID($user->getPHID());
|
||||
$blogger_tokens = array($user->getPHID() => $user->getFullName());
|
||||
$submit_button = 'Create Blog';
|
||||
$delete_button = null;
|
||||
$page_title = 'Create Blog';
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$saved = true;
|
||||
$name = $request->getStr('name');
|
||||
$description = $request->getStr('description');
|
||||
$blogger_arr = $request->getArr('bloggers');
|
||||
|
||||
if (empty($blogger_arr)) {
|
||||
$error = 'Bloggers must be nonempty.';
|
||||
if ($this->isBlogEdit()) {
|
||||
$error .= ' To delete the blog, use the delete button.';
|
||||
} else {
|
||||
$error .= ' A blog cannot exist without bloggers.';
|
||||
}
|
||||
$e_bloggers = 'Required';
|
||||
$errors[] = $error;
|
||||
}
|
||||
$new_bloggers = array_values($blogger_arr);
|
||||
if ($this->isBlogEdit()) {
|
||||
$old_bloggers = array_keys($blogger_tokens);
|
||||
} else {
|
||||
$old_bloggers = array();
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
$errors[] = 'Name must be nonempty.';
|
||||
$e_name = 'Required';
|
||||
}
|
||||
$blog->setName($name);
|
||||
$blog->setDescription($description);
|
||||
|
||||
if (empty($errors)) {
|
||||
$blog->save();
|
||||
|
||||
$add_phids = $new_bloggers;
|
||||
$rem_phids = array_diff($old_bloggers, $new_bloggers);
|
||||
$editor = new PhabricatorEdgeEditor();
|
||||
$edge_type = PhabricatorEdgeConfig::TYPE_BLOG_HAS_BLOGGER;
|
||||
$editor->setUser($user);
|
||||
foreach ($add_phids as $phid) {
|
||||
$editor->addEdge($blog->getPHID(), $edge_type, $phid);
|
||||
}
|
||||
foreach ($rem_phids as $phid) {
|
||||
$editor->removeEdge($blog->getPHID(), $edge_type, $phid);
|
||||
}
|
||||
$editor->save();
|
||||
|
||||
} else {
|
||||
$saved = false;
|
||||
}
|
||||
|
||||
if ($saved) {
|
||||
$uri = new PhutilURI($blog->getViewURI());
|
||||
$uri->setQueryParam('new', true);
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($uri);
|
||||
}
|
||||
}
|
||||
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader($page_title);
|
||||
$panel->setWidth(AphrontPanelView::WIDTH_FULL);
|
||||
if ($delete_button) {
|
||||
$panel->addButton($delete_button);
|
||||
}
|
||||
|
||||
$remarkup_reference = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' =>
|
||||
PhabricatorEnv::getDoclink('article/Remarkup_Reference.html'),
|
||||
'tabindex' => '-1',
|
||||
'target' => '_blank',
|
||||
),
|
||||
'Formatting Reference');
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($user)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel('Name')
|
||||
->setName('name')
|
||||
->setValue($blog->getName())
|
||||
->setID('blog-name')
|
||||
->setError($e_name)
|
||||
)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextAreaControl())
|
||||
->setLabel('Description')
|
||||
->setName('description')
|
||||
->setValue($blog->getDescription())
|
||||
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
|
||||
->setID('blog-description')
|
||||
->setCaption($remarkup_reference)
|
||||
)
|
||||
->appendChild(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel('Bloggers')
|
||||
->setName('bloggers')
|
||||
->setValue($blogger_tokens)
|
||||
->setUser($user)
|
||||
->setDatasource('/typeahead/common/users/')
|
||||
->setError($e_bloggers)
|
||||
)
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->addCancelButton('/phame/blog/')
|
||||
->setValue($submit_button)
|
||||
);
|
||||
|
||||
$panel->appendChild($form);
|
||||
|
||||
if ($errors) {
|
||||
$error_view = id(new AphrontErrorView())
|
||||
->setTitle('Errors saving blog.')
|
||||
->setErrors($errors);
|
||||
} else {
|
||||
$error_view = null;
|
||||
}
|
||||
|
||||
$this->setShowSideNav(true);
|
||||
return $this->buildStandardPageResponse(
|
||||
array(
|
||||
$error_view,
|
||||
$panel,
|
||||
),
|
||||
array(
|
||||
'title' => $page_title,
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameBlogViewController
|
||||
extends PhameController {
|
||||
|
||||
private $blogPHID;
|
||||
private $bloggerPHIDs;
|
||||
private $postPHIDs;
|
||||
|
||||
private function setPostPHIDs($post_phids) {
|
||||
$this->postPHIDs = $post_phids;
|
||||
return $this;
|
||||
}
|
||||
private function getPostPHIDs() {
|
||||
return $this->postPHIDs;
|
||||
}
|
||||
|
||||
private function setBloggerPHIDs($blogger_phids) {
|
||||
$this->bloggerPHIDs = $blogger_phids;
|
||||
return $this;
|
||||
}
|
||||
private function getBloggerPHIDs() {
|
||||
return $this->bloggerPHIDs;
|
||||
}
|
||||
|
||||
private function setBlogPHID($blog_phid) {
|
||||
$this->blogPHID = $blog_phid;
|
||||
return $this;
|
||||
}
|
||||
private function getBlogPHID() {
|
||||
return $this->blogPHID;
|
||||
}
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
$filter = 'blog/view/'.$this->getBlogPHID();
|
||||
return $filter;
|
||||
}
|
||||
protected function getSideNavExtraBlogFilters() {
|
||||
$filters = array(
|
||||
array('key' => $this->getSideNavFilter(),
|
||||
'name' => $this->getPhameTitle())
|
||||
);
|
||||
return $filters;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->setBlogPHID(idx($data, 'phid'));
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
$blog_phid = $this->getBlogPHID();
|
||||
|
||||
$blogs = id(new PhameBlogQuery())
|
||||
->withPHIDs(array($blog_phid))
|
||||
->execute();
|
||||
$blog = reset($blogs);
|
||||
|
||||
if (!$blog) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$this->loadEdges();
|
||||
|
||||
$blogger_phids = $this->getBloggerPHIDs();
|
||||
if ($blogger_phids) {
|
||||
$bloggers = id(new PhabricatorObjectHandleData($blogger_phids))
|
||||
->loadHandles();
|
||||
} else {
|
||||
$bloggers = array();
|
||||
}
|
||||
|
||||
$post_phids = $this->getPostPHIDs();
|
||||
if ($post_phids) {
|
||||
$posts = id(new PhamePostQuery())
|
||||
->withPHIDs($post_phids)
|
||||
->withVisibility(PhamePost::VISIBILITY_PUBLISHED)
|
||||
->execute();
|
||||
} else {
|
||||
$posts = array();
|
||||
}
|
||||
|
||||
$actions = array('view');
|
||||
$is_admin = false;
|
||||
// TODO -- make this check use a policy
|
||||
if (isset($bloggers[$user->getPHID()])) {
|
||||
$actions[] = 'edit';
|
||||
$is_admin = true;
|
||||
}
|
||||
|
||||
if ($request->getExists('new')) {
|
||||
$notice = $this->buildNoticeView()
|
||||
->setTitle('Successfully created your blog.')
|
||||
->appendChild('Time to write some posts.');
|
||||
} else {
|
||||
$notice = null;
|
||||
}
|
||||
|
||||
$details = id(new PhameBlogDetailView())
|
||||
->setUser($user)
|
||||
->setBloggers($bloggers)
|
||||
->setBlog($blog)
|
||||
->setIsAdmin($is_admin);
|
||||
|
||||
$panel = id(new PhamePostListView())
|
||||
->setUser($this->getRequest()->getUser())
|
||||
->setBloggers($bloggers)
|
||||
->setPosts($posts)
|
||||
->setActions($actions)
|
||||
->setDraftList(false);
|
||||
|
||||
$this->setShowSideNav(false);
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
array(
|
||||
$notice,
|
||||
$details,
|
||||
$panel,
|
||||
),
|
||||
array(
|
||||
'title' => $blog->getName(),
|
||||
));
|
||||
}
|
||||
|
||||
private function loadEdges() {
|
||||
|
||||
$edge_types = array(
|
||||
PhabricatorEdgeConfig::TYPE_BLOG_HAS_BLOGGER,
|
||||
PhabricatorEdgeConfig::TYPE_BLOG_HAS_POST,
|
||||
);
|
||||
$blog_phid = $this->getBlogPHID();
|
||||
$phids = array($blog_phid);
|
||||
|
||||
$edges = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs($phids)
|
||||
->withEdgeTypes($edge_types)
|
||||
->execute();
|
||||
|
||||
$blogger_phids = array_keys(
|
||||
$edges[$blog_phid][PhabricatorEdgeConfig::TYPE_BLOG_HAS_BLOGGER]
|
||||
);
|
||||
$this->setBloggerPHIDs($blogger_phids);
|
||||
|
||||
$post_phids = array_keys(
|
||||
$edges[$blog_phid][PhabricatorEdgeConfig::TYPE_BLOG_HAS_POST]
|
||||
);
|
||||
$this->setPostPHIDs($post_phids);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameAllBlogListController
|
||||
extends PhameBlogListBaseController {
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
return 'blog/all';
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$blogs = id(new PhameBlogQuery())
|
||||
->needBloggers(true)
|
||||
->executeWithPager($this->getPager());
|
||||
$this->setBlogs($blogs);
|
||||
|
||||
$page_title = 'All Blogs';
|
||||
$this->setPageTitle($page_title);
|
||||
|
||||
$this->setShowSideNav(true);
|
||||
|
||||
return $this->buildBlogListPageResponse();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
abstract class PhameBlogListBaseController
|
||||
extends PhameController {
|
||||
|
||||
private $blogs;
|
||||
private $pageTitle;
|
||||
|
||||
protected function setBlogs(array $blogs) {
|
||||
assert_instances_of($blogs, 'PhameBlog');
|
||||
$this->blogs = $blogs;
|
||||
return $this;
|
||||
}
|
||||
private function getBlogs() {
|
||||
return $this->blogs;
|
||||
}
|
||||
|
||||
protected function setPageTitle($page_title) {
|
||||
$this->pageTitle = $page_title;
|
||||
return $this;
|
||||
}
|
||||
private function getPageTitle() {
|
||||
return $this->pageTitle;
|
||||
}
|
||||
|
||||
protected function getNoticeView() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getBlogListPanel() {
|
||||
$blogs = $this->getBlogs();
|
||||
|
||||
$panel = id(new PhameBlogListView())
|
||||
->setUser($this->getRequest()->getUser())
|
||||
->setBlogs($blogs)
|
||||
->setHeader($this->getPageTitle());
|
||||
|
||||
return $panel;
|
||||
}
|
||||
|
||||
protected function buildBlogListPageResponse() {
|
||||
return $this->buildStandardPageResponse(
|
||||
array(
|
||||
$this->getNoticeView(),
|
||||
$this->getBlogListPanel(),
|
||||
$this->getPager(),
|
||||
),
|
||||
array(
|
||||
'title' => $this->getPageTitle(),
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameUserBlogListController
|
||||
extends PhameBlogListBaseController {
|
||||
|
||||
protected function getNoticeView() {
|
||||
$request = $this->getRequest();
|
||||
|
||||
if ($request->getExists('deleted')) {
|
||||
$notice_view = $this->buildNoticeView()
|
||||
->appendChild('Successfully deleted blog.');
|
||||
} else {
|
||||
$notice_view = null;
|
||||
}
|
||||
|
||||
return $notice_view;
|
||||
}
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
return 'blog';
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
$phid = $user->getPHID();
|
||||
|
||||
$blog_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$phid,
|
||||
PhabricatorEdgeConfig::TYPE_BLOGGER_HAS_BLOG
|
||||
);
|
||||
|
||||
$blogs = id(new PhameBlogQuery())
|
||||
->withPHIDs($blog_phids)
|
||||
->needBloggers(true)
|
||||
->executeWithPager($this->getPager());
|
||||
|
||||
$this->setBlogs($blogs);
|
||||
|
||||
$this->setPageTitle('My Blogs');
|
||||
|
||||
$this->setShowSideNav(true);
|
||||
|
||||
return $this->buildBlogListPageResponse();
|
||||
}
|
||||
|
||||
}
|
|
@ -32,19 +32,6 @@ extends PhameController {
|
|||
return $this->phid;
|
||||
}
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
return 'post/delete/'.$this->getPostPHID();
|
||||
}
|
||||
|
||||
protected function getSideNavExtraPostFilters() {
|
||||
$filters = array(
|
||||
array('key' => $this->getSideNavFilter(),
|
||||
'name' => 'Delete Post')
|
||||
);
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$phid = $data['phid'];
|
||||
$this->setPostPHID($phid);
|
||||
|
@ -53,26 +40,46 @@ extends PhameController {
|
|||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
$post = id(new PhamePost())->loadOneWhere(
|
||||
'phid = %s',
|
||||
$this->getPostPHID());
|
||||
$post_phid = $this->getPostPHID();
|
||||
$posts = id(new PhamePostQuery())
|
||||
->withPHIDs(array($post_phid))
|
||||
->execute();
|
||||
$post = reset($posts);
|
||||
if (empty($post)) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
if ($post->getBloggerPHID() != $user->getPHID()) {
|
||||
return new Aphront403Response();
|
||||
}
|
||||
$edit_uri = $post->getEditURI();
|
||||
$post_noun = $post->getHumanName();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$edge_type = PhabricatorEdgeConfig::TYPE_POST_HAS_BLOG;
|
||||
$edges = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs(array($post_phid))
|
||||
->withEdgeTypes(array($edge_type))
|
||||
->execute();
|
||||
|
||||
$blog_edges = $edges[$post_phid][$edge_type];
|
||||
$blog_phids = array_keys($blog_edges);
|
||||
$editor = id(new PhabricatorEdgeEditor());
|
||||
$editor->setUser($user);
|
||||
foreach ($blog_phids as $phid) {
|
||||
$editor->removeEdge($post_phid, $edge_type, $phid);
|
||||
}
|
||||
$editor->save();
|
||||
|
||||
$post->delete();
|
||||
return id(new AphrontRedirectResponse())->setURI('/phame/?deleted');
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI('/phame/'.$post_noun.'/?deleted');
|
||||
}
|
||||
|
||||
$edit_uri = $post->getEditURI();
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($user)
|
||||
->setTitle('Delete post?')
|
||||
->appendChild('Really delete this post? It will be gone forever.')
|
||||
->setTitle('Delete '.$post_noun.'?')
|
||||
->appendChild('Really delete this '.$post_noun.'? '.
|
||||
'It will be gone forever.')
|
||||
->addSubmitButton('Delete')
|
||||
->addCancelButton($edit_uri);
|
||||
|
||||
|
|
|
@ -24,6 +24,17 @@ extends PhameController {
|
|||
|
||||
private $phid;
|
||||
private $isPostEdit;
|
||||
private $userBlogs;
|
||||
private $postBlogs;
|
||||
private $post;
|
||||
|
||||
private function setPost(PhamePost $post) {
|
||||
$this->post = $post;
|
||||
return $this;
|
||||
}
|
||||
private function getPost() {
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
private function setPostPHID($phid) {
|
||||
$this->phid = $phid;
|
||||
|
@ -39,17 +50,34 @@ extends PhameController {
|
|||
private function isPostEdit() {
|
||||
return $this->isPostEdit;
|
||||
}
|
||||
private function setUserBlogs(array $blogs) {
|
||||
assert_instances_of($blogs, 'PhameBlog');
|
||||
$this->userBlogs = $blogs;
|
||||
return $this;
|
||||
}
|
||||
private function getUserBlogs() {
|
||||
return $this->userBlogs;
|
||||
}
|
||||
private function setPostBlogs(array $blogs) {
|
||||
assert_instances_of($blogs, 'PhameBlog');
|
||||
$this->postBlogs = $blogs;
|
||||
return $this;
|
||||
}
|
||||
private function getPostBlogs() {
|
||||
return $this->postBlogs;
|
||||
}
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
if ($this->isPostEdit()) {
|
||||
$filter = 'post/edit/'.$this->getPostPHID();
|
||||
$post_noun = $this->getPost()->getHumanName();
|
||||
$filter = $post_noun.'/edit/'.$this->getPostPHID();
|
||||
} else {
|
||||
$filter = 'post/new';
|
||||
}
|
||||
return $filter;
|
||||
}
|
||||
protected function getSideNavExtraPostFilters() {
|
||||
if ($this->isPostEdit()) {
|
||||
if ($this->isPostEdit() && !$this->getPost()->isDraft()) {
|
||||
$filters = array(
|
||||
array('key' => 'post/edit/'.$this->getPostPHID(),
|
||||
'name' => 'Edit Post')
|
||||
|
@ -60,6 +88,18 @@ extends PhameController {
|
|||
|
||||
return $filters;
|
||||
}
|
||||
protected function getSideNavExtraDraftFilters() {
|
||||
if ($this->isPostEdit() && $this->getPost()->isDraft()) {
|
||||
$filters = array(
|
||||
array('key' => 'draft/edit/'.$this->getPostPHID(),
|
||||
'name' => 'Edit Draft')
|
||||
);
|
||||
} else {
|
||||
$filters = array();
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$phid = idx($data, 'phid');
|
||||
|
@ -75,15 +115,17 @@ extends PhameController {
|
|||
$errors = array();
|
||||
|
||||
if ($this->isPostEdit()) {
|
||||
$post = id(new PhamePost())->loadOneWhere(
|
||||
'phid = %s',
|
||||
$this->getPostPHID());
|
||||
$posts = id(new PhamePostQuery())
|
||||
->withPHIDs(array($this->getPostPHID()))
|
||||
->execute();
|
||||
$post = reset($posts);
|
||||
if (empty($post)) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
if ($post->getBloggerPHID() != $user->getPHID()) {
|
||||
return new Aphront403Response();
|
||||
}
|
||||
$post_noun = ucfirst($post->getHumanName());
|
||||
$cancel_uri = $post->getViewURI($user->getUsername());
|
||||
$submit_button = 'Save Changes';
|
||||
$delete_button = javelin_render_tag(
|
||||
|
@ -93,17 +135,19 @@ extends PhameController {
|
|||
'class' => 'grey button',
|
||||
'sigil' => 'workflow',
|
||||
),
|
||||
'Delete Post');
|
||||
$page_title = 'Edit Post';
|
||||
'Delete '.$post_noun);
|
||||
$page_title = 'Edit '.$post_noun;
|
||||
} else {
|
||||
$post = id(new PhamePost())
|
||||
->setBloggerPHID($user->getPHID())
|
||||
->setVisibility(PhamePost::VISIBILITY_DRAFT);
|
||||
$cancel_uri = '/phame/';
|
||||
$submit_button = 'Create Post';
|
||||
$cancel_uri = '/phame/draft/';
|
||||
$submit_button = 'Create Draft';
|
||||
$delete_button = null;
|
||||
$page_title = 'Create Post';
|
||||
$page_title = 'Create Draft';
|
||||
}
|
||||
$this->setPost($post);
|
||||
$this->loadEdgesAndBlogs();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$saved = true;
|
||||
|
@ -135,9 +179,34 @@ extends PhameController {
|
|||
$errors[] = 'Title must be nonempty.';
|
||||
$e_title = 'Required';
|
||||
}
|
||||
|
||||
$blogs_published = array_keys($this->getPostBlogs());
|
||||
$blogs_to_publish = array();
|
||||
$blogs_to_depublish = array();
|
||||
if ($visibility == PhamePost::VISIBILITY_PUBLISHED) {
|
||||
$blogs_arr = $request->getArr('blogs');
|
||||
$blogs_to_publish = array_values($blogs_arr);
|
||||
$blogs_to_depublish = array_diff($blogs_published,
|
||||
$blogs_to_publish);
|
||||
} else {
|
||||
$blogs_to_depublish = $blogs_published;
|
||||
}
|
||||
|
||||
if (empty($errors)) {
|
||||
try {
|
||||
$post->save();
|
||||
|
||||
$editor = new PhabricatorEdgeEditor();
|
||||
$edge_type = PhabricatorEdgeConfig::TYPE_POST_HAS_BLOG;
|
||||
$editor->setUser($user);
|
||||
foreach ($blogs_to_publish as $phid) {
|
||||
$editor->addEdge($post->getPHID(), $edge_type, $phid);
|
||||
}
|
||||
foreach ($blogs_to_depublish as $phid) {
|
||||
$editor->removeEdge($post->getPHID(), $edge_type, $phid);
|
||||
}
|
||||
$editor->save();
|
||||
|
||||
} catch (AphrontQueryDuplicateKeyException $e) {
|
||||
$saved = false;
|
||||
$e_phame_title = 'Not Unique';
|
||||
|
@ -150,6 +219,7 @@ extends PhameController {
|
|||
|
||||
if ($saved) {
|
||||
$uri = new PhutilURI($post->getViewURI($user->getUsername()));
|
||||
$uri->setQueryParam('saved', true);
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($uri);
|
||||
}
|
||||
|
@ -209,6 +279,10 @@ extends PhameController {
|
|||
->setName('visibility')
|
||||
->setValue($post->getVisibility())
|
||||
->setOptions(PhamePost::getVisibilityOptionsForSelect())
|
||||
->setID('post-visibility')
|
||||
)
|
||||
->appendChild(
|
||||
$this->getBlogCheckboxControl($post)
|
||||
)
|
||||
->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
|
@ -247,6 +321,26 @@ extends PhameController {
|
|||
'uri' => '/phame/post/preview/',
|
||||
));
|
||||
|
||||
$visibility_data = array(
|
||||
'select_id' => 'post-visibility',
|
||||
'current' => $post->getVisibility(),
|
||||
'published' => PhamePost::VISIBILITY_PUBLISHED,
|
||||
'draft' => PhamePost::VISIBILITY_DRAFT,
|
||||
'change_uri' => $post->getChangeVisibilityURI(),
|
||||
);
|
||||
|
||||
$blogs_data = array(
|
||||
'checkbox_id' => 'post-blogs',
|
||||
'have_published' => (bool) count($this->getPostBlogs())
|
||||
);
|
||||
|
||||
Javelin::initBehavior(
|
||||
'phame-post-blogs',
|
||||
array(
|
||||
'blogs' => $blogs_data,
|
||||
'visibility' => $visibility_data,
|
||||
));
|
||||
|
||||
if ($errors) {
|
||||
$error_view = id(new AphrontErrorView())
|
||||
->setTitle('Errors saving post.')
|
||||
|
@ -266,4 +360,76 @@ extends PhameController {
|
|||
'title' => $page_title,
|
||||
));
|
||||
}
|
||||
|
||||
private function getBlogCheckboxControl(PhamePost $post) {
|
||||
if ($post->getVisibility() == PhamePost::VISIBILITY_PUBLISHED) {
|
||||
$control_style = null;
|
||||
} else {
|
||||
$control_style = 'display: none';
|
||||
}
|
||||
|
||||
$control = id(new AphrontFormCheckboxControl())
|
||||
->setLabel('Blogs')
|
||||
->setControlID('post-blogs')
|
||||
->setControlStyle($control_style);
|
||||
|
||||
$post_blogs = $this->getPostBlogs();
|
||||
$user_blogs = $this->getUserBlogs();
|
||||
$all_blogs = $post_blogs + $user_blogs;
|
||||
$all_blogs = msort($all_blogs, 'getName');
|
||||
foreach ($all_blogs as $phid => $blog) {
|
||||
$control->addCheckbox(
|
||||
'blogs[]',
|
||||
$blog->getPHID(),
|
||||
$blog->getName(),
|
||||
isset($post_blogs[$phid])
|
||||
);
|
||||
}
|
||||
|
||||
return $control;
|
||||
}
|
||||
|
||||
private function loadEdgesAndBlogs() {
|
||||
$edge_types = array(PhabricatorEdgeConfig::TYPE_BLOGGER_HAS_BLOG);
|
||||
$blogger_phid = $this->getRequest()->getUser()->getPHID();
|
||||
$phids = array($blogger_phid);
|
||||
if ($this->isPostEdit()) {
|
||||
$edge_types[] = PhabricatorEdgeConfig::TYPE_POST_HAS_BLOG;
|
||||
$phids[] = $this->getPostPHID();
|
||||
}
|
||||
|
||||
$edges = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs($phids)
|
||||
->withEdgeTypes($edge_types)
|
||||
->execute();
|
||||
|
||||
$all_blogs_assoc = array();
|
||||
foreach ($phids as $phid) {
|
||||
foreach ($edge_types as $type) {
|
||||
$all_blogs_assoc += $edges[$phid][$type];
|
||||
}
|
||||
}
|
||||
|
||||
$blogs = id(new PhameBlogQuery())
|
||||
->withPHIDs(array_keys($all_blogs_assoc))
|
||||
->execute();
|
||||
$blogs = mpull($blogs, null, 'getPHID');
|
||||
|
||||
$user_blogs = array_intersect_key(
|
||||
$blogs,
|
||||
$edges[$blogger_phid][PhabricatorEdgeConfig::TYPE_BLOGGER_HAS_BLOG]
|
||||
);
|
||||
|
||||
if ($this->isPostEdit()) {
|
||||
$post_blogs = array_intersect_key(
|
||||
$blogs,
|
||||
$edges[$this->getPostPHID()][PhabricatorEdgeConfig::TYPE_POST_HAS_BLOG]
|
||||
);
|
||||
} else {
|
||||
$post_blogs = array();
|
||||
}
|
||||
|
||||
$this->setUserBlogs($user_blogs);
|
||||
$this->setPostBlogs($post_blogs);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,19 +83,19 @@ extends PhameController {
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$post = id(new PhamePost())->loadOneWhere(
|
||||
'phid = %s',
|
||||
$post_phid);
|
||||
$posts = id(new PhamePostQuery())
|
||||
->withPHIDs(array($post_phid))
|
||||
->execute();
|
||||
$post = reset($posts);
|
||||
|
||||
if ($post) {
|
||||
$this->setPhameTitle($post->getPhameTitle());
|
||||
}
|
||||
|
||||
$blogger = id(new PhabricatorUser())->loadOneWhere(
|
||||
'phid = %s', $post->getBloggerPHID());
|
||||
if (!$blogger) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
}
|
||||
|
||||
} else if ($this->getBloggerName() && $this->getPhameTitle()) {
|
||||
$phame_title = $this->getPhameTitle();
|
||||
|
@ -106,10 +106,12 @@ extends PhameController {
|
|||
if (!$blogger) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
$post = id(new PhamePost())->loadOneWhere(
|
||||
'bloggerPHID = %s AND phameTitle = %s',
|
||||
$blogger->getPHID(),
|
||||
$phame_title);
|
||||
$posts = id(new PhamePostQuery())
|
||||
->withBloggerPHID($blogger->getPHID())
|
||||
->withPhameTitle($phame_title)
|
||||
->execute();
|
||||
$post = reset($posts);
|
||||
|
||||
if ($post && $phame_title != $this->getPhameTitle()) {
|
||||
$uri = $post->getViewURI($this->getBloggerName());
|
||||
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||
|
@ -126,13 +128,24 @@ extends PhameController {
|
|||
}
|
||||
|
||||
if ($post->isDraft()) {
|
||||
$notice = id(new AphrontErrorView())
|
||||
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||
$notice = $this->buildNoticeView()
|
||||
->setTitle('You are previewing a draft.')
|
||||
->setErrors(array(
|
||||
'Only you can see this draft until you publish it.',
|
||||
'If you chose a comment widget it will show up when you publish.',
|
||||
));
|
||||
} else if ($request->getExists('saved')) {
|
||||
$new_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/phame/post/new/',
|
||||
'class' => 'button green',
|
||||
),
|
||||
'write another blog post'
|
||||
);
|
||||
$notice = $this->buildNoticeView()
|
||||
->appendChild('<p>Saved post successfully.</p>')
|
||||
->appendChild('Seek even more phame and '.$new_link);
|
||||
} else {
|
||||
$notice = null;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameAllBloggersPostListController
|
||||
final class PhameAllPostListController
|
||||
extends PhamePostListBaseController {
|
||||
|
||||
public function shouldRequireLogin() {
|
||||
|
@ -27,7 +27,7 @@ final class PhameAllBloggersPostListController
|
|||
}
|
||||
|
||||
protected function getSideNavFilter() {
|
||||
return 'everyone';
|
||||
return 'post/all';
|
||||
}
|
||||
|
||||
protected function getNoticeView() {
|
||||
|
@ -65,9 +65,7 @@ final class PhameAllBloggersPostListController
|
|||
'If you need more help try the '.$guide_link.'.',
|
||||
);
|
||||
|
||||
$notice_view = id(new AphrontErrorView())
|
||||
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||
->setTitle('Meta thoughts and feelings');
|
||||
$notice_view = $this->buildNoticeView();
|
||||
foreach ($notices as $notice) {
|
||||
$notice_view->appendChild('<p>'.$notice.'</p>');
|
||||
}
|
||||
|
@ -84,7 +82,7 @@ final class PhameAllBloggersPostListController
|
|||
|
||||
$this->setActions(array('view'));
|
||||
|
||||
$page_title = 'Posts by Everyone';
|
||||
$page_title = 'All Posts';
|
||||
$this->setPageTitle($page_title);
|
||||
|
||||
$this->setShowSideNav(true);
|
|
@ -34,6 +34,19 @@ final class PhameDraftListController
|
|||
return true;
|
||||
}
|
||||
|
||||
protected function getNoticeView() {
|
||||
$request = $this->getRequest();
|
||||
|
||||
if ($request->getExists('deleted')) {
|
||||
$notice_view = $this->buildNoticeView()
|
||||
->appendChild('Deleted draft successfully.');
|
||||
} else {
|
||||
$notice_view = null;
|
||||
}
|
||||
|
||||
return $notice_view;
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
$phid = $user->getPHID();
|
||||
|
|
|
@ -54,17 +54,6 @@ abstract class PhamePostListBaseController
|
|||
return false;
|
||||
}
|
||||
|
||||
protected function getPager() {
|
||||
$request = $this->getRequest();
|
||||
$pager = new AphrontPagerView();
|
||||
$page_size = 50;
|
||||
$pager->setURI($request->getRequestURI(), 'offset');
|
||||
$pager->setPageSize($page_size);
|
||||
$pager->setOffset($request->getInt('offset'));
|
||||
|
||||
return $pager;
|
||||
}
|
||||
|
||||
protected function getNoticeView() {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -31,38 +31,13 @@ final class PhameUserPostListController
|
|||
}
|
||||
|
||||
protected function getNoticeView() {
|
||||
$user = $this->getRequest()->getUser();
|
||||
$request = $this->getRequest();
|
||||
|
||||
$new_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/phame/post/new/',
|
||||
'class' => 'button green',
|
||||
),
|
||||
'write another blog post'
|
||||
);
|
||||
|
||||
$pretty_uri = PhabricatorEnv::getProductionURI(
|
||||
'/phame/posts/'.$user->getUserName().'/');
|
||||
$pretty_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => (string) $pretty_uri
|
||||
),
|
||||
(string) $pretty_uri
|
||||
);
|
||||
|
||||
$notices = array(
|
||||
'Seek even more phame and '.$new_link,
|
||||
'Published posts also appear at the awesome, world-accessible '.
|
||||
'URI: '.$pretty_link
|
||||
);
|
||||
|
||||
$notice_view = id(new AphrontErrorView())
|
||||
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||
->setTitle('Meta thoughts and feelings');
|
||||
foreach ($notices as $notice) {
|
||||
$notice_view->appendChild('<p>'.$notice.'</p>');
|
||||
if ($request->getExists('deleted')) {
|
||||
$notice_view = $this->buildNoticeView()
|
||||
->appendChild('Deleted post successfully.');
|
||||
} else {
|
||||
$notice_view = null;
|
||||
}
|
||||
|
||||
return $notice_view;
|
||||
|
|
108
src/applications/phame/query/PhameBlogQuery.php
Normal file
108
src/applications/phame/query/PhameBlogQuery.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameBlogQuery extends PhabricatorOffsetPagedQuery {
|
||||
|
||||
private $phids;
|
||||
private $needBloggers;
|
||||
|
||||
public function withPHIDs($phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needBloggers($need_bloggers) {
|
||||
$this->needBloggers = $need_bloggers;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
$table = new PhameBlog();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
||||
$where_clause = $this->buildWhereClause($conn_r);
|
||||
$order_clause = $this->buildOrderClause($conn_r);
|
||||
$limit_clause = $this->buildLimitClause($conn_r);
|
||||
|
||||
$data = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT * FROM %T b %Q %Q %Q',
|
||||
$table->getTableName(),
|
||||
$where_clause,
|
||||
$order_clause,
|
||||
$limit_clause);
|
||||
|
||||
$blogs = $table->loadAllFromArray($data);
|
||||
|
||||
if ($blogs) {
|
||||
if ($this->needBloggers) {
|
||||
$this->loadBloggers($blogs);
|
||||
}
|
||||
}
|
||||
|
||||
return $blogs;
|
||||
}
|
||||
|
||||
private function loadBloggers(array $blogs) {
|
||||
assert_instances_of($blogs, 'PhameBlog');
|
||||
$blog_phids = mpull($blogs, 'getPHID');
|
||||
|
||||
$edge_types = array(PhabricatorEdgeConfig::TYPE_BLOG_HAS_BLOGGER);
|
||||
|
||||
$query = new PhabricatorEdgeQuery();
|
||||
$query->withSourcePHIDs($blog_phids)
|
||||
->withEdgeTypes($edge_types)
|
||||
->execute();
|
||||
|
||||
$all_blogger_phids = $query->getDestinationPHIDs(
|
||||
$blog_phids,
|
||||
$edge_types
|
||||
);
|
||||
|
||||
$handles = id(new PhabricatorObjectHandleData($all_blogger_phids))
|
||||
->loadHandles();
|
||||
|
||||
foreach ($blogs as $blog) {
|
||||
$blogger_phids = $query->getDestinationPHIDs(
|
||||
array($blog->getPHID()),
|
||||
$edge_types
|
||||
);
|
||||
$blog->attachBloggers(array_select_keys($handles, $blogger_phids));
|
||||
}
|
||||
}
|
||||
|
||||
private function buildWhereClause($conn_r) {
|
||||
$where = array();
|
||||
|
||||
if ($this->phids) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
}
|
||||
|
||||
private function buildOrderClause($conn_r) {
|
||||
return 'ORDER BY id DESC';
|
||||
}
|
||||
}
|
|
@ -16,11 +16,16 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhamePostQuery extends PhabricatorOffsetPagedQuery {
|
||||
|
||||
private $bloggerPHID;
|
||||
private $withoutBloggerPHID;
|
||||
private $phameTitle;
|
||||
private $visibility;
|
||||
private $phids;
|
||||
|
||||
/**
|
||||
* Mutually exlusive with @{method:withoutBloggerPHID}.
|
||||
|
@ -37,11 +42,21 @@ final class PhamePostQuery extends PhabricatorOffsetPagedQuery {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withPhameTitle($phame_title) {
|
||||
$this->phameTitle = $phame_title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withVisibility($visibility) {
|
||||
$this->visibility = $visibility;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs($phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
$table = new PhamePost();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
@ -52,7 +67,7 @@ final class PhamePostQuery extends PhabricatorOffsetPagedQuery {
|
|||
|
||||
$data = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT * FROM %T e %Q %Q %Q',
|
||||
'SELECT * FROM %T p %Q %Q %Q',
|
||||
$table->getTableName(),
|
||||
$where_clause,
|
||||
$order_clause,
|
||||
|
@ -66,6 +81,14 @@ final class PhamePostQuery extends PhabricatorOffsetPagedQuery {
|
|||
private function buildWhereClause($conn_r) {
|
||||
$where = array();
|
||||
|
||||
if ($this->phids) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->bloggerPHID) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
|
@ -80,6 +103,14 @@ final class PhamePostQuery extends PhabricatorOffsetPagedQuery {
|
|||
);
|
||||
}
|
||||
|
||||
if ($this->phameTitle) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'phameTitle = %s',
|
||||
$this->phameTitle
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->visibility !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
|
|
135
src/applications/phame/storage/PhameBlog.php
Normal file
135
src/applications/phame/storage/PhameBlog.php
Normal file
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameBlog extends PhameDAO {
|
||||
|
||||
protected $id;
|
||||
protected $phid;
|
||||
protected $name;
|
||||
protected $description;
|
||||
protected $configData;
|
||||
protected $creatorPHID;
|
||||
|
||||
private $bloggerPHIDs;
|
||||
private $bloggers;
|
||||
|
||||
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_BLOG);
|
||||
}
|
||||
|
||||
public function loadBloggerPHIDs() {
|
||||
if (!$this->getPHID()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($this->bloggerPHIDs) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->bloggerPHIDs = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$this->getPHID(),
|
||||
PhabricatorEdgeConfig::TYPE_BLOG_HAS_BLOGGER
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBloggerPHIDs() {
|
||||
if ($this->bloggerPHIDs === null) {
|
||||
throw new Exception(
|
||||
'You must loadBloggerPHIDs before you can getBloggerPHIDs!'
|
||||
);
|
||||
}
|
||||
|
||||
return $this->bloggerPHIDs;
|
||||
}
|
||||
|
||||
public function loadBloggers() {
|
||||
if ($this->bloggers) {
|
||||
return $this->bloggers;
|
||||
}
|
||||
|
||||
$blogger_phids = $this->loadBloggerPHIDs()->getBloggerPHIDs();
|
||||
|
||||
if (empty($blogger_phids)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$bloggers = id(new PhabricatorObjectHandleData($blogger_phids))
|
||||
->loadHandles();
|
||||
|
||||
$this->attachBloggers($bloggers);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function attachBloggers(array $bloggers) {
|
||||
assert_instances_of($bloggers, 'PhabricatorObjectHandle');
|
||||
|
||||
$this->bloggers = $bloggers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBloggers() {
|
||||
if ($this->bloggers === null) {
|
||||
throw new Exception(
|
||||
'You must loadBloggers or attachBloggers before you can getBloggers!'
|
||||
);
|
||||
}
|
||||
|
||||
return $this->bloggers;
|
||||
}
|
||||
|
||||
public function getPostListURI() {
|
||||
return $this->getActionURI('posts');
|
||||
}
|
||||
|
||||
public function getViewURI() {
|
||||
return $this->getActionURI('view');
|
||||
}
|
||||
|
||||
public function getEditURI() {
|
||||
return $this->getActionURI('edit');
|
||||
}
|
||||
|
||||
public function getEditFilter() {
|
||||
return 'blog/edit/'.$this->getPHID();
|
||||
}
|
||||
|
||||
public function getDeleteURI() {
|
||||
return $this->getActionURI('delete');
|
||||
}
|
||||
|
||||
private function getActionURI($action) {
|
||||
return '/phame/blog/'.$action.'/'.$this->getPHID().'/';
|
||||
}
|
||||
}
|
|
@ -50,6 +50,9 @@ final class PhamePost extends PhameDAO {
|
|||
public function getDeleteURI() {
|
||||
return $this->getActionURI('delete');
|
||||
}
|
||||
public function getChangeVisibilityURI() {
|
||||
return $this->getActionURI('changevisibility');
|
||||
}
|
||||
private function getActionURI($action) {
|
||||
return '/phame/post/'.$action.'/'.$this->getPHID().'/';
|
||||
}
|
||||
|
@ -58,6 +61,16 @@ final class PhamePost extends PhameDAO {
|
|||
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)) {
|
||||
|
|
95
src/applications/phame/view/PhameBlogDetailView.php
Normal file
95
src/applications/phame/view/PhameBlogDetailView.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameBlogDetailView extends AphrontView {
|
||||
|
||||
private $user;
|
||||
|
||||
private $isAdmin;
|
||||
private $blog;
|
||||
private $bloggers;
|
||||
|
||||
public function setIsAdmin($is_admin) {
|
||||
$this->isAdmin = $is_admin;
|
||||
return $this;
|
||||
}
|
||||
private function getIsAdmin() {
|
||||
return $this->isAdmin;
|
||||
}
|
||||
|
||||
public function setUser(PhabricatorUser $user) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
private function getUser() {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setBloggers(array $bloggers) {
|
||||
assert_instances_of($bloggers, 'PhabricatorObjectHandle');
|
||||
$this->bloggers = $bloggers;
|
||||
return $this;
|
||||
}
|
||||
private function getBloggers() {
|
||||
return $this->bloggers;
|
||||
}
|
||||
|
||||
public function setBlog(PhameBlog $blog) {
|
||||
$this->blog = $blog;
|
||||
return $this;
|
||||
}
|
||||
private function getBlog() {
|
||||
return $this->blog;
|
||||
}
|
||||
|
||||
|
||||
public function render() {
|
||||
require_celerity_resource('phabricator-remarkup-css');
|
||||
|
||||
$user = $this->getUser();
|
||||
$blog = $this->getBlog();
|
||||
$bloggers = $this->getBloggers();
|
||||
$name = phutil_escape_html($blog->getName());
|
||||
$description = phutil_escape_html($blog->getDescription());
|
||||
$bloggers_txt = implode(' · ', mpull($bloggers, 'renderLink'));
|
||||
$panel = id(new AphrontPanelView())
|
||||
->setHeader($name)
|
||||
->setCaption($description)
|
||||
->setWidth(AphrontPanelView::WIDTH_FORM)
|
||||
->appendChild('Current bloggers: '.$bloggers_txt);
|
||||
|
||||
if ($this->getIsAdmin()) {
|
||||
$panel->addButton(
|
||||
phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $blog->getEditURI(),
|
||||
'class' => 'button grey',
|
||||
),
|
||||
'Edit Blog')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return $panel->render();
|
||||
}
|
||||
|
||||
}
|
125
src/applications/phame/view/PhameBlogListView.php
Normal file
125
src/applications/phame/view/PhameBlogListView.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group phame
|
||||
*/
|
||||
final class PhameBlogListView extends AphrontView {
|
||||
|
||||
private $user;
|
||||
private $blogs;
|
||||
private $header;
|
||||
|
||||
public function setHeader($header) {
|
||||
$this->header = $header;
|
||||
return $this;
|
||||
}
|
||||
private function getHeader() {
|
||||
return $this->header;
|
||||
}
|
||||
|
||||
public function setUser(PhabricatorUser $user) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
private function getUser() {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setBlogs(array $blogs) {
|
||||
assert_instances_of($blogs, 'PhameBlog');
|
||||
$this->blogs = $blogs;
|
||||
return $this;
|
||||
}
|
||||
private function getBlogs() {
|
||||
return $this->blogs;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$user = $this->getUser();
|
||||
$blogs = $this->getBlogs();
|
||||
$panel = new AphrontPanelView();
|
||||
|
||||
if (empty($blogs)) {
|
||||
$panel = id(new AphrontPanelView())
|
||||
->setHeader('No blogs... Yet!')
|
||||
->setCaption('Will you answer the call to phame?')
|
||||
->setCreateButton('New Blog',
|
||||
'/phame/blog/new');
|
||||
return $panel->render();
|
||||
}
|
||||
|
||||
$table_data = array();
|
||||
foreach ($blogs as $blog) {
|
||||
$view_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $blog->getViewURI(),
|
||||
),
|
||||
phutil_escape_html($blog->getName()));
|
||||
$bloggers = $blog->getBloggers();
|
||||
if (isset($bloggers[$user->getPHID()])) {
|
||||
$edit = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'class' => 'button small grey',
|
||||
'href' => $blog->getEditURI(),
|
||||
),
|
||||
'Edit');
|
||||
} else {
|
||||
$edit = null;
|
||||
}
|
||||
$view = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'class' => 'button small grey',
|
||||
'href' => $blog->getViewURI(),
|
||||
),
|
||||
'View');
|
||||
$table_data[] =
|
||||
array(
|
||||
$view_link,
|
||||
implode(', ', mpull($blog->getBloggers(), 'renderLink')),
|
||||
$view,
|
||||
$edit,
|
||||
);
|
||||
}
|
||||
|
||||
$table = new AphrontTableView($table_data);
|
||||
$table->setHeaders(
|
||||
array(
|
||||
'Name',
|
||||
'Bloggers',
|
||||
'',
|
||||
'',
|
||||
));
|
||||
$table->setColumnClasses(
|
||||
array(
|
||||
null,
|
||||
null,
|
||||
'action',
|
||||
'action',
|
||||
));
|
||||
|
||||
$panel->setCreateButton('Create a Blog', '/phame/blog/new/');
|
||||
$panel->setHeader($this->getHeader());
|
||||
$panel->appendChild($table);
|
||||
|
||||
return $panel->render();
|
||||
}
|
||||
}
|
|
@ -41,5 +41,5 @@ final class PhabricatorPHIDConstants {
|
|||
const PHID_TYPE_OASA = 'OASA';
|
||||
const PHID_TYPE_POST = 'POST';
|
||||
const PHID_TYPE_TOBJ = 'TOBJ';
|
||||
|
||||
const PHID_TYPE_BLOG = 'BLOG';
|
||||
}
|
||||
|
|
|
@ -6,7 +6,39 @@ Journal about your thoughts and feelings. Share with others. Profit.
|
|||
= Overview =
|
||||
|
||||
Phame is a simple blogging platform. You can write drafts which only you can
|
||||
see. Later, you can publish these drafts as posts which anyone can see.
|
||||
see. Later, you can publish these drafts as posts which anyone who can access
|
||||
the Phabricator instance can see. You can also add posts to blogs to increase
|
||||
your distribution.
|
||||
|
||||
Overall, Phame is intended to help an individual spread their message. As
|
||||
such, pertinent design decisions skew towards favoring the individual
|
||||
rather than the collective.
|
||||
|
||||
= Drafts =
|
||||
|
||||
Drafts are completely private so draft away.
|
||||
|
||||
= Posts =
|
||||
|
||||
Posts are accessible to anyone who has access to the Phabricator instance.
|
||||
|
||||
= Blogs =
|
||||
|
||||
Blogs are collections of posts. Each blog has associated metadata like
|
||||
a name, description, and set of bloggers who can add posts to the blog.
|
||||
Each blogger can also edit metadata about the blog and delete the blog
|
||||
outright.
|
||||
|
||||
Soon, blogs will be useful for powering external websites, like
|
||||
|
||||
blog.yourcompany.com
|
||||
|
||||
by making pertinent configuration changes with your DNS authority and
|
||||
Phabricator instance.
|
||||
|
||||
NOTE: removing a blogger from a given blog does not remove their posts that
|
||||
are already associated with the blog. Rather, it removes their ability to edit
|
||||
metadata about and add posts to the blog.
|
||||
|
||||
= Comment Widgets =
|
||||
|
||||
|
@ -14,5 +46,11 @@ Phame supports comment widgets from Facebook and Disqus. The adminstrator
|
|||
of the Phabricator instance must properly configure Phabricator to enable
|
||||
this functionality.
|
||||
|
||||
NOTE: Phame is extremely new and very basic for now. Give us feedback on
|
||||
A given comment widget is tied 1:1 with a given post. This means the same
|
||||
instance of a given comment widget will appear for a given post regardless
|
||||
of whether that post is being viewed in the context of a blog.
|
||||
|
||||
= Next Steps =
|
||||
|
||||
- Phame is extremely new and very basic for now. Give us feedback on
|
||||
what you'd like to see improve! See @{article:Give Feedback! Get Support!}.
|
||||
|
|
|
@ -30,6 +30,11 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
|
|||
const TYPE_DREV_DEPENDS_ON_DREV = 5;
|
||||
const TYPE_DREV_DEPENDED_ON_BY_DREV = 6;
|
||||
|
||||
const TYPE_BLOG_HAS_POST = 7;
|
||||
const TYPE_POST_HAS_BLOG = 8;
|
||||
const TYPE_BLOG_HAS_BLOGGER = 9;
|
||||
const TYPE_BLOGGER_HAS_BLOG = 10;
|
||||
|
||||
const TYPE_TEST_NO_CYCLE = 9000;
|
||||
|
||||
public static function getInverse($edge_type) {
|
||||
|
@ -42,6 +47,11 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
|
|||
|
||||
self::TYPE_DREV_DEPENDS_ON_DREV => self::TYPE_DREV_DEPENDED_ON_BY_DREV,
|
||||
self::TYPE_DREV_DEPENDED_ON_BY_DREV => self::TYPE_DREV_DEPENDS_ON_DREV,
|
||||
|
||||
self::TYPE_BLOG_HAS_POST => self::TYPE_POST_HAS_BLOG,
|
||||
self::TYPE_POST_HAS_BLOG => self::TYPE_BLOG_HAS_POST,
|
||||
self::TYPE_BLOG_HAS_BLOGGER => self::TYPE_BLOGGER_HAS_BLOG,
|
||||
self::TYPE_BLOGGER_HAS_BLOG => self::TYPE_BLOG_HAS_BLOGGER,
|
||||
);
|
||||
|
||||
return idx($map, $edge_type);
|
||||
|
@ -67,6 +77,8 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
|
|||
PhabricatorPHIDConstants::PHID_TYPE_MLST =>
|
||||
'PhabricatorMetaMTAMailingList',
|
||||
PhabricatorPHIDConstants::PHID_TYPE_TOBJ => 'HarbormasterObject',
|
||||
PhabricatorPHIDConstants::PHID_TYPE_BLOG => 'PhameBlog',
|
||||
PhabricatorPHIDConstants::PHID_TYPE_POST => 'PhamePost',
|
||||
);
|
||||
|
||||
$class = idx($class_map, $phid_type);
|
||||
|
@ -79,5 +91,4 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
|
|||
return newv($class, array())->establishConnection($conn_type);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -924,6 +924,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'name' => $this->getPatchPath(
|
||||
'migrate-differential-dependencies.php'),
|
||||
),
|
||||
'phameblog.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('phameblog.sql'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
23
webroot/rsrc/js/application/phame/phame-post-blogs.js
Normal file
23
webroot/rsrc/js/application/phame/phame-post-blogs.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* @provides javelin-behavior-phame-post-blogs
|
||||
* @requires javelin-behavior
|
||||
* javelin-dom
|
||||
*/
|
||||
|
||||
JX.behavior('phame-post-blogs', function(config) {
|
||||
|
||||
var visibility_select = JX.$(config.visibility.select_id);
|
||||
var blogs_widget = JX.$(config.blogs.checkbox_id);
|
||||
|
||||
var visibilityCallback = function(e) {
|
||||
if (visibility_select.value == config.visibility.published) {
|
||||
JX.DOM.show(blogs_widget);
|
||||
} else {
|
||||
JX.DOM.hide(blogs_widget);
|
||||
}
|
||||
e.kill();
|
||||
}
|
||||
|
||||
JX.DOM.listen(visibility_select, 'change', null, visibilityCallback);
|
||||
|
||||
});
|
Loading…
Reference in a new issue