diff --git a/conf/default.conf.php b/conf/default.conf.php index 6af98fb7f5..4666e05d01 100644 --- a/conf/default.conf.php +++ b/conf/default.conf.php @@ -735,15 +735,6 @@ return array( // The Phabricator "Client Secret" to use for Phabricator API access. 'phabricator.application-secret' => null, -// -- Disqus Comments ------------------------------------------------------- // - - // Should Phame users have Disqus comment widget, and if so what's the - // website shortname to use? For example, secure.phabricator.org uses - // "phabricator", which we registered with Disqus. If you aren't familiar - // with Disqus, see: - // Disqus quick start guide - http://docs.disqus.com/help/4/ - // Information on shortnames - http://docs.disqus.com/help/68/ - 'disqus.shortname' => null, // -- Recaptcha ------------------------------------------------------------- // @@ -1070,6 +1061,16 @@ return array( 'phriction.enabled' => true, +// -- Phame ----------------------------------------------------------------- // + + // Should Phame users have Disqus comment widget, and if so what's the + // website shortname to use? For example, secure.phabricator.org uses + // "phabricator", which we registered with Disqus. If you aren't familiar + // with Disqus, see: + // Disqus quick start guide - http://docs.disqus.com/help/4/ + // Information on shortnames - http://docs.disqus.com/help/68/ + 'disqus.shortname' => null, + // -- Remarkup -------------------------------------------------------------- // // If you enable this, linked YouTube videos will be embeded inline. This has diff --git a/resources/sql/patches/phamedomain.sql b/resources/sql/patches/phamedomain.sql new file mode 100644 index 0000000000..066fa37e46 --- /dev/null +++ b/resources/sql/patches/phamedomain.sql @@ -0,0 +1,4 @@ +ALTER TABLE `{$NAMESPACE}_phame`.`phame_blog` + ADD COLUMN `domain` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin + AFTER `description`, + ADD UNIQUE KEY (`domain`); diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index 8220980ba6..c288c059d0 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -91,9 +91,11 @@ abstract class AphrontApplicationConfiguration { /** * Using builtin and application routes, build the appropriate * @{class:AphrontController} class for the request. To route a request, we - * test the URI against all builtin routes from @{method:getURIMap}, then - * against all application routes from installed - * @{class:PhabricatorApplication}s. + * first test if the HTTP_HOST is configured as a valid Phabricator URI. If + * it isn't, we do a special check to see if it's a custom domain for a blog + * in the Phame application and if that fails we error. Otherwise, we test + * the URI against all builtin routes from @{method:getURIMap}, then against + * all application routes from installed @{class:PhabricatorApplication}s. * * If we match a route, we construct the controller it points at, build it, * and return it. @@ -117,7 +119,6 @@ abstract class AphrontApplicationConfiguration { */ final public function buildController() { $request = $this->getRequest(); - $path = $request->getPath(); if (PhabricatorEnv::getEnvConfig('security.require-https')) { if (!$request->isHTTPS()) { @@ -128,6 +129,41 @@ abstract class AphrontApplicationConfiguration { } } + $path = $request->getPath(); + $host = $request->getHost(); + $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); + $prod_uri = PhabricatorEnv::getEnvConfig('phabricator.production-uri'); + $file_uri = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); + if ($host != id(new PhutilURI($base_uri))->getDomain() && + $host != id(new PhutilURI($prod_uri))->getDomain() && + $host != id(new PhutilURI($file_uri))->getDomain()) { + $blogs = id(new PhameBlogQuery())->withDomain($host)->execute(); + $blog = reset($blogs); + if (!$blog) { + if ($prod_uri) { + $prod_str = ' or '.$prod_uri; + } else { + $prod_str = ''; + } + throw new Exception( + 'Specified domain '.$host.' is not configured for Phabricator '. + 'requests. Please use '.$base_uri.$prod_str.' to visit this instance.' + ); + } + + // 2 basic cases + // -- looking at a list of blog posts, path is nothing or '/' + // -- looking at an actual blog post, path is like /btrahan/post_title + if (!$path || $path == '/') { + $path = $blog->getViewURI(); + } else { + $path = '/phame/posts/'.trim($path, '/').'/'; + } + + // TODO - now we need to tell Celerity to render static resources with + // full URIs like secure.phabricator.org/rsrc/blahblah + } + list($controller, $uri_data) = $this->buildControllerForPath($path); if (!$controller) { if (!preg_match('@/$@', $path)) { diff --git a/src/applications/phame/controller/blog/PhameBlogEditController.php b/src/applications/phame/controller/blog/PhameBlogEditController.php index e7a806049a..7aa6ba4328 100644 --- a/src/applications/phame/controller/blog/PhameBlogEditController.php +++ b/src/applications/phame/controller/blog/PhameBlogEditController.php @@ -74,11 +74,12 @@ final class PhameBlogEditController } public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - $e_name = null; - $e_bloggers = null; - $errors = array(); + $request = $this->getRequest(); + $user = $request->getUser(); + $e_name = null; + $e_bloggers = null; + $e_custom_domain = null; + $errors = array(); if ($this->isBlogEdit()) { $blogs = id(new PhameBlogQuery()) @@ -117,10 +118,11 @@ final class PhameBlogEditController } if ($request->isFormPost()) { - $saved = true; - $name = $request->getStr('name'); - $description = $request->getStr('description'); - $blogger_arr = $request->getArr('bloggers'); + $saved = true; + $name = $request->getStr('name'); + $description = $request->getStr('description'); + $blogger_arr = $request->getArr('bloggers'); + $custom_domain = $request->getStr('custom_domain'); if (empty($blogger_arr)) { $error = 'Bloggers must be nonempty.'; @@ -145,6 +147,14 @@ final class PhameBlogEditController } $blog->setName($name); $blog->setDescription($description); + if (!empty($custom_domain)) { + $error = $blog->validateCustomDomain($custom_domain); + if ($error) { + $errors[] = $error; + $e_custom_domain = 'Invalid'; + } + $blog->setDomain($custom_domain); + } if (empty($errors)) { $blog->save(); @@ -168,7 +178,11 @@ final class PhameBlogEditController if ($saved) { $uri = new PhutilURI($blog->getViewURI()); - $uri->setQueryParam('new', true); + if ($this->isBlogEdit()) { + $uri->setQueryParam('edit', true); + } else { + $uri->setQueryParam('new', true); + } return id(new AphrontRedirectResponse()) ->setURI($uri); } @@ -208,8 +222,17 @@ final class PhameBlogEditController ->setDatasource('/typeahead/common/users/') ->setError($e_bloggers) ) - ->appendChild( - id(new AphrontFormSubmitControl()) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Custom Domain') + ->setName('custom_domain') + ->setValue($blog->getDomain()) + ->setCaption('Must include at least one dot (.), e.g. '. + 'blog.example.com') + ->setError($e_custom_domain) + ) + ->appendChild( + id(new AphrontFormSubmitControl()) ->addCancelButton('/phame/blog/') ->setValue($submit_button) ); diff --git a/src/applications/phame/controller/blog/PhameBlogViewController.php b/src/applications/phame/controller/blog/PhameBlogViewController.php index 3bb2d94b3a..7ed20e82af 100644 --- a/src/applications/phame/controller/blog/PhameBlogViewController.php +++ b/src/applications/phame/controller/blog/PhameBlogViewController.php @@ -111,6 +111,10 @@ final class PhameBlogViewController $notice = $this->buildNoticeView() ->setTitle('Successfully created your blog.') ->appendChild('Time to write some posts.'); + } else if ($request->getExists('edit')) { + $notice = $this->buildNoticeView() + ->setTitle('Successfully edited your blog.') + ->appendChild('Time to write some posts.'); } else { $notice = null; } diff --git a/src/applications/phame/query/PhameBlogQuery.php b/src/applications/phame/query/PhameBlogQuery.php index b68ed4894d..31159d5ca6 100644 --- a/src/applications/phame/query/PhameBlogQuery.php +++ b/src/applications/phame/query/PhameBlogQuery.php @@ -22,6 +22,7 @@ final class PhameBlogQuery extends PhabricatorOffsetPagedQuery { private $phids; + private $domain; private $needBloggers; public function withPHIDs($phids) { @@ -29,6 +30,11 @@ final class PhameBlogQuery extends PhabricatorOffsetPagedQuery { return $this; } + public function withDomain($domain) { + $this->domain = $domain; + return $this; + } + public function needBloggers($need_bloggers) { $this->needBloggers = $need_bloggers; return $this; @@ -96,7 +102,16 @@ final class PhameBlogQuery extends PhabricatorOffsetPagedQuery { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', - $this->phids); + $this->phids + ); + } + + if ($this->domain) { + $where[] = qsprintf( + $conn_r, + 'domain = %s', + $this->domain + ); } return $this->formatWhereClause($where); diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index b5b5a3a52d..f33a7da0a7 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -25,6 +25,7 @@ final class PhameBlog extends PhameDAO { protected $phid; protected $name; protected $description; + protected $domain; protected $configData; protected $creatorPHID; @@ -45,6 +46,38 @@ final class PhameBlog extends PhameDAO { PhabricatorPHIDConstants::PHID_TYPE_BLOG); } + /** + * Makes sure a given custom blog uri is properly configured in DNS + * to point at this Phabricator instance. If there is an error in + * the configuration, return a string describing the error and how + * to fix it. If there is no error, return an empty string. + * + * @return string + */ + public function validateCustomDomain($custom_domain) { + $example_domain = '(e.g. blog.example.com)'; + $valid = ''; + + // note this "uri" should be pretty busted given the desired input + // so just use it to test if there's a protocol specified + $uri = new PhutilURI($custom_domain); + if ($uri->getProtocol()) { + return 'Do not specify a protocol, just the domain. '.$example_domain; + } + + if (strpos($custom_domain, '/') !== false) { + return 'Do not specify a path, just the domain. '.$example_domain; + } + + if (strpos($custom_domain, '.') === false) { + return 'Custom domain must contain at least one dot (.) because '. + 'some browsers fail to set cookies on domains such as '. + 'http://example. '.$example_domain; + } + + return $valid; + } + public function loadBloggerPHIDs() { if (!$this->getPHID()) { return $this; diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 33cfa46ac4..35c3986953 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -988,6 +988,11 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { 'type' => 'sql', 'name' => $this->getPatchPath('draft-metadata.sql'), ), + 'phamedomain.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('phamedomain.sql'), + ), + ); }