diff --git a/core/authorize.php b/core/authorize.php
index d703b33..fd0774d 100644
--- a/core/authorize.php
+++ b/core/authorize.php
@@ -84,10 +84,7 @@ module_list(TRUE, FALSE, FALSE, $module_list);
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.
diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index 83ddd30..0b07d8e 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -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));
}
/**
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index c2c3d91..e64bfca 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -4,6 +4,7 @@ use Drupal\Core\Database\Database;
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_SESSION = 4;
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;
@@ -1606,6 +1607,29 @@ function request_uri($omit_query_string = FALSE) {
}
/**
+ * 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.
*
* This is a wrapper function for watchdog() which automatically decodes an
@@ -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();
}
}
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 4a8b1f0..be6cab9 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1,5 +1,7 @@
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();
diff --git a/core/includes/errors.inc b/core/includes/errors.inc
index 0524170..97f5b6a 100644
--- a/core/includes/errors.inc
+++ b/core/includes/errors.inc
@@ -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;
}
}
}
diff --git a/core/includes/file.inc b/core/includes/file.inc
index f204989..f6cab86 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -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);
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index e62a7b5..eb9dff9 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -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.
diff --git a/core/lib/Drupal/Core/ContentNegotiation.php b/core/lib/Drupal/Core/ContentNegotiation.php
new file mode 100644
index 0000000..6302db8
--- /dev/null
+++ b/core/lib/Drupal/Core/ContentNegotiation.php
@@ -0,0 +1,54 @@
+.
+ 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';
+ }
+}
diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php
index 3805864..dbf7c36 100644
--- a/core/lib/Drupal/Core/Database/Connection.php
+++ b/core/lib/Drupal/Core/Database/Connection.php
@@ -524,15 +524,14 @@ abstract class Connection extends PDO {
}
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;
}
diff --git a/core/lib/Drupal/Core/Database/DatabaseExceptionWrapper.php b/core/lib/Drupal/Core/Database/DatabaseExceptionWrapper.php
new file mode 100644
index 0000000..b212478
--- /dev/null
+++ b/core/lib/Drupal/Core/Database/DatabaseExceptionWrapper.php
@@ -0,0 +1,19 @@
+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 @@ class Connection extends DatabaseConnection {
//
// 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();
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
new file mode 100644
index 0000000..b7dfd3a
--- /dev/null
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -0,0 +1,73 @@
+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')));
+ }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
new file mode 100644
index 0000000..4d10489
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
@@ -0,0 +1,53 @@
+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;
+ }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
new file mode 100644
index 0000000..6e23845
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -0,0 +1,83 @@
+getResponse();
+
+ // Set the X-UA-Compatible HTTP header to force IE to use the most recent
+ // rendering engine or use Chrome's frame rendering engine if available.
+ $response->headers->set('X-UA-Compatible', 'IE=edge,chrome=1', false);
+
+ // Set the Content-language header.
+ $response->headers->set('Content-language', drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode);
+
+ // Because pages are highly dynamic, set the last-modified time to now
+ // since the page is in fact being regenerated right now.
+ // @todo Remove this and use a more intelligent default so that HTTP
+ // caching can function properly.
+ $response->headers->set('Last-Modified', gmdate(DATE_RFC1123, REQUEST_TIME));
+
+ // Also give each page a unique ETag. This will force clients to include
+ // both an If-Modified-Since header and an If-None-Match header when doing
+ // conditional requests for the page (required by RFC 2616, section 13.3.4),
+ // making the validation more robust. This is a workaround for a bug in
+ // Mozilla Firefox that is triggered when Drupal's caching is enabled and
+ // the user accesses Drupal via an HTTP proxy (see
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=269303): When an
+ // authenticated user requests a page, and then logs out and requests the
+ // same page again, Firefox may send a conditional request based on the
+ // page that was cached locally when the user was logged in. If this page
+ // did not have an ETag header, the request only contains an
+ // If-Modified-Since header. The date will be recent, because with
+ // authenticated users the Last-Modified header always refers to the time
+ // of the request. If the user accesses Drupal via a proxy server, and the
+ // proxy already has a cached copy of the anonymous page with an older
+ // Last-Modified date, the proxy may respond with 304 Not Modified, making
+ // the client think that the anonymous and authenticated pageviews are
+ // identical.
+ // @todo Remove this line as no longer necessary per
+ // http://drupal.org/node/1573064
+ $response->headers->set('ETag', '"' . REQUEST_TIME . '"');
+
+ // Authenticated users are always given a 'no-cache' header, and will fetch
+ // a fresh page on every request. This prevents authenticated users from
+ // seeing locally cached pages.
+ // @todo Revisit whether or not this is still appropriate now that the
+ // Response object does its own cache control procesisng and we intend to
+ // use partial page caching more extensively.
+ $response->headers->set('Expires', 'Sun, 19 Nov 1978 05:00:00 GMT');
+ $response->headers->set('Cache-Control', 'no-cache, must-revalidate, post-check=0, pre-check=0');
+ }
+
+ /**
+ * Registers the methods in this class that should be listeners.
+ *
+ * @return array
+ * An array of event listener definitions.
+ */
+ static function getSubscribedEvents() {
+ $events[KernelEvents::RESPONSE][] = array('onRespond');
+ return $events;
+ }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
new file mode 100644
index 0000000..48cc8a0
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
@@ -0,0 +1,63 @@
+getRequest()->attributes->get('drupal_menu_item');
+ $controller = $event->getController();
+
+ // This BC logic applies only to functions. Otherwise, skip it.
+ if (is_string($controller) && function_exists($controller)) {
+ $new_controller = function() use ($router_item) {
+ return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
+ };
+ $event->setController($new_controller);
+ }
+ }
+
+ /**
+ * Registers the methods in this class that should be listeners.
+ *
+ * @return array
+ * An array of event listener definitions.
+ */
+ static function getSubscribedEvents() {
+ $events[KernelEvents::CONTROLLER][] = array('onKernelControllerLegacy', 30);
+
+ return $events;
+ }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyRequestSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyRequestSubscriber.php
new file mode 100644
index 0000000..84365a5
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/LegacyRequestSubscriber.php
@@ -0,0 +1,62 @@
+getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
+ // Prior to invoking hook_init(), initialize the theme (potentially a
+ // custom one for this page), so that:
+ // - Modules with hook_init() implementations that call theme() or
+ // theme_get_registry() don't initialize the incorrect theme.
+ // - The theme can have hook_*_alter() implementations affect page
+ // building (e.g., hook_form_alter(), hook_node_view_alter(),
+ // hook_page_alter()), ahead of when rendering starts.
+ menu_set_custom_theme();
+ drupal_theme_initialize();
+ module_invoke_all('init');
+
+ // Tell Drupal it is now fully bootstrapped (for the benefit of code that
+ // calls drupal_get_bootstrap_phase()), but without having
+ // _drupal_bootstrap_full() do anything, since we've already done the
+ // equivalent above and in earlier listeners.
+ _drupal_bootstrap_full(TRUE);
+ drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+ }
+ }
+
+ /**
+ * 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('onKernelRequestLegacy', 90);
+
+ return $events;
+ }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
new file mode 100644
index 0000000..daed1c9
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
@@ -0,0 +1,59 @@
+getRequest()->attributes->get('system_path');
+ drupal_alter('menu_site_status', $status, $read_only_path);
+
+ // Only continue if the site is online.
+ if ($status != MENU_SITE_ONLINE) {
+ // Deliver the 503 page.
+ drupal_maintenance_theme();
+ drupal_set_title(t('Site under maintenance'));
+ $content = theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))));
+ $response = new Response('Service unavailable', 503);
+ $response->setContent($content);
+ $event->setResponse($response);
+ }
+ }
+
+ /**
+ * 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('onKernelRequestMaintenanceModeCheck', 40);
+ return $events;
+ }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/PathListenerBase.php b/core/lib/Drupal/Core/EventSubscriber/PathListenerBase.php
new file mode 100644
index 0000000..fd0a765
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/PathListenerBase.php
@@ -0,0 +1,29 @@
+attributes->get('system_path');
+ return isset($path) ? $path : trim($request->getPathInfo(), '/');
+ }
+
+ public function setPath(Request $request, $path) {
+ $request->attributes->set('system_path', $path);
+
+ // @todo Remove this line once code has been refactored to use the request
+ // object directly.
+ _current_path($path);
+ }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php
new file mode 100644
index 0000000..1e2a95a
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php
@@ -0,0 +1,127 @@
+getRequest();
+
+ $path = $this->extractPath($request);
+
+ $path = drupal_get_normal_path($path);
+
+ $this->setPath($request, $path);
+ }
+
+ /**
+ * Resolve the front-page default path.
+ *
+ * @todo The path system should be objectified to remove the function calls in
+ * this method.
+ *
+ * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+ * The Event to process.
+ */
+ public function onKernelRequestFrontPageResolve(GetResponseEvent $event) {
+ $request = $event->getRequest();
+ $path = $this->extractPath($request);
+
+ if (empty($path)) {
+ // @todo Temporary hack. Fix when configuration is injectable.
+ $path = variable_get('site_frontpage', 'user');
+ }
+
+ $this->setPath($request, $path);
+ }
+
+ /**
+ * Decode language information embedded in the request path.
+ *
+ * @todo Refactor this entire method to inline the relevant portions of
+ * drupal_language_initialize(). See the inline comment for more details.
+ *
+ * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+ * The Event to process.
+ */
+ public function onKernelRequestLanguageResolve(GetResponseEvent $event) {
+ $request = $event->getRequest();
+ $path = $this->extractPath($request);
+
+ // drupal_language_initialize() combines:
+ // - Determination of language from $request information (e.g., path).
+ // - Determination of language from other information (e.g., site default).
+ // - Population of determined language into drupal_container().
+ // - Removal of language code from _current_path().
+ // @todo Decouple the above, but for now, invoke it and update the path
+ // prior to front page and alias resolution. When above is decoupled, also
+ // add 'langcode' (determined from $request only) to $request->attributes.
+ _current_path($path);
+ drupal_language_initialize();
+ $path = _current_path();
+
+ $this->setPath($request, $path);
+ }
+
+ /**
+ * Decodes the path of the request.
+ *
+ * Parameters in the URL sometimes represent code-meaningful strings. It is
+ * therefore useful to always urldecode() those values so that individual
+ * controllers need not concern themselves with it. This is Drupal-specific
+ * logic and may not be familiar for developers used to other Symfony-family
+ * projects.
+ *
+ * @todo Revisit whether or not this logic is appropriate for here or if
+ * controllers should be required to implement this logic themselves. If we
+ * decide to keep this code, remove this TODO.
+ *
+ * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+ * The Event to process.
+ */
+ public function onKernelRequestDecodePath(GetResponseEvent $event) {
+ $request = $event->getRequest();
+ $path = $this->extractPath($request);
+
+ $path = urldecode($path);
+
+ $this->setPath($request, $path);
+ }
+
+ /**
+ * 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('onKernelRequestDecodePath', 200);
+ $events[KernelEvents::REQUEST][] = array('onKernelRequestLanguageResolve', 150);
+ $events[KernelEvents::REQUEST][] = array('onKernelRequestFrontPageResolve', 101);
+ $events[KernelEvents::REQUEST][] = array('onKernelRequestPathResolve', 100);
+
+ return $events;
+ }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php
new file mode 100644
index 0000000..9b3ef45
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php
@@ -0,0 +1,63 @@
+getResponse();
+ $config = config('system.performance');
+
+ if ($config->get('cache') && ($cache = drupal_page_set_cache())) {
+ drupal_serve_page_from_cache($cache);
+ }
+ else {
+ ob_flush();
+ }
+
+ _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
+ drupal_cache_system_paths();
+ module_implements_write_cache();
+ system_run_automated_cron();
+ }
+
+ /**
+ * Registers the methods in this class that should be listeners.
+ *
+ * @return array
+ * An array of event listener definitions.
+ */
+ static function getSubscribedEvents() {
+ $events[KernelEvents::TERMINATE][] = array('onTerminate');
+
+ return $events;
+ }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/RouterListener.php b/core/lib/Drupal/Core/EventSubscriber/RouterListener.php
new file mode 100644
index 0000000..c7e13a2
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/RouterListener.php
@@ -0,0 +1,97 @@
+urlMatcher = $urlMatcher;
+ $this->logger = $logger;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * This method is nearly identical to the parent, except it passes the
+ * $request->attributes->get('system_path') variable to the matcher.
+ * That is where Drupal stores its processed, de-aliased, and sanitized
+ * internal path. We also pass the full request object to the URL Matcher,
+ * since we want attributes to be available to the matcher and to controllers.
+ */
+ public function onKernelRequest(GetResponseEvent $event) {
+ $request = $event->getRequest();
+
+ if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
+ $this->urlMatcher->getContext()->fromRequest($request);
+ $this->urlMatcher->setRequest($request);
+ }
+
+ if ($request->attributes->has('_controller')) {
+ // Routing is already done.
+ return;
+ }
+
+ // Add attributes based on the path info (routing).
+ try {
+ $parameters = $this->urlMatcher->match($request->attributes->get('system_path'));
+
+ if (null !== $this->logger) {
+ $this->logger->info(sprintf('Matched route "%s" (parameters: %s)', $parameters['_route'], $this->parametersToString($parameters)));
+ }
+
+ $request->attributes->add($parameters);
+ unset($parameters['_route']);
+ unset($parameters['_controller']);
+ $request->attributes->set('_route_params', $parameters);
+ }
+ catch (ResourceNotFoundException $e) {
+ $message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo());
+
+ throw new NotFoundHttpException($message, $e);
+ }
+ catch (MethodNotAllowedException $e) {
+ $message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getPathInfo(), strtoupper(implode(', ', $e->getAllowedMethods())));
+
+ throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e);
+ }
+ }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
new file mode 100644
index 0000000..4c6bde2
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
@@ -0,0 +1,132 @@
+negotiation = $negotiation;
+ }
+
+ /**
+ * Processes a successful controller into an HTTP 200 response.
+ *
+ * Some controllers may not return a response object but simply the body of
+ * one. The VIEW event is called in that case, to allow us to mutate that
+ * body into a Response object. In particular we assume that the return
+ * from an JSON-type response is a JSON string, so just wrap it into a
+ * Response object.
+ *
+ * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+ * The Event to process.
+ */
+ public function onView(GetResponseEvent $event) {
+
+ $request = $event->getRequest();
+
+ $method = 'on' . $this->negotiation->getContentType($request);
+
+ if (method_exists($this, $method)) {
+ $event->setResponse($this->$method($event));
+ }
+ else {
+ $event->setResponse(new Response('Unsupported Media Type', 415));
+ }
+ }
+
+ public function onJson(GetResponseEvent $event) {
+ $page_callback_result = $event->getControllerResult();
+
+ $response = new JsonResponse();
+ $response->setContent($page_callback_result);
+
+ return $response;
+ }
+
+ public function onAjax(GetResponseEvent $event) {
+ $page_callback_result = $event->getControllerResult();
+
+ // Construct the response content from the page callback result.
+ $commands = ajax_prepare_response($page_callback_result);
+ $json = ajax_render($commands);
+
+ // Build the actual response object.
+ $response = new JsonResponse();
+ $response->setContent($json);
+
+ return $response;
+ }
+
+ public function onIframeUpload(GetResponseEvent $event) {
+ $page_callback_result = $event->getControllerResult();
+
+ // Construct the response content from the page callback result.
+ $commands = ajax_prepare_response($page_callback_result);
+ $json = ajax_render($commands);
+
+ // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification
+ // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into
+ // links. This corrupts the JSON response. Protect the integrity of the
+ // JSON data by making it the value of a textarea.
+ // @see http://malsup.com/jquery/form/#file-upload
+ // @see http://drupal.org/node/1009382
+ $html = '';
+
+ return new Response($html);
+ }
+
+ /**
+ * Processes a successful controller into an HTTP 200 response.
+ *
+ * Some controllers may not return a response object but simply the body of
+ * one. The VIEW event is called in that case, to allow us to mutate that
+ * body into a Response object. In particular we assume that the return from
+ * an HTML-type response is a render array from a legacy page callback and
+ * render it.
+ *
+ * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+ * The Event to process.
+ */
+ public function onHtml(GetResponseEvent $event) {
+ $page_callback_result = $event->getControllerResult();
+ return new Response(drupal_render_page($page_callback_result));
+ }
+
+ /**
+ * Registers the methods in this class that should be listeners.
+ *
+ * @return array
+ * An array of event listener definitions.
+ */
+ static function getSubscribedEvents() {
+ $events[KernelEvents::VIEW][] = array('onView');
+
+ return $events;
+ }
+}
diff --git a/core/lib/Drupal/Core/ExceptionController.php b/core/lib/Drupal/Core/ExceptionController.php
new file mode 100644
index 0000000..a2ab913
--- /dev/null
+++ b/core/lib/Drupal/Core/ExceptionController.php
@@ -0,0 +1,411 @@
+kernel = $kernel;
+ $this->negotiation = $negotiation;
+ }
+
+ /**
+ * Handles an exception on a request.
+ *
+ * @param Symfony\Component\HttpKernel\Exception\FlattenException $exception
+ * The flattened exception.
+ * @param Symfony\Component\HttpFoundation\Request $request
+ * The request that generated the exception.
+ *
+ * @return Symfony\Component\HttpFoundation\Response
+ * A response object to be sent to the server.
+ */
+ public function execute(FlattenException $exception, Request $request) {
+ $method = 'on' . $exception->getStatusCode() . $this->negotiation->getContentType($request);
+
+ if (method_exists($this, $method)) {
+ return $this->$method($exception, $request);
+ }
+
+ return new Response('A fatal error occurred: ' . $exception->getMessage(), $exception->getStatusCode());
+ }
+
+ /**
+ * Processes a MethodNotAllowed exception into an HTTP 405 response.
+ *
+ * @param Symfony\Component\HttpKernel\Exception\FlattenException $exception
+ * The flattened exception.
+ * @param Symfony\Component\HttpFoundation\Request $request
+ * The request object that triggered this exception.
+ */
+ public function on405Html(FlattenException $exception, Request $request) {
+ $event->setResponse(new Response('Method Not Allowed', 405));
+ }
+
+ /**
+ * Processes an AccessDenied exception into an HTTP 403 response.
+ *
+ * @param Symfony\Component\HttpKernel\Exception\FlattenException $exception
+ * The flattened exception.
+ * @param Symfony\Component\HttpFoundation\Request $request
+ * The request object that triggered this exception.
+ */
+ public function on403Html(FlattenException $exception, Request $request) {
+ $system_path = $request->attributes->get('system_path');
+ watchdog('access denied', $system_path, NULL, WATCHDOG_WARNING);
+
+ $path = drupal_get_normal_path(variable_get('site_403', ''));
+ if ($path && $path != $system_path) {
+ // Keep old path for reference, and to allow forms to redirect to it.
+ if (!isset($_GET['destination'])) {
+ $_GET['destination'] = $system_path;
+ }
+
+ $subrequest = Request::create('/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all());
+
+ // The active trail is being statically cached from the parent request to
+ // the subrequest, like any other static. Unfortunately that means the
+ // data in it is incorrect and does not get regenerated correctly for
+ // the subrequest. In this instance, that even causes a fatal error in
+ // some circumstances because menu_get_active_trail() ends up having
+ // a missing localized_options value. To work around that, reset the
+ // menu static variables and let them be regenerated as needed.
+ // @todo It is likely that there are other such statics that need to be
+ // reset that are not triggering test failures right now. If found,
+ // add them here.
+ // @todo Refactor the breadcrumb system so that it does not rely on static
+ // variables in the first place, which will eliminate the need for this
+ // hack.
+ drupal_static_reset('menu_set_active_trail');
+ menu_reset_static_cache();
+
+ $response = $this->kernel->handle($subrequest, DrupalKernel::SUB_REQUEST);
+ $response->setStatusCode(403, 'Access denied');
+ }
+ else {
+ $response = new Response('Access Denied', 403);
+
+ // @todo Replace this block with something cleaner.
+ $return = t('You are not authorized to access this page.');
+ drupal_set_title(t('Access denied'));
+ drupal_set_page_content($return);
+ $page = element_info('page');
+ $content = drupal_render_page($page);
+
+ $response->setContent($content);
+ }
+
+ return $response;
+ }
+
+ /**
+ * Processes a NotFound exception into an HTTP 404 response.
+ *
+ * @param Symfony\Component\HttpKernel\Exception\FlattenException $exception
+ * The flattened exception.
+ * @param Sonfony\Component\HttpFoundation\Request $request
+ * The request object that triggered this exception.
+ */
+ public function on404Html(FlattenException $exception, Request $request) {
+ watchdog('page not found', check_plain($request->attributes->get('system_path')), NULL, WATCHDOG_WARNING);
+
+ // Check for and return a fast 404 page if configured.
+ // @todo Inline this rather than using a function.
+ drupal_fast_404();
+
+ $system_path = $request->attributes->get('system_path');
+
+ // Keep old path for reference, and to allow forms to redirect to it.
+ if (!isset($_GET['destination'])) {
+ $_GET['destination'] = $system_path;
+ }
+
+ $path = drupal_get_normal_path(variable_get('site_404', ''));
+ if ($path && $path != $system_path) {
+ // @todo Um, how do I specify an override URL again? Totally not clear. Do
+ // that and sub-call the kernel rather than using meah().
+ // @todo The create() method expects a slash-prefixed path, but we store a
+ // normal system path in the site_404 variable.
+ $subrequest = Request::create('/' . $path, 'get', array(), $request->cookies->all(), array(), $request->server->all());
+
+ // The active trail is being statically cached from the parent request to
+ // the subrequest, like any other static. Unfortunately that means the
+ // data in it is incorrect and does not get regenerated correctly for
+ // the subrequest. In this instance, that even causes a fatal error in
+ // some circumstances because menu_get_active_trail() ends up having
+ // a missing localized_options value. To work around that, reset the
+ // menu static variables and let them be regenerated as needed.
+ // @todo It is likely that there are other such statics that need to be
+ // reset that are not triggering test failures right now. If found,
+ // add them here.
+ // @todo Refactor the breadcrumb system so that it does not rely on static
+ // variables in the first place, which will eliminate the need for this
+ // hack.
+ drupal_static_reset('menu_set_active_trail');
+ menu_reset_static_cache();
+
+ $response = $this->kernel->handle($subrequest, HttpKernelInterface::SUB_REQUEST);
+ $response->setStatusCode(404, 'Not Found');
+ }
+ else {
+ $response = new Response('Not Found', 404);
+
+ // @todo Replace this block with something cleaner.
+ $return = t('The requested page "@path" could not be found.', array('@path' => $request->getPathInfo()));
+ drupal_set_title(t('Page not found'));
+ drupal_set_page_content($return);
+ $page = element_info('page');
+ $content = drupal_render_page($page);
+
+ $response->setContent($content);
+ }
+
+ return $response;
+ }
+
+ /**
+ * Processes a generic exception into an HTTP 500 response.
+ *
+ * @param Symfony\Component\HttpKernel\Exception\FlattenException $exception
+ * Metadata about the exception that was thrown.
+ * @param Symfony\Component\HttpFoundation\Request $request
+ * The request object that triggered this exception.
+ */
+ public function on500Html(FlattenException $exception, Request $request) {
+ $error = $this->decodeException($exception);
+
+ // Because the kernel doesn't run until full bootstrap, we know that
+ // most subsystems are already initialized.
+
+ $headers = array();
+
+ // When running inside the testing framework, we relay the errors
+ // to the tested site by the way of HTTP headers.
+ $test_info = &$GLOBALS['drupal_test_info'];
+ if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
+ // $number does not use drupal_static as it should not be reset
+ // as it uniquely identifies each PHP error.
+ static $number = 0;
+ $assertion = array(
+ $error['!message'],
+ $error['%type'],
+ array(
+ 'function' => $error['%function'],
+ 'file' => $error['%file'],
+ 'line' => $error['%line'],
+ ),
+ );
+ $headers['X-Drupal-Assertion-' . $number] = rawurlencode(serialize($assertion));
+ $number++;
+ }
+
+ watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
+
+ // Display the message if the current error reporting level allows this type
+ // of message to be displayed, and unconditionnaly in update.php.
+ if (error_displayable($error)) {
+ $class = 'error';
+
+ // If error type is 'User notice' then treat it as debug information
+ // instead of an error message, see dd().
+ if ($error['%type'] == 'User notice') {
+ $error['%type'] = 'Debug';
+ $class = 'status';
+ }
+
+ drupal_set_message(t('%type: !message in %function (line %line of %file).', $error), $class);
+ }
+
+ drupal_set_title(t('Error'));
+ // We fallback to a maintenance page at this point, because the page
+ // generation itself can generate errors.
+ $output = theme('maintenance_page', array('content' => t('The website encountered an unexpected error. Please try again later.')));
+
+ $response = new Response($output, 500);
+ $response->setStatusCode(500, '500 Service unavailable (with message)');
+
+ return $response;
+ }
+
+ /**
+ * Processes an AccessDenied exception that occured on a JSON request.
+ *
+ * @param Symfony\Component\HttpKernel\Exception\FlattenException $exception
+ * The flattened exception.
+ * @param Symfony\Component\HttpFoundation\Request $request
+ * The request object that triggered this exception.
+ */
+ public function on403Json(FlattenException $exception, Request $request) {
+ $response = new JsonResponse();
+ $response->setStatusCode(403, 'Access Denied');
+ return $response;
+ }
+
+ /**
+ * Processes a NotFound exception that occured on a JSON request.
+ *
+ * @param Symfony\Component\HttpKernel\Exception\FlattenException $exception
+ * The flattened exception.
+ * @param Symfony\Component\HttpFoundation\Request $request
+ * The request object that triggered this exception.
+ */
+ public function on404Json(FlattenException $exception, Request $request) {
+ $response = new JsonResponse();
+ $response->setStatusCode(404, 'Not Found');
+ return $response;
+ }
+
+ /**
+ * Processes a MethodNotAllowed exception that occured on a JSON request.
+ *
+ * @param Symfony\Component\HttpKernel\Exception\FlattenException $exception
+ * The flattened exception.
+ * @param Symfony\Component\HttpFoundation\Request $request
+ * The request object that triggered this exception.
+ */
+ public function on405Json(FlattenException $exception, Request $request) {
+ $response = new JsonResponse();
+ $response->setStatusCode(405, 'Method Not Allowed');
+ return $response;
+ }
+
+
+ /**
+ * This method is a temporary port of _drupal_decode_exception().
+ *
+ * @todo This should get refactored. FlattenException could use some
+ * improvement as well.
+ *
+ * @return array
+ */
+ protected function decodeException(FlattenException $exception) {
+ $message = $exception->getMessage();
+
+ $backtrace = $exception->getTrace();
+
+ // This value is missing from the stack for some reason in the
+ // FlattenException version of the backtrace.
+ $backtrace[0]['line'] = $exception->getLine();
+
+ // For database errors, we try to return the initial caller,
+ // skipping internal functions of the database layer.
+ if (strpos($exception->getClass(), 'DatabaseExceptionWrapper') !== FALSE) {
+ // A DatabaseExceptionWrapper exception is actually just a courier for
+ // the original PDOException. It's the stack trace from that exception
+ // that we care about.
+ $backtrace = $exception->getPrevious()->getTrace();
+ $backtrace[0]['line'] = $exception->getLine();
+
+ // The first element in the stack is the call, the second element gives us the caller.
+ // We skip calls that occurred in one of the classes of the database layer
+ // or in one of its global functions.
+ $db_functions = array('db_query', 'db_query_range');
+ while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
+ ((strpos($caller['namespace'], 'Drupal\Core\Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
+ in_array($caller['function'], $db_functions)) {
+ // We remove that call.
+ array_shift($backtrace);
+ }
+ }
+ $caller = $this->getLastCaller($backtrace);
+
+ return array(
+ '%type' => $exception->getClass(),
+ // The standard PHP exception handler considers that the exception message
+ // is plain-text. We mimick this behavior here.
+ '!message' => check_plain($message),
+ '%function' => $caller['function'],
+ '%file' => $caller['file'],
+ '%line' => $caller['line'],
+ 'severity_level' => WATCHDOG_ERROR,
+ );
+ }
+
+ /**
+ * Gets the last caller from a backtrace.
+ *
+ * The last caller is not necessarily the first item in the backtrace. Rather,
+ * it is the first item in the backtrace that is a PHP userspace function,
+ * and not one of our debug functions.
+ *
+ * @param $backtrace
+ * A standard PHP backtrace.
+ *
+ * @return
+ * An associative array with keys 'file', 'line' and 'function'.
+ */
+ protected function getLastCaller($backtrace) {
+ // Ignore black listed error handling functions.
+ $blacklist = array('debug', '_drupal_error_handler', '_drupal_exception_handler');
+
+ // Errors that occur inside PHP internal functions do not generate
+ // information about file and line.
+ while (($backtrace && !isset($backtrace[0]['line'])) ||
+ (isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], $blacklist))) {
+ array_shift($backtrace);
+ }
+
+ // The first trace is the call itself.
+ // It gives us the line and the file of the last call.
+ $call = $backtrace[0];
+
+ // The second call give us the function where the call originated.
+ if (isset($backtrace[1])) {
+ if (isset($backtrace[1]['class'])) {
+ $call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
+ }
+ else {
+ $call['function'] = $backtrace[1]['function'] . '()';
+ }
+ }
+ else {
+ $call['function'] = 'main()';
+ }
+ return $call;
+ }
+}
diff --git a/core/lib/Drupal/Core/LegacyUrlMatcher.php b/core/lib/Drupal/Core/LegacyUrlMatcher.php
new file mode 100644
index 0000000..1ea0c5d
--- /dev/null
+++ b/core/lib/Drupal/Core/LegacyUrlMatcher.php
@@ -0,0 +1,168 @@
+context = new RequestContext();
+ }
+
+ /**
+ * Sets the request context.
+ *
+ * This method is just to satisfy the interface, and is largely vestigial.
+ * The request context object does not contain the information we need, so
+ * we will use the original request object.
+ *
+ * @param Symfony\Component\Routing\RequestContext $context
+ * The context.
+ *
+ * @api
+ */
+ public function setContext(RequestContext $context) {
+ $this->context = $context;
+ }
+
+ /**
+ * Gets the request context.
+ *
+ * This method is just to satisfy the interface, and is largely vestigial.
+ * The request context object does not contain the information we need, so
+ * we will use the original request object.
+ *
+ * @return Symfony\Component\Routing\RequestContext
+ * The context.
+ */
+ public function getContext() {
+ return $this->context;
+ }
+
+ /**
+ * Sets the request object to use.
+ *
+ * This is used by the RouterListener to make additional request attributes
+ * available.
+ *
+ * @param Symfony\Component\HttpFoundation\Request $request
+ * The request object.
+ */
+ public function setRequest(Request $request) {
+ $this->request = $request;
+ }
+
+ /**
+ * Gets the request object.
+ *
+ * @return Symfony\Component\HttpFoundation\Request $request
+ * The request object.
+ */
+ public function getRequest() {
+ return $this->request;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @api
+ */
+ public function match($pathinfo) {
+ if ($router_item = $this->matchDrupalItem($pathinfo)) {
+ $ret = $this->convertDrupalItem($router_item);
+ // Stash the router item in the attributes while we're transitioning.
+ $ret['drupal_menu_item'] = $router_item;
+
+ // Most legacy controllers (aka page callbacks) are in a separate file,
+ // so we have to include that.
+ if ($router_item['include_file']) {
+ require_once DRUPAL_ROOT . '/' . $router_item['include_file'];
+ }
+
+ return $ret;
+ }
+
+ // This matcher doesn't differentiate by method, so don't bother with those
+ // exceptions.
+ throw new ResourceNotFoundException();
+ }
+
+ /**
+ * Get a Drupal menu item.
+ *
+ * @todo Make this return multiple possible candidates for the resolver to
+ * consider.
+ *
+ * @param string $path
+ * The path being looked up by
+ */
+ protected function matchDrupalItem($path) {
+ // For now we can just proxy our procedural method. At some point this will
+ // become more complicated because we'll need to get back candidates for a
+ // path and them resolve them based on things like method and scheme which
+ // we currently can't do.
+ return menu_get_item($path);
+ }
+
+ /**
+ * Converts a Drupal menu item to a route array.
+ *
+ * @param array $router_item
+ * The Drupal menu item.
+ *
+ * @return
+ * An array of parameters.
+ */
+ protected function convertDrupalItem($router_item) {
+ $route = array(
+ '_controller' => $router_item['page_callback']
+ );
+
+ // @todo menu_get_item() does not unserialize page arguments when the access
+ // is denied. Remove this temporary hack that always does that.
+ if (!is_array($router_item['page_arguments'])) {
+ $router_item['page_arguments'] = unserialize($router_item['page_arguments']);
+ }
+
+ // Place argument defaults on the route.
+ foreach ($router_item['page_arguments'] as $k => $v) {
+ $route[$k] = $v;
+ }
+ return $route;
+ }
+}
diff --git a/core/lib/Drupal/Core/Lock/DatabaseLockBackend.php b/core/lib/Drupal/Core/Lock/DatabaseLockBackend.php
index 6971d9f..d877dba 100644
--- a/core/lib/Drupal/Core/Lock/DatabaseLockBackend.php
+++ b/core/lib/Drupal/Core/Lock/DatabaseLockBackend.php
@@ -7,7 +7,7 @@
namespace Drupal\Core\Lock;
-use PDOException;
+use Drupal\Core\Database\DatabaseExceptionWrapper;
/**
* Defines the database lock backend. This is the default backend in Drupal.
@@ -53,7 +53,7 @@ class DatabaseLockBackend extends LockBackendAbstract {
// We never need to try again.
$retry = FALSE;
}
- catch (PDOException $e) {
+ catch (DatabaseExceptionWrapper $e) {
// Suppress the error. If this is our first pass through the loop,
// then $retry is FALSE. In this case, the insert must have failed
// meaning some other request acquired the lock but did not release it.
diff --git a/core/modules/aggregator/aggregator.admin.inc b/core/modules/aggregator/aggregator.admin.inc
index 9e5546f..c064e4f 100644
--- a/core/modules/aggregator/aggregator.admin.inc
+++ b/core/modules/aggregator/aggregator.admin.inc
@@ -406,9 +406,14 @@ function _aggregator_parse_opml($opml) {
* @see aggregator_menu()
*/
function aggregator_admin_refresh_feed($feed) {
- if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'aggregator/update/' . $feed->fid)) {
- return MENU_ACCESS_DENIED;
+ // @todo CSRF tokens are validated in page callbacks rather than access
+ // callbacks, because access callbacks are also invoked during menu link
+ // generation. Add token support to routing: http://drupal.org/node/755584.
+ $token = request()->query->get('token');
+ if (!isset($token) || !drupal_valid_token($token, 'aggregator/update/' . $feed->fid)) {
+ drupal_access_denied();
}
+
aggregator_refresh($feed);
drupal_goto('admin/config/services/aggregator');
}
diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc
index ccc307e..9eabe13 100644
--- a/core/modules/comment/comment.admin.inc
+++ b/core/modules/comment/comment.admin.inc
@@ -260,7 +260,7 @@ function comment_confirm_delete_page($cid) {
if ($comment = comment_load($cid)) {
return drupal_get_form('comment_confirm_delete', $comment);
}
- return MENU_NOT_FOUND;
+ drupal_not_found();
}
/**
diff --git a/core/modules/comment/comment.pages.inc b/core/modules/comment/comment.pages.inc
index 0832eff..2986d13 100644
--- a/core/modules/comment/comment.pages.inc
+++ b/core/modules/comment/comment.pages.inc
@@ -107,9 +107,14 @@ function comment_reply(Node $node, $pid = NULL) {
* @see comment_menu()
*/
function comment_approve($cid) {
- if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], "comment/$cid/approve")) {
- return MENU_ACCESS_DENIED;
+ // @todo CSRF tokens are validated in page callbacks rather than access
+ // callbacks, because access callbacks are also invoked during menu link
+ // generation. Add token support to routing: http://drupal.org/node/755584.
+ $token = request()->query->get('token');
+ if (!isset($token) || !drupal_valid_token($token, "comment/$cid/approve")) {
+ drupal_access_denied();
}
+
if ($comment = comment_load($cid)) {
$comment->status = COMMENT_PUBLISHED;
comment_save($comment);
@@ -117,5 +122,5 @@ function comment_approve($cid) {
drupal_set_message(t('Comment approved.'));
drupal_goto('node/' . $comment->nid);
}
- return MENU_NOT_FOUND;
+ drupal_not_found();
}
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 3edf83c..9d94ac2 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -5,6 +5,9 @@
* Exposes global functionality for creating image styles.
*/
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+
/**
* Image style constant for user presets in the database.
*/
@@ -726,13 +729,16 @@ function image_style_deliver($style, $scheme) {
if ($success) {
$image = image_load($derivative_uri);
- file_transfer($image->source, array('Content-Type' => $image->info['mime_type'], 'Content-Length' => $image->info['file_size']));
+ $uri = $image->source;
+ $headers = array(
+ 'Content-Type' => $image->info['mime_type'],
+ 'Content-Length' => $image->info['file_size'],
+ );
+ return file_transfer($uri, $headers);
}
else {
watchdog('image', 'Unable to generate the derived image located at %path.', array('%path' => $derivative_uri));
- drupal_add_http_header('Status', '500 Internal Server Error');
- print t('Error generating image.');
- drupal_exit();
+ return new Response(t('Error generating image.'), 500);
}
}
diff --git a/core/modules/image/image.test b/core/modules/image/image.test
index f9a4ec1..0752c44 100644
--- a/core/modules/image/image.test
+++ b/core/modules/image/image.test
@@ -658,7 +658,7 @@ class ImageFieldDisplayTestCase extends ImageFieldTestCase {
// sent by Drupal.
$this->assertEqual($this->drupalGetHeader('Content-Type'), 'image/png; name="' . $test_image->filename . '"', t('Content-Type header was sent.'));
$this->assertEqual($this->drupalGetHeader('Content-Disposition'), 'inline; filename="' . $test_image->filename . '"', t('Content-Disposition header was sent.'));
- $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'private', t('Cache-Control header was sent.'));
+ $this->assertTrue(strstr($this->drupalGetHeader('Cache-Control'),'private') !== FALSE, t('Cache-Control header was sent.'));
// Log out and try to access the file.
$this->drupalLogout();
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeFeedTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeFeedTest.php
index 29878fa..ca660e4 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeFeedTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeFeedTest.php
@@ -25,10 +25,7 @@ class NodeFeedTest extends WebTestBase {
* Ensure that node_feed accepts and prints extra channel elements.
*/
function testNodeFeedExtraChannelElements() {
- ob_start();
- node_feed(array(), array('copyright' => 'Drupal is a registered trademark of Dries Buytaert.'));
- $output = ob_get_clean();
-
- $this->assertTrue(strpos($output, 'Drupal is a registered trademark of Dries Buytaert.') !== FALSE);
+ $response = node_feed(array(), array('copyright' => 'Drupal is a registered trademark of Dries Buytaert.'));
+ $this->assertTrue(strpos($response->getContent(), 'Drupal is a registered trademark of Dries Buytaert.') !== FALSE);
}
}
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 3c3881d..4859aba 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1,10 +1,5 @@
\n";
- drupal_add_http_header('Content-Type', 'application/rss+xml; charset=utf-8');
- print $output;
+ return new Response($output, 200, array('Content-Type' => 'application/rss+xml; charset=utf-8'));
}
/**
diff --git a/core/modules/openid/tests/openid_test.module b/core/modules/openid/tests/openid_test.module
index ac49dbd..4481818 100644
--- a/core/modules/openid/tests/openid_test.module
+++ b/core/modules/openid/tests/openid_test.module
@@ -20,6 +20,9 @@
* key is used for verifying the signed messages from the provider.
*/
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Response;
+
/**
* Implements hook_menu().
*/
@@ -97,8 +100,7 @@ function openid_test_yadis_xrds() {
if (arg(3) == 'xri' && (arg(4) != '@example*résumé;%25' || $_GET['_xrd_r'] != 'application/xrds xml')) {
drupal_not_found();
}
- drupal_add_http_header('Content-Type', 'application/xrds+xml');
- print '
+ $output = '
@@ -127,7 +129,7 @@ function openid_test_yadis_xrds() {
';
if (arg(3) == 'server') {
- print '
+ $output .= '
http://specs.openid.net/auth/2.0/server
http://example.com/this-has-too-low-priority
@@ -138,7 +140,7 @@ function openid_test_yadis_xrds() {
';
}
elseif (arg(3) == 'delegate') {
- print '
+ $output .= '
http://specs.openid.net/auth/2.0/signon
http://openid.net/srv/ax/1.0
@@ -146,9 +148,10 @@ function openid_test_yadis_xrds() {
http://example.com/xrds-delegate
';
}
- print '
+ $output .= '
';
+ return new Response($output, 200, array('Content-type' => 'application/xrds+xml; charset=utf-8'));
}
else {
return t('This is a regular HTML page. If the client sends an Accept: application/xrds+xml header when requesting this URL, an XRDS document is returned.');
@@ -207,11 +210,9 @@ function openid_test_html_openid2() {
function openid_test_endpoint() {
switch ($_REQUEST['openid_mode']) {
case 'associate':
- _openid_test_endpoint_associate();
- break;
+ return _openid_test_endpoint_associate();
case 'checkid_setup':
- _openid_test_endpoint_authenticate();
- break;
+ return _openid_test_endpoint_authenticate();
}
}
@@ -226,8 +227,7 @@ function openid_test_redirect($count = 0) {
$url = url('openid-test/redirect/' . --$count, array('absolute' => TRUE));
}
$http_response_code = variable_get('openid_test_redirect_http_reponse_code', 301);
- header('Location: ' . $url, TRUE, $http_response_code);
- exit();
+ return new RedirectResponse($url, $http_response_code);
}
/**
@@ -283,8 +283,7 @@ function _openid_test_endpoint_associate() {
// Respond to Relying Party in the special Key-Value Form Encoding (see OpenID
// Authentication 1.0, section 4.1.1).
- drupal_add_http_header('Content-Type', 'text/plain');
- print _openid_create_message($response);
+ return new Response(_openid_create_message($response), 200, array('Content-Type' => 'text/plain'));
}
/**
@@ -306,9 +305,7 @@ function _openid_test_endpoint_authenticate() {
'openid.mode' => 'error',
'openid.error' => 'Unexpted identity',
);
- drupal_add_http_header('Content-Type', 'text/plain');
- header('Location: ' . url($_REQUEST['openid_return_to'], array('query' => $response, 'external' => TRUE)));
- return;
+ return new RedirectResponse(url($_REQUEST['openid_return_to'], array('query' => $response, 'external' => TRUE)));
}
// Generate unique identifier for this authentication.
@@ -348,8 +345,7 @@ function _openid_test_endpoint_authenticate() {
// Put the signed message into the query string of a URL supplied by the
// Relying Party, and redirect the user.
- drupal_add_http_header('Content-Type', 'text/plain');
- header('Location: ' . url($_REQUEST['openid_return_to'], array('query' => $response, 'external' => TRUE)));
+ return new RedirectResponse(url($_REQUEST['openid_return_to'], array('query' => $response, 'external', TRUE)));
}
/**
diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module
index 02c0883..5fdd453 100644
--- a/core/modules/overlay/overlay.module
+++ b/core/modules/overlay/overlay.module
@@ -5,6 +5,8 @@
* Displays the Drupal administration interface in an overlay.
*/
+use Symfony\Component\HttpFoundation\Response;
+
/**
* Implements hook_help().
*/
@@ -19,7 +21,7 @@ function overlay_help($path, $arg) {
}
/**
- * Implements hook_menu().
+ * Implements hook_menu()
*/
function overlay_menu() {
$items['overlay-ajax/%'] = array(
@@ -32,7 +34,7 @@ function overlay_menu() {
$items['overlay/dismiss-message'] = array(
'title' => '',
'page callback' => 'overlay_user_dismiss_message',
- 'access arguments' => array('access overlay'),
+ 'access callback' => 'overlay_user_dismiss_message_access',
'type' => MENU_CALLBACK,
);
return $items;
@@ -299,25 +301,48 @@ function overlay_page_alter(&$page) {
}
/**
- * Menu callback; dismisses the overlay accessibility message for this user.
+ * Access callback; determines access to dismiss the overlay accessibility message.
+ *
+ * @see overlay_user_dismiss_message()
+ * @see overlay_menu()
*/
-function overlay_user_dismiss_message() {
+function overlay_user_dismiss_message_access() {
global $user;
+ if (!user_access('access overlay')) {
+ return FALSE;
+ }
// It's unlikely, but possible that "access overlay" permission is granted to
// the anonymous role. In this case, we do not display the message to disable
- // the overlay, so there is nothing to dismiss. Also, protect against
- // cross-site request forgeries by validating a token.
- if (empty($user->uid) || !isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'overlay')) {
- return MENU_ACCESS_DENIED;
+ // the overlay, so there is nothing to dismiss.
+ if (empty($user->uid)) {
+ return FALSE;
}
- else {
- $account = user_load($user->uid);
- $account->data['overlay_message_dismissed'] = 1;
- $account->save();
- drupal_set_message(t('The message has been dismissed. You can change your overlay settings at any time by visiting your profile page.'));
- // Destination is normally given. Go to the user profile as a fallback.
- drupal_goto('user/' . $user->uid . '/edit');
+ return TRUE;
+}
+
+/**
+ * Menu callback; dismisses the overlay accessibility message for this user.
+ *
+ * @see overlay_user_dismiss_message_access()
+ * @see overlay_menu()
+ */
+function overlay_user_dismiss_message() {
+ global $user;
+
+ // @todo CSRF tokens are validated in page callbacks rather than access
+ // callbacks, because access callbacks are also invoked during menu link
+ // generation. Add token support to routing: http://drupal.org/node/755584.
+ $token = request()->query->get('token');
+ if (!isset($token) || !drupal_valid_token($token, 'overlay')) {
+ drupal_access_denied();
}
+
+ $account = user_load($user->uid);
+ $account->data['overlay_message_dismissed'] = 1;
+ $account->save();
+ drupal_set_message(t('The message has been dismissed. You can change your overlay settings at any time by visiting your profile page.'));
+ // Destination is normally given. Go to the user profile as a fallback.
+ drupal_goto('user/' . $user->uid . '/edit');
}
/**
@@ -667,7 +692,8 @@ function overlay_overlay_child_initialize() {
// it to the same content rendered in overlay_exit(), at the end of the page
// request. This allows us to check if anything actually did change, and, if
// so, trigger an immediate Ajax refresh of the parent window.
- if (!empty($_POST) || isset($_GET['token'])) {
+ $token = request()->query->get('token');
+ if (!empty($_POST) || isset($token)) {
foreach (overlay_supplemental_regions() as $region) {
overlay_store_rendered_content($region, overlay_render_region($region));
}
@@ -979,5 +1005,5 @@ function overlay_trigger_refresh() {
* @see Drupal.overlay.refreshRegions()
*/
function overlay_ajax_render_region($region) {
- print overlay_render_region($region);
+ return new Response(overlay_render_region($region));
}
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index 33b26e3..14e218a 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -1158,6 +1158,7 @@ abstract class WebTestBase extends TestBase {
* Retrieve a Drupal path or an absolute path and JSON decode the result.
*/
protected function drupalGetAJAX($path, array $options = array(), array $headers = array()) {
+ $headers[] = 'X-Requested-With: XMLHttpRequest';
return drupal_json_decode($this->drupalGet($path, $options, $headers));
}
@@ -1365,6 +1366,7 @@ abstract class WebTestBase extends TestBase {
}
$content = $this->content;
$drupal_settings = $this->drupalSettings;
+ $headers[] = 'X-Requested-With: XMLHttpRequest';
// Get the Ajax settings bound to the triggering element.
if (!isset($ajax_settings)) {
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index fc0e8bb..a31e278 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -5,6 +5,8 @@
* Admin page callbacks for the system module.
*/
+use Symfony\Component\HttpFoundation\Response;
+
/**
* Menu callback; Provide the administration overview page.
*/
@@ -2303,6 +2305,9 @@ function system_batch_page() {
if ($output === FALSE) {
drupal_access_denied();
}
+ elseif ($output instanceof Response) {
+ return $output;
+ }
elseif (isset($output)) {
// Force a page without blocks or messages to
// display a list of collected messages later.
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 6e60da8..4dfb6e4 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -5,6 +5,8 @@
* Configuration system that lets administrators modify the workings of the site.
*/
+use Symfony\Component\HttpFoundation\Response;
+
/**
* Maximum age of temporary files in seconds.
*/
@@ -1114,11 +1116,13 @@ function system_menu() {
*
* @see system_cron_access().
*/
+
function system_cron_page() {
drupal_page_is_cacheable(FALSE);
drupal_cron_run();
- // Returning nothing causes no output to be generated.
+ // HTTP 204 is "No content", meaning "I did what you asked and we're done."
+ return new Response('', 204);
}
/**
@@ -2008,8 +2012,11 @@ function system_add_module_assets() {
* Implements hook_custom_theme().
*/
function system_custom_theme() {
- if (user_access('view the administration theme') && path_is_admin(current_path())) {
- return variable_get('admin_theme');
+ if ($request = request()) {
+ $path = $request->attributes->get('system_path');
+ if (user_access('view the administration theme') && path_is_admin($path)) {
+ return variable_get('admin_theme');
+ }
}
}
diff --git a/core/modules/system/system.test b/core/modules/system/system.test
index ec1a9a6..9d25130 100644
--- a/core/modules/system/system.test
+++ b/core/modules/system/system.test
@@ -824,7 +824,7 @@ class CronRunTestCase extends WebTestBase {
// Run cron anonymously with the valid cron key.
$key = config('system.cron')->get('cron_key');
$this->drupalGet('cron/' . $key);
- $this->assertResponse(200);
+ $this->assertResponse(204);
}
/**
diff --git a/core/modules/system/tests/bootstrap.test b/core/modules/system/tests/bootstrap.test
index 098e12e..b3650bd 100644
--- a/core/modules/system/tests/bootstrap.test
+++ b/core/modules/system/tests/bootstrap.test
@@ -194,7 +194,7 @@ class BootstrapPageCacheTestCase extends WebTestBase {
$this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), t('Caching was bypassed.'));
$this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, t('Vary: Cookie header was not sent.'));
- $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', t('Cache-Control header was sent.'));
+ $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, post-check=0, pre-check=0, private', t('Cache-Control header was sent.'));
$this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', t('Expires header was sent.'));
$this->assertEqual($this->drupalGetHeader('Foo'), 'bar', t('Custom header was sent.'));
diff --git a/core/modules/system/tests/error.test b/core/modules/system/tests/error.test
index a088902..0a3b522 100644
--- a/core/modules/system/tests/error.test
+++ b/core/modules/system/tests/error.test
@@ -75,14 +75,14 @@ class DrupalErrorHandlerUnitTest extends WebTestBase {
'%type' => 'Exception',
'!message' => 'Drupal is awesome',
'%function' => 'error_test_trigger_exception()',
- '%line' => 57,
+ '%line' => 56,
'%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
);
$error_pdo_exception = array(
- '%type' => 'PDOException',
+ '%type' => 'DatabaseExceptionWrapper',
'!message' => 'SELECT * FROM bananas_are_awesome',
'%function' => 'error_test_trigger_pdo_exception()',
- '%line' => 65,
+ '%line' => 64,
'%file' => drupal_get_path('module', 'error_test') . '/error_test.module',
);
diff --git a/core/modules/taxonomy/taxonomy.test b/core/modules/taxonomy/taxonomy.test
index 4a3db65..c7e87e8 100644
--- a/core/modules/taxonomy/taxonomy.test
+++ b/core/modules/taxonomy/taxonomy.test
@@ -769,9 +769,8 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
$path = 'taxonomy/autocomplete/taxonomy_';
$path .= $this->vocabulary->machine_name . '/' . $input;
// The result order is not guaranteed, so check each term separately.
- $url = url($path, array('absolute' => TRUE));
- $result = drupal_http_request($url);
- $data = drupal_json_decode($result->data);
+ $result = $this->drupalGet($path);
+ $data = drupal_json_decode($result);
$this->assertEqual($data[$first_term->name], check_plain($first_term->name), 'Autocomplete returned the first matching term');
$this->assertEqual($data[$second_term->name], check_plain($second_term->name), 'Autocomplete returned the second matching term');
diff --git a/core/modules/update/tests/modules/update_test/update_test.module b/core/modules/update/tests/modules/update_test/update_test.module
index ff5aad5..6254d25 100644
--- a/core/modules/update/tests/modules/update_test/update_test.module
+++ b/core/modules/update/tests/modules/update_test/update_test.module
@@ -1,5 +1,8 @@
'text/xml; charset=utf-8');
+ if (!is_file($file)) {
+ // Return an empty response.
+ return new Response('', 200, $headers);
+ }
+ return file_transfer($file, $headers);
}
/**
diff --git a/core/modules/update/update.fetch.inc b/core/modules/update/update.fetch.inc
index c6652a70..4d8e3c2 100644
--- a/core/modules/update/update.fetch.inc
+++ b/core/modules/update/update.fetch.inc
@@ -142,7 +142,7 @@ function _update_process_fetch_task($project) {
$project_name = $project['name'];
if (empty($fail[$fetch_url_base]) || $fail[$fetch_url_base] < $max_fetch_attempts) {
- $result = drupal_http_request($url);
+ $result = drupal_http_request($url, array('headers' => array('accept' => 'text/xml')));
if (isset($result->error)) {
watchdog('update', 'HTTP request to @url failed with error: @error.', array('@url' => $url, '@error' => $result->error));
}
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index cfde270..09ef3f5 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -2328,9 +2328,12 @@ function user_delete_multiple(array $uids) {
* Page callback wrapper for user_view().
*/
function user_view_page($account) {
+ if (is_object($account)) {
+ return user_view($account);
+ }
// An administrator may try to view a non-existent account,
// so we give them a 404 (versus a 403 for non-admins).
- return is_object($account) ? user_view($account) : MENU_NOT_FOUND;
+ drupal_not_found();
}
/**
diff --git a/core/update.php b/core/update.php
index 9797833..b47702e 100644
--- a/core/update.php
+++ b/core/update.php
@@ -14,6 +14,9 @@
* back to its original state!
*/
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
// Change the directory to the Drupal root.
chdir('..');
@@ -391,11 +394,24 @@ $default = language_default();
drupal_container()->register(LANGUAGE_TYPE_INTERFACE, 'Drupal\\Core\\Language\\Language')
->addMethodCall('extend', array($default));
+// A request object from the HTTPFoundation to tell us about the request.
+// @todo These two lines were copied from index.php which has its own todo about
+// a change required here. Revisit this when that change has been made.
+$request = Request::createFromGlobals();
+request($request);
+
+// There can be conflicting 'op' parameters because both update and batch use
+// this parameter name. We need the 'op' coming from a POST request to trump
+// that coming from a GET request.
+$op = $request->request->get('op');
+if (is_null($op)) {
+ $op = $request->query->get('op');
+}
+
// Only allow the requirements check to proceed if the current user has access
// to run updates (since it may expose sensitive information about the site's
// configuration).
-$op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
-if (empty($op) && update_access_allowed()) {
+if (is_null($op) && update_access_allowed()) {
require_once DRUPAL_ROOT . '/core/includes/install.inc';
require_once DRUPAL_ROOT . '/core/modules/system/system.install';
@@ -423,16 +439,10 @@ if (empty($op) && update_access_allowed()) {
install_goto('core/update.php?op=info');
}
-// update_fix_d8_requirements() needs to run before bootstrapping beyond path.
-// So bootstrap to DRUPAL_BOOTSTRAP_LANGUAGE then include unicode.inc.
-
-drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE);
-include_once DRUPAL_ROOT . '/core/includes/unicode.inc';
-
+// Allow update_fix_d8_requirements() to run before code that can break on a
+// Drupal 7 database.
+drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
update_fix_d8_requirements();
-
-// Now proceed with a full bootstrap.
-
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
drupal_maintenance_theme();
@@ -440,6 +450,7 @@ drupal_maintenance_theme();
// not passed through the error handler) will cause a message to be printed.
ini_set('display_errors', TRUE);
+
// Only proceed with updates if the user is allowed to run them.
if (update_access_allowed()) {
@@ -453,27 +464,29 @@ if (update_access_allowed()) {
// no errors, skip reporting them if the user has provided a URL parameter
// acknowledging the warnings and indicating a desire to continue anyway. See
// drupal_requirements_url().
- $skip_warnings = !empty($_GET['continue']);
+ $continue = $request->query->get('continue');
+ $skip_warnings = !empty($continue);
update_check_requirements($skip_warnings);
- $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
switch ($op) {
// update.php ops.
case 'selection':
- if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) {
+ $token = $request->query->get('token');
+ if (isset($token) && drupal_valid_token($token, 'update')) {
$output = update_selection_page();
break;
}
case 'Apply pending updates':
- if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) {
+ $token = $request->query->get('token');
+ if (isset($token) && drupal_valid_token($token, 'update')) {
// Generate absolute URLs for the batch processing (using $base_root),
// since the batch API will pass them to url() which does not handle
// update.php correctly by default.
$batch_url = $base_root . drupal_current_script_url();
$redirect_url = $base_root . drupal_current_script_url(array('op' => 'results'));
- update_batch($_POST['start'], $redirect_url, $batch_url);
+ update_batch($request->request->get('start'), $redirect_url, $batch_url);
break;
}
@@ -500,5 +513,11 @@ if (isset($output) && $output) {
drupal_session_start();
// We defer the display of messages until all updates are done.
$progress_page = ($batch = batch_get()) && isset($batch['running']);
- print theme('update_page', array('content' => $output, 'show_messages' => !$progress_page));
+ if ($output instanceof Response) {
+ $output->send();
+ }
+ else {
+ print theme('update_page', array('content' => $output, 'show_messages' => !$progress_page));
+ }
+
}
diff --git a/index.php b/index.php
index b91fb1e..2f05bb3 100644
--- a/index.php
+++ b/index.php
@@ -11,11 +11,37 @@
* See COPYRIGHT.txt and LICENSE.txt files in the "core" directory.
*/
+use Drupal\Core\DrupalKernel;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\HttpKernel\Controller\ControllerResolver;
+
/**
* Root directory of Drupal installation.
*/
define('DRUPAL_ROOT', getcwd());
-
+// Bootstrap the lowest level of what we need.
require_once DRUPAL_ROOT . '/core/includes/bootstrap.inc';
-drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
-menu_execute_active_handler();
+drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
+
+// Create a request object from the HTTPFoundation.
+$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);
+
+// Bootstrap all of Drupal's subsystems, but do not initialize anything that
+// depends on the fully resolved Drupal path, because path resolution happens
+// during the REQUEST event of the kernel.
+// @see Drupal\Core\EventSubscriber\PathSubscriber;
+// @see Drupal\Core\EventSubscriber\LegacyRequestSubscriber;
+drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
+
+$dispatcher = new EventDispatcher();
+$resolver = new ControllerResolver();
+
+$kernel = new DrupalKernel($dispatcher, $resolver);
+$response = $kernel->handle($request)->prepare($request)->send();
+$kernel->terminate($request, $response);