Commit 30206132 authored by Dries's avatar Dries

Merge remote-tracking branch 'wscci/kernel' into 8.x

parents 5b0715d7 94d877eb
......@@ -84,10 +84,7 @@ function authorize_access_allowed() {
drupal_load('module', 'system');
drupal_load('module', 'user');
// We also want to have the language system available, but we do *NOT* want to
// actually call drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE), since that would
// also force us through the DRUPAL_BOOTSTRAP_PAGE_HEADER phase, which loads
// all the modules, and that's exactly what we're trying to avoid.
// Initialize the language system.
drupal_language_initialize();
// Initialize the maintenance theme for this administrative script.
......
......@@ -14,6 +14,8 @@
* @see batch_get()
*/
use \Symfony\Component\HttpFoundation\JsonResponse;
/**
* Loads a batch from the database.
*
......@@ -77,7 +79,7 @@ function _batch_page() {
case 'do':
// JavaScript-based progress page callback.
_batch_do();
$output = _batch_do();
break;
case 'do_nojs':
......@@ -160,7 +162,7 @@ function _batch_do() {
// Perform actual processing.
list($percentage, $message) = _batch_process();
drupal_json_output(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message));
return new JsonResponse(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message));
}
/**
......
......@@ -4,6 +4,7 @@
use Symfony\Component\ClassLoader\UniversalClassLoader;
use Symfony\Component\ClassLoader\ApcUniversalClassLoader;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpFoundation\Request;
/**
* @file
......@@ -137,12 +138,12 @@
const DRUPAL_BOOTSTRAP_PAGE_HEADER = 5;
/**
* Seventh bootstrap phase: find out language of the page.
* Seventh bootstrap phase: load code for subsystems and modules.
*/
const DRUPAL_BOOTSTRAP_LANGUAGE = 6;
const DRUPAL_BOOTSTRAP_CODE = 6;
/**
* Final bootstrap phase: Drupal is fully loaded; validate and fix input data.
* Final bootstrap phase: initialize language, path, theme, and modules.
*/
const DRUPAL_BOOTSTRAP_FULL = 7;
......@@ -1605,6 +1606,29 @@ function request_uri($omit_query_string = FALSE) {
return $omit_query_string ? strtok($uri, '?') : $uri;
}
/**
* Returns the current global request object.
*
* @todo Replace this function with a proper dependency injection container.
*
* @staticvar Symfony\Component\HttpFoundation\Request $request
*
* @param Symfony\Component\HttpFoundation\Request $new_request
* Optional. The new request object to store. This parameter should only be
* used by index.php.
*
* @return Symfony\Component\HttpFoundation\Request
* The current request object.
*/
function request(Request $new_request = NULL) {
static $request;
if ($new_request) {
$request = $new_request;
}
return $request;
}
/**
* Logs an exception.
*
......@@ -2094,7 +2118,7 @@ function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
DRUPAL_BOOTSTRAP_VARIABLES,
DRUPAL_BOOTSTRAP_SESSION,
DRUPAL_BOOTSTRAP_PAGE_HEADER,
DRUPAL_BOOTSTRAP_LANGUAGE,
DRUPAL_BOOTSTRAP_CODE,
DRUPAL_BOOTSTRAP_FULL,
);
// Not drupal_static(), because the only legitimate API to control this is to
......@@ -2147,12 +2171,12 @@ function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
_drupal_bootstrap_page_header();
break;
case DRUPAL_BOOTSTRAP_LANGUAGE:
drupal_language_initialize();
case DRUPAL_BOOTSTRAP_CODE:
require_once DRUPAL_ROOT . '/core/includes/common.inc';
_drupal_bootstrap_code();
break;
case DRUPAL_BOOTSTRAP_FULL:
require_once DRUPAL_ROOT . '/core/includes/common.inc';
_drupal_bootstrap_full();
break;
}
......@@ -2375,7 +2399,6 @@ function _drupal_bootstrap_page_header() {
if (!drupal_is_cli()) {
ob_start();
drupal_page_header();
}
}
......
<?php
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Drupal\Core\Database\Database;
/**
......@@ -705,7 +707,7 @@ function drupal_site_offline() {
* bubble up to menu_execute_active_handler() should call drupal_not_found().
*/
function drupal_not_found() {
drupal_deliver_page(MENU_NOT_FOUND);
throw new NotFoundHttpException();
}
/**
......@@ -718,7 +720,7 @@ function drupal_not_found() {
* drupal_access_denied().
*/
function drupal_access_denied() {
drupal_deliver_page(MENU_ACCESS_DENIED);
throw new AccessDeniedHttpException();
}
/**
......@@ -5101,13 +5103,10 @@ function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
return (($skip_anonymous && $user->uid == 0) || ($token == drupal_get_token($value)));
}
function _drupal_bootstrap_full() {
static $called = FALSE;
if ($called) {
return;
}
$called = TRUE;
/**
* Loads code for subsystems and modules, and registers stream wrappers.
*/
function _drupal_bootstrap_code() {
require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'core/includes/path.inc');
require_once DRUPAL_ROOT . '/core/includes/theme.inc';
require_once DRUPAL_ROOT . '/core/includes/pager.inc';
......@@ -5126,16 +5125,38 @@ function _drupal_bootstrap_full() {
// Load all enabled modules
module_load_all();
// Make sure all stream wrappers are registered.
file_get_stream_wrappers();
// Now that stream wrappers are registered, log fatal errors from a simpletest
// child site to a test specific file directory.
$test_info = &$GLOBALS['drupal_test_info'];
if (!empty($test_info['in_child_site'])) {
// Running inside the simpletest child site, log fatal errors to test
// specific file directory.
ini_set('log_errors', 1);
ini_set('error_log', 'public://error.log');
}
}
/**
* Temporary BC function for scripts not using DrupalKernel.
*
* DrupalKernel skips this and replicates it via event listeners.
*
* @see Drupal\Core\EventSubscriber\PathSubscriber;
* @see Drupal\Core\EventSubscriber\LegacyRequestSubscriber;
*/
function _drupal_bootstrap_full($skip = FALSE) {
static $called = FALSE;
if ($called || $skip) {
$called = TRUE;
return;
}
// Initialize language (which can strip path prefix) prior to initializing
// current_path().
drupal_language_initialize();
// Initialize current_path() prior to invoking hook_init().
drupal_path_initialize();
......
......@@ -5,6 +5,8 @@
* Functions for error handling.
*/
use Symfony\Component\HttpFoundation\Response;
/**
* Error reporting level: display no errors.
*/
......@@ -215,10 +217,6 @@ function _drupal_log_error($error, $fatal = FALSE) {
watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
if ($fatal) {
drupal_add_http_header('Status', '500 Service unavailable (with message)');
}
if (drupal_is_cli()) {
if ($fatal) {
// When called from CLI, simply output a plain text message.
......@@ -254,8 +252,14 @@ function _drupal_log_error($error, $fatal = FALSE) {
drupal_set_title(t('Error'));
// We fallback to a maintenance page at this point, because the page generation
// itself can generate errors.
print theme('maintenance_page', array('content' => t('The website encountered an unexpected error. Please try again later.')));
exit;
$output = theme('maintenance_page', array('content' => t('The website encountered an unexpected error. Please try again later.')));
$response = new Response($output, 500);
if ($fatal) {
$response->setStatusCode(500, '500 Service unavailable (with message)');
}
return $response;
}
}
}
......
......@@ -5,6 +5,9 @@
* API for handling file uploads and server file management.
*/
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Drupal\Core\StreamWrapper\LocalStream;
/**
......@@ -1975,26 +1978,15 @@ function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EX
* An array of HTTP headers to send along with file.
*/
function file_transfer($uri, $headers) {
if (ob_get_level()) {
ob_end_clean();
}
foreach ($headers as $name => $value) {
drupal_add_http_header($name, $value);
}
drupal_send_headers();
$scheme = file_uri_scheme($uri);
// Transfer file in 1024 byte chunks to save memory usage.
if ($scheme && file_stream_wrapper_valid_scheme($scheme) && $fd = fopen($uri, 'rb')) {
while (!feof($fd)) {
print fread($fd, 1024);
return new StreamedResponse(function() use ($uri) {
// Transfer file in 1024 byte chunks to save memory usage.
if (file_exists($uri) && $fd = fopen($uri, 'rb')) {
while (!feof($fd)) {
print fread($fd, 1024);
}
fclose($fd);
}
fclose($fd);
}
else {
drupal_not_found();
}
drupal_exit();
}, 200, $headers);
}
/**
......@@ -2026,18 +2018,18 @@ function file_download() {
$function = $module . '_file_download';
$result = $function($uri);
if ($result == -1) {
return drupal_access_denied();
throw new AccessDeniedHttpException();
}
if (isset($result) && is_array($result)) {
$headers = array_merge($headers, $result);
}
}
if (count($headers)) {
file_transfer($uri, $headers);
return file_transfer($uri, $headers);
}
return drupal_access_denied();
throw new AccessDeniedHttpException();
}
return drupal_not_found();
throw new NotFoundHttpException();
}
......@@ -2517,7 +2509,8 @@ function file_directory_temp() {
* A file object.
*
* @return
* An associative array of headers, as expected by file_transfer().
* An associative array of headers, as expected by
* \Symfony\Component\HttpFoundation\StreamedResponse.
*/
function file_get_content_headers($file) {
$name = mime_header_encode($file->filename);
......
......@@ -3,6 +3,9 @@
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Install\TaskException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* @file
* API functions for installing Drupal.
......@@ -248,6 +251,14 @@ function install_begin_request(&$install_state) {
drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
// A request object from the HTTPFoundation to tell us about the request.
$request = Request::createFromGlobals();
// Set the global $request object. This is a temporary measure to
// keep legacy utility functions working. It should be moved to a dependency
// injection container at some point.
request($request);
// This must go after drupal_bootstrap(), which unsets globals!
global $conf;
......@@ -477,6 +488,15 @@ function install_run_task($task, &$install_state) {
elseif ($current_batch == $function) {
include_once DRUPAL_ROOT . '/core/includes/batch.inc';
$output = _batch_page();
// Because Batch API now returns a JSON response for intermediary steps,
// but the installer doesn't handle Response objects yet, just send the
// output here and emulate the old model.
// @todo Replace this when we refactor the installer to use a request-
// response workflow.
if ($output instanceof Response) {
$output->send();
$output = NULL;
}
// The task is complete when we try to access the batch page and receive
// FALSE in return, since this means we are at a URL where we are no
// longer requesting a batch ID.
......
<?php
/**
* @file
* Definition of Drupal\Core\ContentNegotiation.
*/
namespace Drupal\Core;
use Symfony\Component\HttpFoundation\Request;
/**
* This class is a central library for content type negotiation.
*
* @todo Replace this class with a real content negotiation library based on
* mod_negotiation. Development of that is a work in progress.
*/
class ContentNegotiation {
/**
* Gets the normalized type of a request.
*
* The normalized type is a short, lowercase version of the format, such as
* 'html', 'json' or 'atom'.
*
* @param Symfony\Component\HttpFoundation\Request $request
* The request object from which to extract the content type.
*
* @return
* The normalized type of a given request.
*/
public function getContentType(Request $request) {
// AJAX iframe uploads need special handling, because they contain a JSON
// response wrapped in <textarea>.
if ($request->get('ajax_iframe_upload', FALSE)) {
return 'iframeupload';
}
// AJAX calls need to be run through ajax rendering functions
elseif ($request->isXmlHttpRequest()) {
return 'ajax';
}
foreach ($request->getAcceptableContentTypes() as $mime_type) {
$format = $request->getFormat($mime_type);
if (!is_null($format)) {
return $format;
}
}
// Do HTML last so that it always wins.
return 'html';
}
}
......@@ -524,15 +524,14 @@ public function query($query, array $args = array(), $options = array()) {
}
catch (PDOException $e) {
if ($options['throw_exception']) {
// Add additional debug information.
if ($query instanceof DatabaseStatementInterface) {
$e->query_string = $stmt->getQueryString();
}
else {
$e->query_string = $query;
}
$e->args = $args;
throw $e;
// Wrap the exception in another exception, because PHP does not allow
// overriding Exception::getMessage(). Its message is the extra database
// debug information.
$query_string = ($query instanceof DatabaseStatementInterface) ? $stmt->getQueryString() : $query;
$message = $e->getMessage() . ": " . $query_string . "; " . print_r($args, TRUE);
$exception = new DatabaseExceptionWrapper($message, 0, $e);
throw $exception;
}
return NULL;
}
......
<?php
/**
* @file
* Definition of Drupal\Core\Database\DatabaseExceptionWrapper.
*/
namespace Drupal\Core\Database;
use RuntimeException;
/**
* This wrapper class serves only to provide additional debug information.
*
* This class will always wrap a PDOException.
*/
class DatabaseExceptionWrapper extends RuntimeException implements DatabaseException {
}
......@@ -7,12 +7,14 @@
namespace Drupal\Core\Database\Driver\mysql;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\TransactionCommitFailedException;
use Drupal\Core\Database\DatabaseException;
use Drupal\Core\Database\Connection as DatabaseConnection;
use PDO;
use PDOException;
/**
* @addtogroup database
......@@ -153,7 +155,7 @@ public function nextIdDelete() {
// errors. There is no problem with completely ignoring errors here: if
// these queries fail, the sequence will work just fine, just use a bit
// more database storage and memory.
catch (PDOException $e) {
catch (DatabaseException $e) {
}
}
......@@ -180,7 +182,7 @@ protected function popCommittableTransactions() {
try {
$this->query('RELEASE SAVEPOINT ' . $name);
}
catch (PDOException $e) {
catch (DatabaseExceptionWrapper $e) {
// However, in MySQL (InnoDB), savepoints are automatically committed
// when tables are altered or created (DDL transactions are not
// supported). This can cause exceptions due to trying to release
......@@ -188,7 +190,7 @@ protected function popCommittableTransactions() {
//
// To avoid exceptions when no actual error has occurred, we silently
// succeed for MySQL error code 1305 ("SAVEPOINT does not exist").
if ($e->errorInfo[1] == '1305') {
if ($e->getPrevious()->errorInfo[1] == '1305') {
// If one SAVEPOINT was released automatically, then all were.
// Therefore, clean the transaction stack.
$this->transactionLayers = array();
......
<?php
/**
* @file
* Definition of Drupal\Core\DrupalKernel.
*/
namespace Drupal\Core;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
use Symfony\Component\HttpKernel\EventListener\ExceptionListener;
use Drupal\Core\EventSubscriber\ViewSubscriber;
use Drupal\Core\EventSubscriber\AccessSubscriber;
use Drupal\Core\EventSubscriber\FinishResponseSubscriber;
use Drupal\Core\EventSubscriber\PathSubscriber;
use Drupal\Core\EventSubscriber\LegacyRequestSubscriber;
use Drupal\Core\EventSubscriber\LegacyControllerSubscriber;
use Drupal\Core\EventSubscriber\MaintenanceModeSubscriber;
use Drupal\Core\EventSubscriber\RequestCloseSubscriber;
use Drupal\Core\EventSubscriber\RouterListener;
/**
* The DrupalKernel class is the core of Drupal itself.
*/
class DrupalKernel extends HttpKernel {
/**
* Constructor.
*
* @param Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
* An EventDispatcherInterface instance.
* @param Symfony\Component\HttpKernel\Controller\ControllerResolverInterface $resolver
* A ControllerResolverInterface instance.
*/
public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver) {
parent::__construct($dispatcher, $resolver);
$this->matcher = new LegacyUrlMatcher();
$this->dispatcher->addSubscriber(new RouterListener($this->matcher));
$negotiation = new ContentNegotiation();
// @todo Make this extensible rather than just hard coding some.
// @todo Add a subscriber to handle other things, too, like our Ajax
// replacement system.
$this->dispatcher->addSubscriber(new ViewSubscriber($negotiation));
$this->dispatcher->addSubscriber(new AccessSubscriber());
$this->dispatcher->addSubscriber(new MaintenanceModeSubscriber());
$this->dispatcher->addSubscriber(new PathSubscriber());
$this->dispatcher->addSubscriber(new LegacyRequestSubscriber());
$this->dispatcher->addSubscriber(new LegacyControllerSubscriber());
$this->dispatcher->addSubscriber(new FinishResponseSubscriber());
$this->dispatcher->addSubscriber(new RequestCloseSubscriber());
// Some other form of error occured that wasn't handled by another kernel
// listener. That could mean that it's a method/mime-type/error
// combination that is not accounted for, or some other type of error.
// Either way, treat it as a server-level error and return an HTTP 500.
// By default, this will be an HTML-type response because that's a decent
// best guess if we don't know otherwise.
$this->dispatcher->addSubscriber(new ExceptionListener(array(new ExceptionController($this, $negotiation), 'execute')));
}
}
<?php
/**
* @file
* Definition of Drupal\Core\EventSubscriber\AccessSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Access subscriber for controller requests.
*/
class AccessSubscriber implements EventSubscriberInterface {
/**
* Verifies that the current user can access the requested path.
*
* @todo This is a total hack to keep our current access system working. It
* should be replaced with something robust and injected at some point.
*
* @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
*/
public function onKernelRequestAccessCheck(GetResponseEvent $event) {
$router_item = $event->getRequest()->attributes->get('drupal_menu_item');
if (isset($router_item['access']) && !$router_item['access']) {
throw new AccessDeniedHttpException();
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30);
return $events;
}
}
<?php
/**
* @file
* Definition of Drupal\Core\EventSubscriber\FinishResponseSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;