diff --git a/Bootstraps/Drupal.php b/Bootstraps/Drupal.php new file mode 100644 index 0000000..405bf81 --- /dev/null +++ b/Bootstraps/Drupal.php @@ -0,0 +1,73 @@ +appenv = $appenv; + } + + /** + * Create a Drupal application. + */ + public function getApplication() { + + // Bootstrap Drupal. + // Bootstrap code is modeled on a few examples in core/scripts, such as + // db-tools.php. + // Assume we're in DRUPAL_ROOT/vendor/php-pm/httpkernel-adapter/Bootstraps. + // There may be a safer way to do this... + $drupal_root = dirname(dirname(dirname(dirname(__DIR__)))); + + // @todo: Is it necessary to call bootEnv()? It's called automatically by createFromRequest(). + DrupalKernel::bootEnvironment(); + + $request = Request::createFromGlobals(); + + // @todo: Is it necessary to call initialize()? Is it called through createFromRequest()? + $autoloader = include $drupal_root . '/autoload.php'; + Settings::initialize($drupal_root, DrupalKernel::findSitePath($request), $autoloader); + + $app = DrupalKernel::createFromRequest($request, $autoloader, $this->appenv); + + $app->boot(); + + return $app; + } + + /** + * Return the StackPHP stack. + */ + public function getStack(Builder $stack) { + + return $stack; + } + +} diff --git a/Bootstraps/Laravel.php b/Bootstraps/Laravel.php deleted file mode 100644 index e12bce3..0000000 --- a/Bootstraps/Laravel.php +++ /dev/null @@ -1,68 +0,0 @@ -appenv = $appenv; - } - - /** - * Create a Laravel application - */ - public function getApplication() - { - // Laravel 5 / Lumen - if (file_exists('bootstrap/app.php')) { - return $this->app = require_once 'bootstrap/app.php'; - } - - // Laravel 4 - if (file_exists('bootstrap/start.php')) { - require_once 'bootstrap/autoload.php'; - return $this->app = require_once 'bootstrap/start.php'; - } - - throw new \RuntimeException('Laravel bootstrap file not found'); - } - - /** - * Return the StackPHP stack. - * @param Builder $stack - * @return Builder - */ - public function getStack(Builder $stack) - { - $sessionReject = $this->app->bound('session.reject') ? $this->app['session.reject'] : null; - - $stack - ->push('Illuminate\Cookie\Guard', $this->app['encrypter']) - ->push('Illuminate\Cookie\Queue', $this->app['cookie']) - ->push('Illuminate\Session\Middleware', $this->app['session'], $sessionReject); - - return $stack; - } -} diff --git a/Bootstraps/StackableBootstrapInterface.php b/Bootstraps/StackableBootstrapInterface.php deleted file mode 100644 index 04ce608..0000000 --- a/Bootstraps/StackableBootstrapInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -appenv = $appenv; - } - - /** - * Create a Symfony application - */ - public function getApplication() - { - if (file_exists('./app/AppKernel.php')) { - require_once './app/AppKernel.php'; - } - - $this->includeAutoload(); - - $app = new \AppKernel($this->appenv, false); - $app->loadClassCache(); - - return $app; - } - - /** - * Return the StackPHP stack. - */ - public function getStack(Builder $stack) - { - return $stack; - } - - /** - * Includes the autoload file from the app directory, if available. - * - * The Symfony standard edition configures the annotation class loading - * in that file. - * The alternative bootstrap.php.cache cannot be included as that leads - * to "Cannot redeclare class" error, when starting php-pm. - */ - protected function includeAutoload() - { - $info = new \ReflectionClass('AppKernel'); - $appDir = dirname($info->getFileName()); - $symfonyAutoload = $appDir . '/autoload.php'; - if (is_file($symfonyAutoload)) { - require_once $symfonyAutoload; - } - } -} diff --git a/Bridges/DrupalKernel.php b/Bridges/DrupalKernel.php new file mode 100644 index 0000000..0c80b9a --- /dev/null +++ b/Bridges/DrupalKernel.php @@ -0,0 +1,173 @@ +application) { + return; + } + + $content = ''; + $headers = $request->getHeaders(); + $contentLength = isset($headers['Content-Length']) ? (int) $headers['Content-Length'] : 0; + + $request->on('data', function($data) + use ($request, $response, &$content, $contentLength) { + + // Read data (may be empty for GET request). + $content .= $data; + + // Handle request after receive. + if (strlen($content) >= $contentLength) { + $syRequest = self::mapRequest($request, $content); + + try { + $syResponse = $this->application->handle($syRequest); + } + catch (\Exception $exception) { + // Internal server error. + $response->writeHead(500); + $response->end(); + return; + } + + self::mapResponse($response, $syResponse); + + if ($this->application instanceof TerminableInterface) { + $this->application->terminate($syRequest, $syResponse); + } + } + }); + } + + /** + * {@inheritdoc} + */ + protected static function mapRequest(ReactRequest $reactRequest, $content) { + + $method = strtoupper($reactRequest->getMethod()); + $headers = $reactRequest->getHeaders(); + $query = $reactRequest->getQuery(); + $post = array(); + + $requestIsPostType = in_array( + $method, + array('POST', 'PUT', 'DELETE', 'PATCH') + ); + + // Parse body? + if (isset($headers['Content-Type']) && (0 === strpos($headers['Content-Type'], 'application/x-www-form-urlencoded')) + && $requestIsPostType + ) { + parse_str($content, $post); + } + + $cookies = array(); + if (isset($headers['Cookie'])) { + $headersCookie = explode(';', $headers['Cookie']); + foreach ($headersCookie as $cookie) { + list($name, $value) = explode('=', trim($cookie)); + $cookies[$name] = $value; + } + } + + // Add any query string to URI so SymfonyRequest::create() can access it. + $uri = $reactRequest->getPath() . + (empty($query) ? '' : '?' . http_build_query($query)); + + // SymfonyRequest::create() expects $parameters to contain either + // $_GET or $_POST. + $parameters = $requestIsPostType ? $post : $query; + + $syRequest = SymfonyRequest::create( + // $uri, $method, $parameters, $cookies, $files, $server, $content. + $uri, + $method, + $parameters, + $cookies, + array(), + array(), + $content + ); + $syRequest->headers->replace($headers); + + // Set CGI/1.1 (RFC 3875) server vars. + if (empty($_ENV)) { + // In some cases with cli, $_ENV isn't set, so get with getenv(). + // @see http://stackoverflow.com/questions/8798294/getenv-vs-env-in-php/21473853#21473853 + // @todo: Make this more efficient to eliminate running per request. + // Static variable? + $_ENV['DOCUMENT_ROOT'] = getenv('DOCUMENT_ROOT'); + $_ENV['SCRIPT_NAME'] = getenv('SCRIPT_NAME'); + } + $serverVars = array_merge( + $syRequest->server->all(), + array( + 'DOCUMENT_ROOT' => $_ENV['DOCUMENT_ROOT'], + 'GATEWAY_INTERFACE' => 'CGI/1.1', + 'SCRIPT_NAME' => $_ENV['SCRIPT_NAME'], + // SCRIPT_FILENAME contains the name of the php-pm startup script. + // Must override here. + 'SCRIPT_FILENAME' => $_ENV['DOCUMENT_ROOT'] . $_ENV['SCRIPT_NAME'], + ) + ); + $syRequest->server->replace($serverVars); + + return $syRequest; + } + + + /** + * {@inheritdoc} + */ + protected static function mapResponse(ReactResponse $reactResponse, + SymfonyResponse $syResponse) { + + $headers = $syResponse->headers->all(); + $reactResponse->writeHead($syResponse->getStatusCode(), $headers); + + // @TODO convert StreamedResponse in an async manner + if ($syResponse instanceof SymfonyStreamedResponse) { + ob_start(); + $syResponse->sendContent(); + $content = ob_get_contents(); + ob_end_clean(); + } + else { + $content = $syResponse->getContent(); + } + + $reactResponse->end($content); + } + +} diff --git a/Bridges/HttpKernel.php b/Bridges/HttpKernel.php deleted file mode 100644 index 5423d9b..0000000 --- a/Bridges/HttpKernel.php +++ /dev/null @@ -1,187 +0,0 @@ -normalizeAppBootstrap($appBootstrap); - - $bootstrap = new $appBootstrap($appenv); - - if ($bootstrap instanceof BootstrapInterface) { - $this->application = $bootstrap->getApplication(); - - if ($bootstrap instanceof StackableBootstrapInterface) { - $stack = new Builder(); - $stack = $bootstrap->getStack($stack); - $this->application = $stack->resolve($this->application); - } - } - } - - /** - * Handle a request using a HttpKernelInterface implementing application. - * - * @param \React\Http\Request $request - * @param \React\Http\Response $response - */ - public function onRequest(ReactRequest $request, ReactResponse $response) - { - if (null === $this->application) { - return; - } - - $content = ''; - $headers = $request->getHeaders(); - $contentLength = isset($headers['Content-Length']) ? (int) $headers['Content-Length'] : 0; - - $request->on('data', function($data) - use ($request, $response, &$content, $contentLength) - { - // read data (may be empty for GET request) - $content .= $data; - - // handle request after receive - if (strlen($content) >= $contentLength) { - $syRequest = self::mapRequest($request, $content); - - try { - $syResponse = $this->application->handle($syRequest); - } catch (\Exception $exception) { - $response->writeHead(500); // internal server error - $response->end(); - return; - } - - self::mapResponse($response, $syResponse); - - if ($this->application instanceof TerminableInterface) { - $this->application->terminate($syRequest, $syResponse); - } - } - }); - } - - /** - * Convert React\Http\Request to Symfony\Component\HttpFoundation\Request - * - * @param ReactRequest $reactRequest - * @return SymfonyRequest $syRequest - */ - protected static function mapRequest(ReactRequest $reactRequest, $content) - { - $method = $reactRequest->getMethod(); - $headers = $reactRequest->getHeaders(); - $query = $reactRequest->getQuery(); - $post = array(); - - // parse body? - if (isset($headers['Content-Type']) && (0 === strpos($headers['Content-Type'], 'application/x-www-form-urlencoded')) - && in_array(strtoupper($method), array('POST', 'PUT', 'DELETE', 'PATCH')) - ) { - parse_str($content, $post); - } - - $cookies = array(); - if (isset($headers['Cookie'])) { - $headersCookie = explode(';', $headers['Cookie']); - foreach ($headersCookie as $cookie) { - list($name, $value) = explode('=', trim($cookie)); - $cookies[$name] = $value; - } - } - - $parameters = 'GET' === $method ? $query : $post; - $syRequest = SymfonyRequest::create( - // $uri, $method , $parameters , $cookies , $files , $server , $content - $reactRequest->getPath(), $method, $parameters, $cookies, array(), array(), $content - ); - $syRequest->headers->replace($headers); - - return $syRequest; - } - - /** - * Convert Symfony\Component\HttpFoundation\Response to React\Http\Response - * - * @param ReactResponse $reactResponse - * @param SymfonyResponse $syResponse - */ - protected static function mapResponse(ReactResponse $reactResponse, - SymfonyResponse $syResponse) - { - $headers = $syResponse->headers->all(); - $reactResponse->writeHead($syResponse->getStatusCode(), $headers); - - // @TODO convert StreamedResponse in an async manner - if ($syResponse instanceof SymfonyStreamedResponse) { - ob_start(); - $syResponse->sendContent(); - $content = ob_get_contents(); - ob_end_clean(); - } - else { - $content = $syResponse->getContent(); - } - - $reactResponse->end($content); - } - - /** - * @param $appBootstrap - * @return string - * @throws \RuntimeException - */ - protected function normalizeAppBootstrap($appBootstrap) - { - $appBootstrap = str_replace('\\\\', '\\', $appBootstrap); - if (false === class_exists($appBootstrap)) { - $appBootstrap = '\\' . $appBootstrap; - if (false === class_exists($appBootstrap)) { - throw new \RuntimeException('Could not find bootstrap class ' . $appBootstrap); - } - return $appBootstrap; - } - return $appBootstrap; - } -} diff --git a/README.md b/README.md index 957388c..9af34c2 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,76 @@ -# PHP-PM HttpKernel Adapter +# PHP-PM DrupalKernel Adapter -HttpKernel adapter for use of Symfony and Laravel frameworks with PHP-PM. See https://github.com/php-pm/php-pm. +## Overview -### Setup +This is a fork of PHP-PM's HttpKernel adapter for integrating Drupal with PHP-PM (therefore, also with ReactPHP). - 1. Install PHP-PM +The primary components are a bootstrap and bridge. - composer require php-pm/php-pm:dev-master +See: +* https://github.com/php-pm/php-pm +* https://github.com/php-pm/php-pm-httpkernel +* http://marcjschmidt.de/blog/2014/02/08/php-high-performance.html - 2. Install HttpKernel Adapter +The code is in alpha -- very experimental. Last tested against `drupal-8.0.2`. - composer require php-pm/httpkernel-adapter:dev-master +View / report issues at https://github.com/php-pm/php-pm-drupal/issues. +### Setup / Usage + + 1. Install Drupal. + + 2. From the Drupal web root, install this project with composer: `composer require php-pm/drupal-adapter`. + + This will also install PHP-PM and the default React <-> Symfony bridge (php-pm/httpkernel-adapter). + + 3. Apply these patches to Drupal core: + * `vendor/kentr/php-pm-drupal-adapter/patches/kentr-allow-repeated-setSitePath-in-DrupalKernel.patch` + * `vendor/kentr/php-pm-drupal-adapter/patches/stop_using-2505339-24.patch` + + 4. Start php-pm with + +```bash +/vendor/bin/ppm \ +start \ + \ +--bridge=httpKernel \ +--bootstrap=PHPPM\\Bootstraps\\Drupal +``` + +Example: +```bash +/var/www/html/vendor/bin/ppm \ +start \ +/var/www/html/ \ +--bridge=httpKernel \ +--bootstrap=PHPPM\\Bootstraps\\Drupal +``` + +## DrupalKernel bridge +By default, PHP-PM uses the `\PHPPM\Bridges\HttpKernel` bridge to convert a ReactPHP request into a Symfony request and the Symfony response into a ReactPHP response. + +The included `\PHPPM\Bridges\DrupalKernel` bridge extends `\PHPPM\Bridges\HttpKernel` to populate various request meta-variables specified by [CGI/1.1 (RFC 3875)](http://www.faqs.org/rfcs/rfc3875.html). + +### Setup / Usage + + 1. Install as described above. + + 2. Include the environment variables and the `--bridge` option in the php-pm start command. + + Supported environment variables: + * **SCRIPT_NAME:** '/index.php' to emulate a standard setup where web requests execute Drupal's `index.php` script. + * **SERVER_NAME:** Your site's server / domain name. If you're using trusted host settings (`$settings['trusted_host_patterns']` in `settings.php`), this must match one of the trusted hosts. + * **SERVER_ADDRESS:** IP address of the server. + * **DOCUMENT_ROOT:** Absolute filepath of the web root directory. + +Example: + +```bash +SCRIPT_NAME=/index.php \ +SERVER_NAME=localhost \ +SERVER_ADDRESS=127.0.0.1 \ +DOCUMENT_ROOT=/var/www/html \ +/var/www/html/vendor/bin/ppm start /var/www/html \ +--bridge=PHPPM\\Bridges\\DrupalKernel \ +--bootstrap=PHPPM\\Bootstraps\\Drupal +``` diff --git a/composer.json b/composer.json index 46238b4..98397a1 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,8 @@ { - "name": "php-pm/httpkernel-adapter", + "name": "php-pm/drupal-adapter", "require": { - "stack/builder": "1.0.*", - "symfony/http-foundation": "~2.6", - "symfony/http-kernel": "~2.6" + "php-pm/php-pm": "*", + "php-pm/httpkernel-adapter": "*" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock deleted file mode 100644 index ce6c280..0000000 --- a/composer.lock +++ /dev/null @@ -1,357 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", - "This file is @generated automatically" - ], - "hash": "07978eaced6c127cfa69dce363ff628e", - "packages": [ - { - "name": "psr/log", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", - "shasum": "" - }, - "type": "library", - "autoload": { - "psr-0": { - "Psr\\Log\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "time": "2012-12-21 11:40:51" - }, - { - "name": "stack/builder", - "version": "v1.0.3", - "source": { - "type": "git", - "url": "https://github.com/stackphp/builder.git", - "reference": "c1f8a4693b55c563405024f708a76ef576c3b276" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/stackphp/builder/zipball/c1f8a4693b55c563405024f708a76ef576c3b276", - "reference": "c1f8a4693b55c563405024f708a76ef576c3b276", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "symfony/http-foundation": "~2.1", - "symfony/http-kernel": "~2.1" - }, - "require-dev": { - "silex/silex": "~1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "Stack": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - } - ], - "description": "Builder for stack middlewares based on HttpKernelInterface.", - "keywords": [ - "stack" - ], - "time": "2014-11-23 20:37:11" - }, - { - "name": "symfony/debug", - "version": "v2.6.6", - "target-dir": "Symfony/Component/Debug", - "source": { - "type": "git", - "url": "https://github.com/symfony/Debug.git", - "reference": "d49a46a20a8f0544aedac54466750ad787d3d3e3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Debug/zipball/d49a46a20a8f0544aedac54466750ad787d3d3e3", - "reference": "d49a46a20a8f0544aedac54466750ad787d3d3e3", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" - }, - "require-dev": { - "symfony/class-loader": "~2.2", - "symfony/http-foundation": "~2.1", - "symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2", - "symfony/phpunit-bridge": "~2.7" - }, - "suggest": { - "symfony/http-foundation": "", - "symfony/http-kernel": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Debug\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Debug Component", - "homepage": "http://symfony.com", - "time": "2015-03-22 16:55:57" - }, - { - "name": "symfony/event-dispatcher", - "version": "v2.6.6", - "target-dir": "Symfony/Component/EventDispatcher", - "source": { - "type": "git", - "url": "https://github.com/symfony/EventDispatcher.git", - "reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/70f7c8478739ad21e3deef0d977b38c77f1fb284", - "reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.0,>=2.0.5", - "symfony/dependency-injection": "~2.6", - "symfony/expression-language": "~2.6", - "symfony/phpunit-bridge": "~2.7", - "symfony/stopwatch": "~2.3" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony EventDispatcher Component", - "homepage": "http://symfony.com", - "time": "2015-03-13 17:37:22" - }, - { - "name": "symfony/http-foundation", - "version": "v2.6.6", - "target-dir": "Symfony/Component/HttpFoundation", - "source": { - "type": "git", - "url": "https://github.com/symfony/HttpFoundation.git", - "reference": "8a6337233f08f7520de97f4ffd6f00e947d892f9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/8a6337233f08f7520de97f4ffd6f00e947d892f9", - "reference": "8a6337233f08f7520de97f4ffd6f00e947d892f9", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "symfony/expression-language": "~2.4", - "symfony/phpunit-bridge": "~2.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\HttpFoundation\\": "" - }, - "classmap": [ - "Symfony/Component/HttpFoundation/Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony HttpFoundation Component", - "homepage": "http://symfony.com", - "time": "2015-04-01 16:50:12" - }, - { - "name": "symfony/http-kernel", - "version": "v2.6.6", - "target-dir": "Symfony/Component/HttpKernel", - "source": { - "type": "git", - "url": "https://github.com/symfony/HttpKernel.git", - "reference": "3829cacfe21eaf3f73604a62d79183d1f6e792c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/3829cacfe21eaf3f73604a62d79183d1f6e792c4", - "reference": "3829cacfe21eaf3f73604a62d79183d1f6e792c4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "psr/log": "~1.0", - "symfony/debug": "~2.6,>=2.6.2", - "symfony/event-dispatcher": "~2.5.9|~2.6,>=2.6.2", - "symfony/http-foundation": "~2.5,>=2.5.4" - }, - "require-dev": { - "symfony/browser-kit": "~2.3", - "symfony/class-loader": "~2.1", - "symfony/config": "~2.0,>=2.0.5", - "symfony/console": "~2.3", - "symfony/css-selector": "~2.0,>=2.0.5", - "symfony/dependency-injection": "~2.2", - "symfony/dom-crawler": "~2.0,>=2.0.5", - "symfony/expression-language": "~2.4", - "symfony/finder": "~2.0,>=2.0.5", - "symfony/phpunit-bridge": "~2.7", - "symfony/process": "~2.0,>=2.0.5", - "symfony/routing": "~2.2", - "symfony/stopwatch": "~2.3", - "symfony/templating": "~2.2", - "symfony/translation": "~2.0,>=2.0.5", - "symfony/var-dumper": "~2.6" - }, - "suggest": { - "symfony/browser-kit": "", - "symfony/class-loader": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "", - "symfony/finder": "", - "symfony/var-dumper": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\HttpKernel\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony HttpKernel Component", - "homepage": "http://symfony.com", - "time": "2015-04-01 16:55:26" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": [], - "platform-dev": [] -} diff --git a/patches/kentr-allow-repeated-setSitePath-in-DrupalKernel.patch b/patches/kentr-allow-repeated-setSitePath-in-DrupalKernel.patch new file mode 100644 index 0000000..06f4f0f --- /dev/null +++ b/patches/kentr-allow-repeated-setSitePath-in-DrupalKernel.patch @@ -0,0 +1,13 @@ +diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php +index 274a3fa..9089a7d 100644 +--- a/core/lib/Drupal/Core/DrupalKernel.php ++++ b/core/lib/Drupal/Core/DrupalKernel.php +@@ -410,7 +410,7 @@ public static function findSitePath(Request $request, $require_settings = TRUE, + * {@inheritdoc} + */ + public function setSitePath($path) { +- if ($this->booted) { ++ if ($this->booted && $path !== $this->sitePath) { + throw new \LogicException('Site path cannot be changed after calling boot()'); + } + $this->sitePath = $path; diff --git a/patches/stop_using-2505339-24.patch b/patches/stop_using-2505339-24.patch new file mode 100644 index 0000000..5db4054 --- /dev/null +++ b/patches/stop_using-2505339-24.patch @@ -0,0 +1,240 @@ +diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php +index 46336e0..a21dc40 100644 +--- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php ++++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php +@@ -45,6 +45,16 @@ class DefaultExceptionHtmlSubscriber extends HttpExceptionSubscriberBase { + protected $redirectDestination; + + /** ++ * URL query attribute to specify the status code resulting from an exception. ++ */ ++ const EXCEPTION_STATUSCODE = '_exception_statuscode'; ++ ++ /** ++ * URL query attribute to specify the location where an exception occured. ++ */ ++ const EXCEPTION_LOCATION = '_exception_location'; ++ ++ /** + * Constructs a new DefaultExceptionHtmlSubscriber. + * + * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel +@@ -127,11 +137,22 @@ protected function makeSubrequest(GetResponseForExceptionEvent $event, $url, $st + $current_url = $request->getBasePath() . $request->getPathInfo(); + + if ($url != $request->getBasePath() . '/' && $url != $current_url) { ++ // Pass the current location and the status code into the subrequest. ++ // Forms rendered on that page may then leverage this information, e.g., ++ // to redirect to the original location after a successfull login. Note ++ // that the 'destination' parameter takes precedence over a form redirect ++ // and thus cannot be used here. Otherwise it would be impossible to ++ // implement forms on an error page which need to redirect to a specific ++ // location after submission. ++ $exception_params = [ ++ static::EXCEPTION_LOCATION => $this->redirectDestination->get(), ++ static::EXCEPTION_STATUSCODE => $status_code, ++ ]; + if ($request->getMethod() === 'POST') { +- $sub_request = Request::create($url, 'POST', $this->redirectDestination->getAsArray() + ['_exception_statuscode' => $status_code] + $request->request->all(), $request->cookies->all(), [], $request->server->all()); ++ $sub_request = Request::create($url, 'POST', $exception_params + $request->request->all(), $request->cookies->all(), [], $request->server->all()); + } + else { +- $sub_request = Request::create($url, 'GET', $request->query->all() + $this->redirectDestination->getAsArray() + ['_exception_statuscode' => $status_code], $request->cookies->all(), [], $request->server->all()); ++ $sub_request = Request::create($url, 'GET', $request->query->all() + $exception_params, $request->cookies->all(), [], $request->server->all()); + } + + try { +diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php +index bb7034f..19e4a53 100644 +--- a/core/lib/Drupal/Core/Form/FormBuilder.php ++++ b/core/lib/Drupal/Core/Form/FormBuilder.php +@@ -829,9 +829,7 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) { + * The URL to be used as the $form['#action']. + */ + protected function buildFormAction() { +- // @todo Use instead of the master request in +- // https://www.drupal.org/node/2505339. +- $request = $this->requestStack->getMasterRequest(); ++ $request = $this->requestStack->getCurrentRequest(); + $request_uri = $request->getRequestUri(); + + // Prevent cross site requests via the Form API by using an absolute URL +diff --git a/core/modules/system/src/Tests/Form/ExternalFormUrlTest.php b/core/modules/system/src/Tests/Form/ExternalFormUrlTest.php +index f179ec9..9ddeaff 100644 +--- a/core/modules/system/src/Tests/Form/ExternalFormUrlTest.php ++++ b/core/modules/system/src/Tests/Form/ExternalFormUrlTest.php +@@ -74,10 +74,11 @@ protected function setUp() { + * Tests form behaviour. + */ + public function testActionUrlBehavior() { +- // Create a new request which has a request uri with multiple leading +- // slashes and make it the master request. + $request_stack = \Drupal::service('request_stack'); +- $original_request = $request_stack->pop(); ++ $original_request = $request_stack->getCurrentRequest(); ++ ++ // Create a new request which has a request uri with multiple leading ++ // slashes and make it the current request. + $request = Request::create($original_request->getSchemeAndHttpHost() . '//example.org'); + $request_stack->push($request); + +@@ -88,11 +89,10 @@ public function testActionUrlBehavior() { + $elements = $this->xpath('//form/@action'); + $action = (string) $elements[0]; + $this->assertEqual($original_request->getSchemeAndHttpHost() . '//example.org', $action); ++ $request_stack->pop(); + + // Create a new request which has a request uri with a single leading slash +- // and make it the master request. +- $request_stack = \Drupal::service('request_stack'); +- $original_request = $request_stack->pop(); ++ // and make it the current request. + $request = Request::create($original_request->getSchemeAndHttpHost() . '/example.org'); + $request_stack->push($request); + +@@ -103,6 +103,7 @@ public function testActionUrlBehavior() { + $elements = $this->xpath('//form/@action'); + $action = (string) $elements[0]; + $this->assertEqual('/example.org', $action); ++ $request_stack->pop(); + } + + } +diff --git a/core/modules/system/src/Tests/Routing/DestinationTest.php b/core/modules/system/src/Tests/Routing/DestinationTest.php +index a590d62..56a7edf 100644 +--- a/core/modules/system/src/Tests/Routing/DestinationTest.php ++++ b/core/modules/system/src/Tests/Routing/DestinationTest.php +@@ -72,9 +72,9 @@ public function testDestination() { + $this->assertIdentical($test_case['output'], $post_output, $test_case['message']); + } + +- // Make sure that 404 pages do not populate $_GET['destination'] with +- // external URLs. +- \Drupal::configFactory()->getEditable('system.site')->set('page.404', 'system-test/get-destination')->save(); ++ // Make sure that 404 pages do not populate $_GET['_exception_location'] ++ // with external URLs. ++ \Drupal::configFactory()->getEditable('system.site')->set('page.404', 'system-test/get-exception-location')->save(); + $this->drupalGet('http://example.com', ['external' => FALSE]); + $this->assertResponse(404); + $this->assertIdentical(Url::fromRoute('')->toString(), $this->getRawContent(), 'External URL is not allowed on 404 pages.'); +diff --git a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php +index 9d35102..502fb03 100644 +--- a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php ++++ b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php +@@ -10,15 +10,16 @@ + use Drupal\Core\Access\AccessResult; + use Drupal\Core\Cache\CacheableResponse; + use Drupal\Core\Controller\ControllerBase; ++use Drupal\Core\EventSubscriber\DefaultExceptionHtmlSubscriber; ++use Drupal\Core\Lock\LockBackendInterface; + use Drupal\Core\Render\RendererInterface; + use Drupal\Core\Render\Markup; + use Drupal\Core\Session\AccountInterface; + use Drupal\Core\Url; ++use Symfony\Component\DependencyInjection\ContainerInterface; + use Symfony\Component\HttpFoundation\RedirectResponse; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; +-use Drupal\Core\Lock\LockBackendInterface; +-use Symfony\Component\DependencyInjection\ContainerInterface; + + /** + * Controller routines for system_test routes. +@@ -149,6 +150,20 @@ public function getDestination(Request $request) { + } + + /** ++ * Controller to return $_GET['_exception_location'] for testing. ++ * ++ * @param \Symfony\Component\HttpFoundation\Request $request ++ * The request. ++ * ++ * @return \Symfony\Component\HttpFoundation\Response ++ * The response. ++ */ ++ public function getExceptionLocation(Request $request) { ++ $response = new Response($request->query->get(DefaultExceptionHtmlSubscriber::EXCEPTION_LOCATION)); ++ return $response; ++ } ++ ++ /** + * Controller to return $_REQUEST['destination'] for testing. + * + * @param \Symfony\Component\HttpFoundation\Request $request +diff --git a/core/modules/system/tests/modules/system_test/system_test.routing.yml b/core/modules/system/tests/modules/system_test/system_test.routing.yml +index 1fb4069..de4fa19 100644 +--- a/core/modules/system/tests/modules/system_test/system_test.routing.yml ++++ b/core/modules/system/tests/modules/system_test/system_test.routing.yml +@@ -116,6 +116,13 @@ system_test.get_destination: + requirements: + _access: 'TRUE' + ++system_test.get_exception_location: ++ path: '/system-test/get-exception-location' ++ defaults: ++ _controller: '\Drupal\system_test\Controller\SystemTestController::getExceptionLocation' ++ requirements: ++ _access: 'TRUE' ++ + system_test.permission_dependent_content: + path: '/system-test/permission-dependent-content' + defaults: +diff --git a/core/modules/user/src/Form/UserLoginForm.php b/core/modules/user/src/Form/UserLoginForm.php +index b336091..88329e7 100644 +--- a/core/modules/user/src/Form/UserLoginForm.php ++++ b/core/modules/user/src/Form/UserLoginForm.php +@@ -7,6 +7,7 @@ + + namespace Drupal\user\Form; + ++use Drupal\Core\EventSubscriber\DefaultExceptionHtmlSubscriber; + use Drupal\Core\Flood\FloodInterface; + use Drupal\Core\Form\FormBase; + use Drupal\Core\Form\FormStateInterface; +@@ -134,16 +135,17 @@ public function buildForm(array $form, FormStateInterface $form_state) { + public function submitForm(array &$form, FormStateInterface $form_state) { + $account = $this->userStorage->load($form_state->get('uid')); + +- // A destination was set, probably on an exception controller, +- if (!$this->getRequest()->request->has('destination')) { ++ // A login form rendered on an error page (e.g. 403) should redirect to the ++ // original location after a successfull login. ++ if ($original_location = $this->getRequest()->get(DefaultExceptionHtmlSubscriber::EXCEPTION_LOCATION)) { ++ $this->getRequest()->query->set('destination', $original_location); ++ } ++ else{ + $form_state->setRedirect( + 'entity.user.canonical', + array('user' => $account->id()) + ); + } +- else { +- $this->getRequest()->query->set('destination', $this->getRequest()->request->get('destination')); +- } + + user_login_finalize($account); + } +diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/CustomPageExceptionHtmlSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/CustomPageExceptionHtmlSubscriberTest.php +index a7efb7e..ab2989c 100644 +--- a/core/tests/Drupal/Tests/Core/EventSubscriber/CustomPageExceptionHtmlSubscriberTest.php ++++ b/core/tests/Drupal/Tests/Core/EventSubscriber/CustomPageExceptionHtmlSubscriberTest.php +@@ -82,8 +82,8 @@ protected function setUp() { + $this->redirectDestination = $this->getMock('\Drupal\Core\Routing\RedirectDestinationInterface'); + + $this->redirectDestination->expects($this->any()) +- ->method('getAsArray') +- ->willReturn(['destination' => 'test']); ++ ->method('get') ++ ->willReturn('test'); + + $this->customPageSubscriber = new CustomPageExceptionHtmlSubscriber($this->configFactory, $this->aliasManager, $this->kernel, $this->logger, $this->redirectDestination); + +@@ -146,7 +146,7 @@ public function testHandleWithGetRequest() { + + $response = $event->getResponse(); + $result = $response->getContent() . " " . UrlHelper::buildQuery($request->request->all()); +- $this->assertEquals('GET name=druplicon&pass=12345&destination=test&_exception_statuscode=404 ', $result); ++ $this->assertEquals('GET name=druplicon&pass=12345&_exception_location=test&_exception_statuscode=404 ', $result); + } + + }