From 26f7425ee2877466df00dd1a26f4a58ee7fcd9e0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 17 Oct 2012 08:37:05 -0700 Subject: [PATCH] Allow blog resources to be served without Celerity Summary: Allow skins to serve arbitrary resources without needing to be mapped, so we can have a vibrant community of amateur skinners. For "basic" skins, just put all the "css/" on the page always. Includes an image to prove that works. @vrana, pretty sure this has no impact outside of Phame but it does change Celerity so it might be to blame if there's any weirdness with static resources. Test Plan: {F21341} {F21340} Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T1373 Differential Revision: https://secure.phabricator.com/D3719 --- conf/default.conf.php | 2 +- externals/skins/oblivious/css/oblivious.css | 77 +++++++++++++++++ externals/skins/oblivious/header.php | 3 + externals/skins/oblivious/image/badge.png | Bin 0 -> 1844 bytes src/__phutil_library_map__.php | 4 + ...AphrontDefaultApplicationConfiguration.php | 2 +- .../PhabricatorApplicationPhame.php | 3 + .../controller/PhameResourceController.php | 81 ++++++++++++++++++ .../blog/PhameBlogLiveController.php | 15 ++-- .../phame/skins/PhameBasicBlogSkin.php | 19 +++- .../skins/PhameBasicTemplateBlogSkin.php | 48 ++++++++++- src/applications/phame/storage/PhameBlog.php | 6 ++ src/applications/phame/view/PhamePostView.php | 12 +-- .../CelerityPhabricatorResourceController.php | 59 +++++++++++++ .../celerity/CelerityResourceController.php | 50 +++++------ .../celerity/CelerityResourceTransformer.php | 12 ++- 16 files changed, 341 insertions(+), 52 deletions(-) create mode 100644 externals/skins/oblivious/css/oblivious.css create mode 100644 externals/skins/oblivious/image/badge.png create mode 100644 src/applications/phame/controller/PhameResourceController.php create mode 100644 src/infrastructure/celerity/CelerityPhabricatorResourceController.php diff --git a/conf/default.conf.php b/conf/default.conf.php index ebcaf8761e..4a4e7c2d37 100644 --- a/conf/default.conf.php +++ b/conf/default.conf.php @@ -1086,7 +1086,7 @@ return array( // Directories to look for Phame skins inside of. 'phame.skins' => array( - 'externals/skin/', + 'externals/skins/', ), // -- Remarkup -------------------------------------------------------------- // diff --git a/externals/skins/oblivious/css/oblivious.css b/externals/skins/oblivious/css/oblivious.css new file mode 100644 index 0000000000..13c71259a9 --- /dev/null +++ b/externals/skins/oblivious/css/oblivious.css @@ -0,0 +1,77 @@ +html, body, p, h1, h2, h3 { + padding: 0; + margin: 0; + font-weight: normal; +} + +html { + font-family: "Helvetica Neue", "Arial", sans-serif; + font-size: 16px; + overflow-y: scroll; + color: #555555; +} + +.oblivious-info { + position: fixed; + width: 15%; + border-right: 1px solid #dfdfdf; + top: 0; + bottom: 0; + left: 0; + padding: 140px 2% 0; + overflow: hidden; + + background: url(/image/badge.png); + background-repeat: no-repeat; + background-position: 20px 20px; +} + +.oblivious-content { + padding-top: 3%; + margin-left: 22%; + max-width: 600px; +} + +a { + color: #222222; + text-decoration: none; +} + +a:hover { + color: #a00000; +} + + +h1 { + font-size: 24px; + font-weight: normal; +} + +h2 { + font-size: 22px; + font-weight: bold; +} + +.phame-post { + margin: 0 0 2em; +} + +.phame-post-date { + font-size: 12px; + margin: .25em 0 1em; +} + +.phame-post { + line-height: 1.6em; +} + +.phame-post p { + margin: 0 0 1em; +} + + +.fb-comments, +.fb-comments span, +.fb-comments iframe[style] { + width: 100% !important; +} diff --git a/externals/skins/oblivious/header.php b/externals/skins/oblivious/header.php index 93945f901c..b7aadc2369 100644 --- a/externals/skins/oblivious/header.php +++ b/externals/skins/oblivious/header.php @@ -2,6 +2,9 @@ <?php echo _e($title); ?> + + getCSSResources(); ?> +
diff --git a/externals/skins/oblivious/image/badge.png b/externals/skins/oblivious/image/badge.png new file mode 100644 index 0000000000000000000000000000000000000000..15f84b92f58b4d91b8dae3a576bf12195d7c6f2d GIT binary patch literal 1844 zcmV-42g~@0P)B{bK-f)bNXVEdEa|;-@Es~2cb>QoO8Z+-@WhLd(TNI{Uu9iBeW7S zgcd?Gp^4ByND=Dg@9q*_5OxULgiXQ*p-5OKtPxgqb~d4*Jtql0gl<9?p%Z9lnXpV) zBrFi-36JEzTS7#R5b}h6LLcFD$iF{Fm?ablGlV^5sB{oU3Dfd~>VRpnsly~9cr;AN zd%dhFieh6v&O{6lCJ7~tiKs+LY#xXc5jnybVJkvJRANhf$VHh5UXG3smLg0ge zRMg1e71LCVh^WMr_^7?`GJ2S>5@RwPtcb4(UCB637&m~3N{ox|T1hYAwt-|gxGgcz zLe3K&8cc?RhZ2jL%0dO-x5x6u>F8_h_0KWJIp09{5vHP;zu7pRo@e`en~il zoUHstSfhO${Y5_h z0xt!1Bf^82Ua>WZO|GhT9>)SO<_;mFI~gugGGa424rA3ENI>s_|HxmkQ6a-c%2BZ$ zy+{8+2PY;27#WcxqkN2?hJf)Ys)}Q)l_TUU!ZFYj@bibfCnF@2 z;u}JaNz6S7;Zx{mK|l1EaJB5tjmJd9r>o*4Le5I;|3^q(;SLwyAiMuiF%|Lc21wn3 zxF;);hzQ{${nu3d<(l}4O5h$Kg!4CjAmz8{3Hm4QY>1DyKkm?n3`cfZo@CZr3XaE6|kRnyoI zpJxE7x*S4y!@m=t@K~!LQOP<6qO+kQn&=FitTc^*)uXB@zkahlj+#@8z8Oq;UXZ+m{U_20Kj1HsTA@ z@&Sp#HWRW5P}E`hfW%-k3Hrj!TRtE$*kD4607YL|J|HnDG9l{#MP0PYIAQP#i+x9I zgRdvmo0=tl7 zd%jkM>gB;pxb_%dLugmH%0biWEmzvO1l~OmQH49;jXL-|s%lsW zL5C&V5qT1M)xuo@PKW&ad|8I?XB5dp%9dlCc7pCW0=zRCP6KJQCZ{iD< z3$jo0Ncq1O9$P}ZgtuF{4iy)C&Zzw&FI-8Li+48264a@R$K{b;sQn5@TvUhaxNOLr iur8Tct?Sdj6kq_?qeh7{sL- 'view/AphrontView.php', 'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php', 'CelerityAPI' => 'infrastructure/celerity/CelerityAPI.php', + 'CelerityPhabricatorResourceController' => 'infrastructure/celerity/CelerityPhabricatorResourceController.php', 'CelerityResourceController' => 'infrastructure/celerity/CelerityResourceController.php', 'CelerityResourceGraph' => 'infrastructure/celerity/CelerityResourceGraph.php', 'CelerityResourceMap' => 'infrastructure/celerity/CelerityResourceMap.php', @@ -1174,6 +1175,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', + 'PhameResourceController' => 'applications/phame/controller/PhameResourceController.php', 'PhameSkinSpecification' => 'applications/phame/skins/PhameSkinSpecification.php', 'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php', 'PhortuneStripeBaseController' => 'applications/phortune/stripe/controller/PhortuneStripeBaseController.php', @@ -1334,6 +1336,7 @@ phutil_register_library_map(array( 'AphrontTypeaheadTemplateView' => 'AphrontView', 'AphrontUsageException' => 'AphrontException', 'AphrontWebpageResponse' => 'AphrontResponse', + 'CelerityPhabricatorResourceController' => 'CelerityResourceController', 'CelerityResourceController' => 'PhabricatorController', 'CelerityResourceGraph' => 'AbstractDirectedGraph', 'CelerityResourceTransformerTestCase' => 'PhabricatorTestCase', @@ -2313,6 +2316,7 @@ phutil_register_library_map(array( 'PhamePostUnpublishController' => 'PhameController', 'PhamePostView' => 'AphrontView', 'PhamePostViewController' => 'PhameController', + 'PhameResourceController' => 'CelerityResourceController', 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', 'PhortuneStripeBaseController' => 'PhabricatorController', 'PhortuneStripePaymentFormView' => 'AphrontView', diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php index ee3153d748..33be0b29f9 100644 --- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -155,7 +155,7 @@ class AphrontDefaultApplicationConfiguration '(?Ppkg/)?'. '(?P[a-f0-9]{8})/'. '(?P.+\.(?:css|js|jpg|png|swf|gif))' - => 'CelerityResourceController', + => 'CelerityPhabricatorResourceController', ), ); } diff --git a/src/applications/phame/application/PhabricatorApplicationPhame.php b/src/applications/phame/application/PhabricatorApplicationPhame.php index 84ca525a9c..cd0a684c7c 100644 --- a/src/applications/phame/application/PhabricatorApplicationPhame.php +++ b/src/applications/phame/application/PhabricatorApplicationPhame.php @@ -46,6 +46,9 @@ final class PhabricatorApplicationPhame extends PhabricatorApplication { return array( '/phame/' => array( '' => 'PhamePostListController', + 'r/(?P\d+)/(?P[^/]+)/(?P.*)' + => 'PhameResourceController', + 'live/(?P[^/]+)/(?P.*)' => 'PhameBlogLiveController', 'post/' => array( '(?:(?Pdraft|all)/)?' => 'PhamePostListController', diff --git a/src/applications/phame/controller/PhameResourceController.php b/src/applications/phame/controller/PhameResourceController.php new file mode 100644 index 0000000000..f933adf759 --- /dev/null +++ b/src/applications/phame/controller/PhameResourceController.php @@ -0,0 +1,81 @@ +root; + } + + public function willProcessRequest(array $data) { + $this->id = $data['id']; + $this->hash = $data['hash']; + $this->name = $data['name']; + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + // We require a visible blog associated with a given skin to serve + // resources, so you can't go fishing around where you shouldn't be. + + $blog = id(new PhameBlogQuery()) + ->setViewer($user) + ->withIDs(array($this->id)) + ->executeOne(); + if (!$blog) { + return new Aphront404Response(); + } + + $skin = $blog->getSkinRenderer($request); + $spec = $skin->getSpecification(); + + $this->root = $spec->getRootDirectory().DIRECTORY_SEPARATOR; + return $this->serveResource($this->name, $package_hash = null); + } + + protected function buildResourceTransformer() { + $xformer = new CelerityResourceTransformer(); + $xformer->setMinify(false); + $xformer->setTranslateURICallback(array($this, 'translateResourceURI')); + return $xformer; + } + + public function translateResourceURI(array $matches) { + $uri = trim($matches[1], "'\" \r\t\n"); + + if (Filesystem::pathExists($this->root.$uri)) { + $hash = filemtime($this->root.$uri); + } else { + $hash = '-'; + } + + $uri = '/phame/r/'.$this->id.'/'.$hash.'/'.$uri; + return 'url('.$uri.')'; + } + +} diff --git a/src/applications/phame/controller/blog/PhameBlogLiveController.php b/src/applications/phame/controller/blog/PhameBlogLiveController.php index 8f4e5976f5..02e09b7344 100644 --- a/src/applications/phame/controller/blog/PhameBlogLiveController.php +++ b/src/applications/phame/controller/blog/PhameBlogLiveController.php @@ -50,19 +50,20 @@ final class PhameBlogLiveController extends PhameController { ->setURI('http://'.$blog->getDomain().'/'.$this->more); } - if ($blog->getDomain()) { - $base_path = '/'; - } else { - $base_path = '/phame/live/'.$blog->getID().'/'; - } - $phame_request = clone $request; $phame_request->setPath('/'.ltrim($this->more, '/')); + if ($blog->getDomain()) { + $uri = new PhutilURI('http://'.$blog->getDomain().'/'); + } else { + $uri = '/phame/live/'.$blog->getID().'/'; + $uri = PhabricatorEnv::getURI($uri); + } + $skin = $blog->getSkinRenderer($phame_request); $skin ->setBlog($blog) - ->setBaseURI($request->getRequestURI()->setPath($base_path)); + ->setBaseURI((string)$uri); $skin->willProcessRequest(array()); return $skin->processRequest(); diff --git a/src/applications/phame/skins/PhameBasicBlogSkin.php b/src/applications/phame/skins/PhameBasicBlogSkin.php index aa797dc442..fdfeedd35e 100644 --- a/src/applications/phame/skins/PhameBasicBlogSkin.php +++ b/src/applications/phame/skins/PhameBasicBlogSkin.php @@ -25,7 +25,7 @@ abstract class PhameBasicBlogSkin extends PhameBlogSkin { private $pager; - final public function processRequest() { + public function processRequest() { $request = $this->getRequest(); $content = $this->renderContent($request); @@ -98,6 +98,21 @@ abstract class PhameBasicBlogSkin extends PhameBlogSkin { return '

404 Not Found

'; } + final public function getResourceURI($resource) { + $root = $this->getSpecification()->getRootDirectory(); + $path = $root.DIRECTORY_SEPARATOR.$resource; + + $data = Filesystem::readFile($path); + $hash = PhabricatorHash::digest($data); + $hash = substr($hash, 0, 6); + $id = $this->getBlog()->getID(); + + $uri = '/phame/r/'.$id.'/'.$hash.'/'.$resource; + $uri = PhabricatorEnv::getCDNURI($uri); + + return $uri; + } + /* -( Paging )------------------------------------------------------------- */ @@ -179,7 +194,7 @@ abstract class PhameBasicBlogSkin extends PhameBlogSkin { /** * @task internal */ - private function renderContent(AphrontRequest $request) { + protected function renderContent(AphrontRequest $request) { $user = $request->getUser(); $matches = null; diff --git a/src/applications/phame/skins/PhameBasicTemplateBlogSkin.php b/src/applications/phame/skins/PhameBasicTemplateBlogSkin.php index dfee960423..e94b442ac9 100644 --- a/src/applications/phame/skins/PhameBasicTemplateBlogSkin.php +++ b/src/applications/phame/skins/PhameBasicTemplateBlogSkin.php @@ -21,11 +21,51 @@ */ final class PhameBasicTemplateBlogSkin extends PhameBasicBlogSkin { - public function willProcessRequest(array $data) { + private $cssResources; + + public function processRequest() { $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/support/phame/libskin.php'; - parent::willProcessRequest($data); + $css = $this->getPath('css/'); + if (Filesystem::pathExists($css)) { + $this->cssResources = array(); + foreach (Filesystem::listDirectory($css) as $path) { + if (!preg_match('/.css$/', $path)) { + continue; + } + $this->cssResources[] = phutil_render_tag( + 'link', + array( + 'rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => $this->getResourceURI('css/'.$path), + )); + } + $this->cssResources = implode("\n", $this->cssResources); + } + + $request = $this->getRequest(); + $content = $this->renderContent($request); + + if (!$content) { + $content = $this->render404Page(); + } + + $content = array( + $this->renderHeader(), + $content, + $this->renderFooter(), + ); + + $response = new AphrontWebpageResponse(); + $response->setContent(implode("\n", $content)); + + return $response; + } + + public function getCSSResources() { + return $this->cssResources; } public function getName() { @@ -45,7 +85,9 @@ final class PhameBasicTemplateBlogSkin extends PhameBasicBlogSkin { ob_start(); if (Filesystem::pathExists($this->getPath($__template__))) { - extract($__scope__ + $this->getDefaultScope()); + // Fool lint. + $__evil__ = 'extract'; + $__evil__($__scope__ + $this->getDefaultScope()); require $this->getPath($__template__); } diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index 1d3c4d7693..b006d67206 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -65,6 +65,12 @@ final class PhameBlog extends PhameDAO self::SKIN_DEFAULT); } + if (!$spec) { + throw new Exception( + "This blog has an invalid skin, and the default skin failed to ". + "load."); + } + $skin = newv($spec->getSkinClass(), array($request)); $skin->setSpecification($spec); diff --git a/src/applications/phame/view/PhamePostView.php b/src/applications/phame/view/PhamePostView.php index b39af0d038..ae26615d48 100644 --- a/src/applications/phame/view/PhamePostView.php +++ b/src/applications/phame/view/PhamePostView.php @@ -169,8 +169,7 @@ final class PhamePostView extends AphrontView { ), $this->renderTitle(). $this->renderDatePublished(). - $this->renderSummary(). - $this->renderComments()); + $this->renderSummary()); } private function renderFacebookComments() { @@ -198,13 +197,14 @@ final class PhamePostView extends AphrontView { $c_uri ); + + $uri = $this->getSkin()->getURI('post/'.$this->getPost()->getPhameTitle()); + $fb_comments = phutil_render_tag('div', array( 'class' => 'fb-comments', - 'data-href' => $this->getRequestURI(), + 'data-href' => $uri, 'data-num-posts' => 5, - 'data-width' => 1080, // we hack this to fluid in css - 'data-colorscheme' => 'dark', ), '' ); @@ -249,7 +249,7 @@ final class PhamePostView extends AphrontView { ' document.getElementsByTagName("body")[0]).appendChild(dsq);'. '})(); ', $post->getPHID(), - $this->getRequestURI(), + $this->getSkin()->getURI('post/'.$this->getPost()->getPhameTitle()), $post->getTitle() ); diff --git a/src/infrastructure/celerity/CelerityPhabricatorResourceController.php b/src/infrastructure/celerity/CelerityPhabricatorResourceController.php new file mode 100644 index 0000000000..4eff40b2de --- /dev/null +++ b/src/infrastructure/celerity/CelerityPhabricatorResourceController.php @@ -0,0 +1,59 @@ +path = $data['path']; + $this->hash = $data['hash']; + $this->package = !empty($data['package']); + } + + public function processRequest() { + $package_hash = null; + if ($this->package) { + $package_hash = $this->hash; + } + return $this->serveResource($this->path, $package_hash); + } + + protected function buildResourceTransformer() { + $xformer = new CelerityResourceTransformer(); + $xformer->setMinify(PhabricatorEnv::getEnvConfig('celerity.minify')); + $xformer->setCelerityMap(CelerityResourceMap::getInstance()); + return $xformer; + } + +} diff --git a/src/infrastructure/celerity/CelerityResourceController.php b/src/infrastructure/celerity/CelerityResourceController.php index e29fb4519f..ab00881b1e 100644 --- a/src/infrastructure/celerity/CelerityResourceController.php +++ b/src/infrastructure/celerity/CelerityResourceController.php @@ -16,18 +16,13 @@ * limitations under the License. */ -/** - * Delivers CSS and JS resources to the browser. This controller handles all - * ##/res/## requests, and manages caching, package construction, and resource - * preprocessing. - * - * @group celerity - */ -final class CelerityResourceController extends PhabricatorController { +abstract class CelerityResourceController extends PhabricatorController { - private $path; - private $hash; - private $package; + abstract protected function getRootDirectory(); + + protected function buildResourceTransformer() { + return null; + } public function shouldRequireLogin() { return false; @@ -37,15 +32,11 @@ final class CelerityResourceController extends PhabricatorController { return false; } - public function willProcessRequest(array $data) { - $this->path = $data['path']; - $this->hash = $data['hash']; - $this->package = !empty($data['package']); + private function getDiskPath($to_resource = null) { + return $this->getRootDirectory().$to_resource; } - public function processRequest() { - $path = $this->path; - + protected function serveResource($path, $package_hash = null) { // Sanity checking to keep this from exposing anything sensitive, since it // ultimately boils down to disk reads. if (preg_match('@(//|\.\.)@', $path)) { @@ -66,11 +57,9 @@ final class CelerityResourceController extends PhabricatorController { return $this->makeResponseCacheable(new Aphront304Response()); } - $root = dirname(phutil_get_library_root('phabricator')); - - if ($this->package) { + if ($package_hash) { $map = CelerityResourceMap::getInstance(); - $paths = $map->resolvePackage($this->hash); + $paths = $map->resolvePackage($package_hash); if (!$paths) { return new Aphront404Response(); } @@ -78,7 +67,8 @@ final class CelerityResourceController extends PhabricatorController { try { $data = array(); foreach ($paths as $package_path) { - $data[] = Filesystem::readFile($root.'/webroot/'.$package_path); + $disk_path = $this->getDiskPath($package_path); + $data[] = Filesystem::readFile($disk_path); } $data = implode("\n\n", $data); } catch (Exception $ex) { @@ -86,17 +76,17 @@ final class CelerityResourceController extends PhabricatorController { } } else { try { - $data = Filesystem::readFile($root.'/webroot/'.$path); + $disk_path = $this->getDiskPath($path); + $data = Filesystem::readFile($disk_path); } catch (Exception $ex) { return new Aphront404Response(); } } - $xformer = new CelerityResourceTransformer(); - $xformer->setMinify(PhabricatorEnv::getEnvConfig('celerity.minify')); - $xformer->setCelerityMap(CelerityResourceMap::getInstance()); - - $data = $xformer->transformResource($path, $data); + $xformer = $this->buildResourceTransformer(); + if ($xformer) { + $data = $xformer->transformResource($path, $data); + } $response = new AphrontFileResponse(); $response->setContent($data); @@ -104,7 +94,7 @@ final class CelerityResourceController extends PhabricatorController { return $this->makeResponseCacheable($response); } - private function getSupportedResourceTypes() { + protected function getSupportedResourceTypes() { return array( 'css' => 'text/css; charset=utf-8', 'js' => 'text/javascript; charset=utf-8', diff --git a/src/infrastructure/celerity/CelerityResourceTransformer.php b/src/infrastructure/celerity/CelerityResourceTransformer.php index 7ecada2cd9..f681a08abf 100644 --- a/src/infrastructure/celerity/CelerityResourceTransformer.php +++ b/src/infrastructure/celerity/CelerityResourceTransformer.php @@ -24,6 +24,12 @@ final class CelerityResourceTransformer { private $minify; private $rawResourceMap; private $celerityMap; + private $translateURICallback; + + public function setTranslateURICallback($translate_uricallback) { + $this->translateURICallback = $translate_uricallback; + return $this; + } public function setMinify($minify) { $this->minify = $minify; @@ -46,8 +52,10 @@ final class CelerityResourceTransformer { switch ($type) { case 'css': $data = preg_replace_callback( - '@url\s*\((\s*[\'"]?/rsrc/.*?)\)@s', - array($this, 'translateResourceURI'), + '@url\s*\((\s*[\'"]?.*?)\)@s', + nonempty( + $this->translateURICallback, + array($this, 'translateResourceURI')), $data); break; }