1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-21 22:32:41 +01:00

Allow simple template-based skin definitions

Summary:
Lower the barrier to entry for installing and creating skins, so we can kill Wordpress. You can now install skins by dropping them into a directory, and build either "advanced" (full phutil library) skins or "basic" (simple PHP templates) skins.

Next up is getting static resources working in an easy way for skins.

I put these in `externals/` for now so they don't get hit by lint.

Test Plan: Viewed the Pokeblog with the Oblivious skin.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T1373

Differential Revision: https://secure.phabricator.com/D3717
This commit is contained in:
epriestley 2012-10-17 08:36:48 -07:00
parent 44c6109bf2
commit b3ad8507af
17 changed files with 384 additions and 64 deletions

View file

@ -1084,6 +1084,11 @@ return array(
// Information on shortnames - http://docs.disqus.com/help/68/
'disqus.shortname' => null,
// Directories to look for Phame skins inside of.
'phame.skins' => array(
'externals/skin/',
),
// -- Remarkup -------------------------------------------------------------- //
// If you enable this, linked YouTube videos will be embeded inline. This has

1
externals/skins/oblivious/404.php vendored Normal file
View file

@ -0,0 +1 @@
<h2>404 Not Found</h2>

3
externals/skins/oblivious/footer.php vendored Normal file
View file

@ -0,0 +1,3 @@
</div>
</body>
</html>

15
externals/skins/oblivious/header.php vendored Normal file
View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title><?php echo _e($title); ?></title>
</head>
<body>
<div class="oblivious-info">
<h1>
<a href="<?php echo _e($uri); ?>"><?php
echo _e($blog->getName());
?></a>
</h1>
<p><?php echo _e($blog->getDescription()); ?></p>
</div>
<div class="oblivious-content">

View file

@ -0,0 +1 @@
<?php echo $post->render(); ?>

13
externals/skins/oblivious/post-list.php vendored Normal file
View file

@ -0,0 +1,13 @@
<div class="oblivious-post-list">
<?php
foreach ($posts as $post) {
echo $post->renderWithSummary();
}
?>
</div>
<div class="oblivious-pager">
<?php echo $older; ?>
<?php echo $newer; ?>
</div>

3
externals/skins/oblivious/skin.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"name": "Oblivious"
}

View file

@ -1150,6 +1150,7 @@ phutil_register_library_map(array(
'PhabricatorXHProfSampleListController' => 'applications/xhprof/controller/PhabricatorXHProfSampleListController.php',
'PhabricatorXHProfSampleListView' => 'applications/xhprof/view/PhabricatorXHProfSampleListView.php',
'PhameBasicBlogSkin' => 'applications/phame/skins/PhameBasicBlogSkin.php',
'PhameBasicTemplateBlogSkin' => 'applications/phame/skins/PhameBasicTemplateBlogSkin.php',
'PhameBlog' => 'applications/phame/storage/PhameBlog.php',
'PhameBlogDeleteController' => 'applications/phame/controller/blog/PhameBlogDeleteController.php',
'PhameBlogEditController' => 'applications/phame/controller/blog/PhameBlogEditController.php',
@ -1157,7 +1158,6 @@ phutil_register_library_map(array(
'PhameBlogLiveController' => 'applications/phame/controller/blog/PhameBlogLiveController.php',
'PhameBlogQuery' => 'applications/phame/query/PhameBlogQuery.php',
'PhameBlogSkin' => 'applications/phame/skins/PhameBlogSkin.php',
'PhameBlogSkinTenEleven' => 'applications/phame/skins/PhameBlogSkinTenEleven.php',
'PhameBlogViewController' => 'applications/phame/controller/blog/PhameBlogViewController.php',
'PhameController' => 'applications/phame/controller/PhameController.php',
'PhameDAO' => 'applications/phame/storage/PhameDAO.php',
@ -1174,6 +1174,7 @@ phutil_register_library_map(array(
'PhamePostUnpublishController' => 'applications/phame/controller/post/PhamePostUnpublishController.php',
'PhamePostView' => 'applications/phame/view/PhamePostView.php',
'PhamePostViewController' => 'applications/phame/controller/post/PhamePostViewController.php',
'PhameSkinSpecification' => 'applications/phame/skins/PhameSkinSpecification.php',
'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php',
'PhortuneStripeBaseController' => 'applications/phortune/stripe/controller/PhortuneStripeBaseController.php',
'PhortuneStripePaymentFormView' => 'applications/phortune/stripe/view/PhortuneStripePaymentFormView.php',
@ -2278,6 +2279,7 @@ phutil_register_library_map(array(
'PhabricatorXHProfSampleListController' => 'PhabricatorXHProfController',
'PhabricatorXHProfSampleListView' => 'AphrontView',
'PhameBasicBlogSkin' => 'PhameBlogSkin',
'PhameBasicTemplateBlogSkin' => 'PhameBasicBlogSkin',
'PhameBlog' =>
array(
0 => 'PhameDAO',
@ -2290,7 +2292,6 @@ phutil_register_library_map(array(
'PhameBlogLiveController' => 'PhameController',
'PhameBlogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhameBlogSkin' => 'PhabricatorController',
'PhameBlogSkinTenEleven' => 'PhameBasicBlogSkin',
'PhameBlogViewController' => 'PhameController',
'PhameController' => 'PhabricatorController',
'PhameDAO' => 'PhabricatorLiskDAO',

View file

@ -118,6 +118,9 @@ final class PhameBlogEditController
->setObject($blog)
->execute();
$skins = PhameSkinSpecification::loadAllSkinSpecifications();
$skins = mpull($skins, 'getName');
$form = id(new AphrontFormView())
->setUser($user)
->setFlexible(true)
@ -171,7 +174,7 @@ final class PhameBlogEditController
->setLabel('Skin')
->setName('skin')
->setValue($blog->getSkin())
->setOptions(PhameBlog::getSkinOptionsForSelect())
->setOptions($skins)
)
->appendChild(
id(new AphrontFormSubmitControl())

View file

@ -64,6 +64,7 @@ final class PhameBlogLiveController extends PhameController {
->setBlog($blog)
->setBaseURI($request->getRequestURI()->setPath($base_path));
$skin->willProcessRequest(array());
return $skin->processRequest();
}

View file

@ -54,6 +54,7 @@ abstract class PhameBasicBlogSkin extends PhameBlogSkin {
$response = new AphrontWebpageResponse();
$response->setContent($view->render());
return $response;
}
@ -127,6 +128,9 @@ abstract class PhameBasicBlogSkin extends PhameBlogSkin {
*/
protected function renderOlderPageLink() {
$uri = $this->getOlderPageURI();
if (!$uri) {
return null;
}
return phutil_render_tag(
'a',
array(
@ -156,6 +160,9 @@ abstract class PhameBasicBlogSkin extends PhameBlogSkin {
*/
protected function renderNewerPageLink() {
$uri = $this->getNewerPageURI();
if (!$uri) {
return null;
}
return phutil_render_tag(
'a',
array(

View file

@ -0,0 +1,97 @@
<?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 PhameBasicTemplateBlogSkin extends PhameBasicBlogSkin {
public function willProcessRequest(array $data) {
$root = dirname(phutil_get_library_root('phabricator'));
require_once $root.'/support/phame/libskin.php';
parent::willProcessRequest($data);
}
public function getName() {
return $this->getSpecification()->getName();
}
public function getPath($to_file = null) {
$path = $this->getSpecification()->getRootDirectory();
if ($to_file) {
$path = $path.DIRECTORY_SEPARATOR.$to_file;
}
return $path;
}
private function renderTemplate($__template__, array $__scope__) {
chdir($this->getPath());
ob_start();
if (Filesystem::pathExists($this->getPath($__template__))) {
extract($__scope__ + $this->getDefaultScope());
require $this->getPath($__template__);
}
return ob_get_clean();
}
private function getDefaultScope() {
return array(
'skin' => $this,
'blog' => $this->getBlog(),
'uri' => $this->getURI(''),
);
}
protected function renderHeader() {
return $this->renderTemplate(
'header.php',
array(
'title' => $this->getBlog()->getName(),
));
}
protected function renderFooter() {
return $this->renderTemplate('footer.php', array());
}
protected function render404Page() {
return $this->renderTemplate('404.php', array());
}
protected function renderPostDetail(PhamePostView $post) {
return $this->renderTemplate(
'post-detail.php',
array(
'post' => $post,
));
}
protected function renderPostList(array $posts) {
return $this->renderTemplate(
'post-list.php',
array(
'posts' => $posts,
'older' => $this->renderNewerPageLink(),
'newer' => $this->renderOlderPageLink(),
));
}
}

View file

@ -24,6 +24,16 @@ abstract class PhameBlogSkin extends PhabricatorController {
private $blog;
private $baseURI;
private $preview;
private $specification;
public function setSpecification(PhameSkinSpecification $specification) {
$this->specification = $specification;
return $this;
}
public function getSpecification() {
return $this->specification;
}
public function setPreview($preview) {
$this->preview = $preview;

View file

@ -1,54 +0,0 @@
<?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 PhameBlogSkinTenEleven extends PhameBasicBlogSkin {
public function getName() {
return 'Ten Eleven (Default)';
}
protected function renderHeader() {
$blog_name = $this->getBlog()->getName();
$about = $this->getBlog()->getDescription();
$about = phutil_escape_html($about);
return <<<EOHTML
<div class="main-panel">
<div class="header">
<h1>{$blog_name}</h1>
<div class="about">{$about}</div>
</div>
<div class="splash">
</div>
<div class="content">
EOHTML;
}
protected function renderFooter() {
return <<<EOHTML
</div>
</div>
EOHTML;
}
}

View file

@ -0,0 +1,190 @@
<?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 PhameSkinSpecification {
const TYPE_ADVANCED = 'advanced';
const TYPE_BASIC = 'basic';
private $type;
private $rootDirectory;
private $skinClass;
private $phutilLibraries = array();
private $name;
private $config;
public static function loadAllSkinSpecifications() {
static $specs;
if ($specs === null) {
$paths = PhabricatorEnv::getEnvConfig('phame.skins');
$base = dirname(phutil_get_library_root('phabricator'));
$specs = array();
foreach ($paths as $path) {
$path = Filesystem::resolvePath($path, $base);
foreach (Filesystem::listDirectory($path) as $skin_directory) {
$skin_path = $path.DIRECTORY_SEPARATOR.$skin_directory;
if (!is_dir($skin_path)) {
continue;
}
$spec = self::loadSkinSpecification($skin_path);
if (!$spec) {
continue;
}
$name = trim($skin_directory, DIRECTORY_SEPARATOR);
$spec->setName($name);
if (isset($specs[$name])) {
$that_dir = $specs[$name]->getRootDirectory();
$this_dir = $spec->getRootDirectory();
throw new Exception(
"Two skins have the same name ('{$name}'), in '{$this_dir}' and ".
"'{$that_dir}'. Rename one or adjust your 'phame.skins' ".
"configuration.");
}
$specs[$name] = $spec;
}
}
}
return $specs;
}
public static function loadOneSkinSpecification($name) {
$paths = PhabricatorEnv::getEnvConfig('phame.skins');
$base = dirname(phutil_get_library_root('phabricator'));
foreach ($paths as $path) {
$path = Filesystem::resolvePath($path, $base);
$skin_path = $path.DIRECTORY_SEPARATOR.$name;
if (is_dir($skin_path)) {
$spec = self::loadSkinSpecification($skin_path);
if ($spec) {
$spec->setName($name);
return $spec;
}
}
}
return null;
}
public static function loadSkinSpecification($path) {
$config_path = $path.DIRECTORY_SEPARATOR.'skin.json';
$config = array();
if (Filesystem::pathExists($config_path)) {
$config = Filesystem::readFile($config_path);
$config = json_decode($config, true);
if (!is_array($config)) {
throw new Exception(
"Skin configuration file '{$config_path}' is not a valid JSON file.");
}
$type = idx($config, 'type', self::TYPE_BASIC);
} else {
$type = self::TYPE_BASIC;
}
$spec = new PhameSkinSpecification();
$spec->setRootDirectory($path);
$spec->setConfig($config);
switch ($type) {
case self::TYPE_BASIC:
$spec->setSkinClass('PhameBasicTemplateBlogSkin');
break;
case self::TYPE_ADVANCED:
$spec->setSkinClass($config['class']);
$spec->addPhutilLibrary($path.DIRECTORY_SEPARATOR.'src');
break;
default:
throw new Exception("Unknown skin type!");
}
$spec->setType($type);
return $spec;
}
public function setConfig(array $config) {
$this->config = $config;
return $this;
}
public function getConfig($key, $default = null) {
return idx($this->config, $key, $default);
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->getConfig('name', $this->name);
}
public function setRootDirectory($root_directory) {
$this->rootDirectory = $root_directory;
return $this;
}
public function getRootDirectory() {
return $this->rootDirectory;
}
public function setType($type) {
$this->type = $type;
return $this;
}
public function getType() {
return $this->type;
}
public function setSkinClass($skin_class) {
$this->skinClass = $skin_class;
return $this;
}
public function getSkinClass() {
return $this->skinClass;
}
public function addPhutilLibrary($library) {
$this->phutilLibraries[] = $library;
return $this;
}
public function buildSkin(AphrontRequest $request) {
foreach ($this->phutilLibraries as $library) {
phutil_load_library($library);
}
return newv($this->getSkinClass(), array($request, $this));
}
}

View file

@ -24,7 +24,7 @@ final class PhameBlog extends PhameDAO
const MARKUP_FIELD_DESCRIPTION = 'markup:description';
const SKIN_DEFAULT = 'PhameBlogSkinTenEleven';
const SKIN_DEFAULT = 'oblivious';
protected $id;
protected $phid;
@ -57,14 +57,17 @@ final class PhameBlog extends PhameDAO
}
public function getSkinRenderer(AphrontRequest $request) {
try {
$skin = newv($this->getSkin(), array($request));
} catch (PhutilMissingSymbolException $ex) {
// If this blog has a skin but it's no longer available (for example,
// it was uninstalled) just return the default skin.
$skin = newv(self::SKIN_DEFAULT, array($request));
$spec = PhameSkinSpecification::loadOneSkinSpecification(
$this->getSkin());
if (!$spec) {
$spec = PhameSkinSpecification::loadOneSkinSpecification(
self::SKIN_DEFAULT);
}
$skin = newv($spec->getSkinClass(), array($request));
$skin->setSpecification($spec);
return $skin;
}

21
support/phame/libskin.php Normal file
View file

@ -0,0 +1,21 @@
<?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.
*/
function _e($text) {
return phutil_escape_html($text);
}