diff --git a/.htaccess b/.htaccess
index 895bd4c..a315abc 100644
--- a/.htaccess
+++ b/.htaccess
@@ -108,7 +108,7 @@ DirectoryIndex index.php index.html index.htm
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
- RewriteRule ^ index.php [L]
+ RewriteRule ^(.*)$ index.php [L]
# Rules to correctly serve gzip compressed CSS and JS files.
# Requires both mod_rewrite and mod_headers to be enabled.
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 f65ab4c..e180d1c 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,13 @@ 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; validate and
+ * fix input data.
*/
-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;
@@ -1535,6 +1537,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
@@ -2023,7 +2048,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
@@ -2076,12 +2101,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;
}
@@ -2304,7 +2329,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 fea16e1..8392e64 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() {
+function _drupal_bootstrap_code() {
static $called = FALSE;
if ($called) {
@@ -5197,6 +5207,24 @@ function _drupal_bootstrap_full() {
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.
+ */
+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();
@@ -5230,7 +5258,7 @@ function _drupal_bootstrap_full() {
*
* @see drupal_page_header()
*/
-function drupal_page_set_cache() {
+function drupal_page_set_cache($response_body) {
global $base_root;
if (drupal_page_is_cacheable()) {
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 7559608..c3454e6 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;
/**
@@ -2046,18 +2049,27 @@ 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 new StreamedResponse(function() use ($uri) {
+ $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);
+ }
+ fclose($fd);
+ }
+ }, 200, $headers);
}
- return drupal_access_denied();
+ throw new AccessDeniedHttpException();
}
- return drupal_not_found();
+ throw new NotFoundHttpException();
}
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 31ef052..dea784b 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.
@@ -241,6 +244,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;
@@ -468,6 +479,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..701262a
--- /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..dc6762b
--- /dev/null
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -0,0 +1,90 @@
+dispatcher = $dispatcher;
+ $this->resolver = $resolver;
+
+ $this->matcher = new UrlMatcher();
+ $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..683f1fb
--- /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..cc4da15
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
@@ -0,0 +1,64 @@
+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..12ca8b6
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/LegacyRequestSubscriber.php
@@ -0,0 +1,56 @@
+getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
+ 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..8f29b4d
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/PathListenerBase.php
@@ -0,0 +1,30 @@
+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..8b156d4
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php
@@ -0,0 +1,64 @@
+getResponse();
+ $config = config('system.performance');
+
+ if ($config->get('cache') && ($cache = drupal_page_set_cache($response->getContent()))) {
+ 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..f1a96f7
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/RouterListener.php
@@ -0,0 +1,81 @@
+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($event->getRequest());
+ }
+
+ 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..4761d47
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
@@ -0,0 +1,128 @@
+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..021af0a
--- /dev/null
+++ b/core/lib/Drupal/Core/ExceptionController.php
@@ -0,0 +1,399 @@
+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\Event\GetResponseEvent $event
+ * The Event to process.
+ */
+ 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\Event\GetResponseEvent $event
+ * The Event to process.
+ */
+ 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 403 response.
+ *
+ * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+ * The Event to process.
+ */
+ 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 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 into an HTTP 403 response.
+ *
+ * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+ * The Event to process.
+ */
+ public function on403Json(FlattenException $exception, Request $request) {
+ $response = new JsonResponse();
+ $response->setStatusCode(403, 'Access Denied');
+ return $response;
+ }
+
+ /**
+ * Processes a NotFound exception into an HTTP 404 response.
+ *
+ * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+ * The Event to process.
+ */
+ public function on404Json(FlattenException $exception, Request $request) {
+ $response = new JsonResponse();
+ $response->setStatusCode(404, 'Not Found');
+ return $response;
+ }
+
+ /**
+ * Processes a MethodNotAllowed exception into an HTTP 405 response.
+ *
+ * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+ * The Event to process.
+ */
+ 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/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/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
index 0cd76e0..1b4abc9 100644
--- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
@@ -44,7 +44,7 @@ abstract class LocalStream implements StreamWrapperInterface {
/**
* Gets the path that the wrapper is responsible for.
- * @TODO: Review this method name in D8 per http://drupal.org/node/701358
+ * @todo: Review this method name in D8 per http://drupal.org/node/701358
*
* @return string
* String specifying the path.
diff --git a/core/lib/Drupal/Core/Updater/Updater.php b/core/lib/Drupal/Core/Updater/Updater.php
index 2dca5ba..ad2213a 100644
--- a/core/lib/Drupal/Core/Updater/Updater.php
+++ b/core/lib/Drupal/Core/Updater/Updater.php
@@ -213,7 +213,7 @@ class Updater {
$this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name);
// Run the updates.
- // @TODO: decide if we want to implement this.
+ // @todo: decide if we want to implement this.
$this->postUpdate();
// For now, just return a list of links of things to do.
@@ -252,7 +252,7 @@ class Updater {
$this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name);
// Potentially enable something?
- // @TODO: decide if we want to implement this.
+ // @todo: decide if we want to implement this.
$this->postInstall();
// For now, just return a list of links of things to do.
return $this->postInstallTasks();
diff --git a/core/lib/Drupal/Core/UrlMatcher.php b/core/lib/Drupal/Core/UrlMatcher.php
new file mode 100644
index 0000000..7d6e24c
--- /dev/null
+++ b/core/lib/Drupal/Core/UrlMatcher.php
@@ -0,0 +1,140 @@
+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;
+ }
+
+ public function setRequest(Request $request) {
+ $this->request = $request;
+ }
+
+ 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);
+ }
+
+ protected function convertDrupalItem($router_item) {
+ $route = array(
+ '_controller' => $router_item['page_callback']
+ );
+
+ // Place argument defaults on the route.
+ // @todo For some reason drush test runs have a serialized page_arguments
+ // but HTTP requests are unserialized. Hack to get around this for now.
+ // This might be because page arguments aren't unserialized in
+ // menu_get_item() when the access is denied.
+ !is_array($router_item['page_arguments']) ? $page_arguments = unserialize($router_item['page_arguments']) : $page_arguments = $router_item['page_arguments'];
+ foreach ($page_arguments as $k => $v) {
+ $route[$k] = $v;
+ }
+ return $route;
+ }
+}
diff --git a/core/modules/aggregator/aggregator.admin.inc b/core/modules/aggregator/aggregator.admin.inc
index 09da1cf..ac868b4 100644
--- a/core/modules/aggregator/aggregator.admin.inc
+++ b/core/modules/aggregator/aggregator.admin.inc
@@ -401,11 +401,9 @@ function _aggregator_parse_opml($opml) {
* An object describing the feed to be refreshed.
*
* @see aggregator_menu()
+ * @see aggregator_admin_refresh_feed_access()
*/
function aggregator_admin_refresh_feed($feed) {
- if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'aggregator/update/' . $feed->fid)) {
- return MENU_ACCESS_DENIED;
- }
aggregator_refresh($feed);
drupal_goto('admin/config/services/aggregator');
}
diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index 0626f72..ee285e2 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -138,7 +138,8 @@ function aggregator_menu() {
'title' => 'Update items',
'page callback' => 'aggregator_admin_refresh_feed',
'page arguments' => array(5),
- 'access arguments' => array('administer news feeds'),
+ 'access callback' => 'aggregator_admin_refresh_feed_access',
+ 'access arguments' => array(5),
'file' => 'aggregator.admin.inc',
);
$items['admin/config/services/aggregator/list'] = array(
@@ -796,3 +797,23 @@ function aggregator_preprocess_block(&$variables) {
$variables['attributes_array']['role'] = 'complementary';
}
}
+
+/**
+ * Access callback: Determines if feed refresh is accessible.
+ *
+ * @param $feed
+ * An object describing the feed to be refreshed.
+ *
+ * @see aggregator_admin_refresh_feed()
+ * @see aggregator_menu()
+ */
+function aggregator_admin_refresh_feed_access($feed) {
+ if (!user_access('administer news feeds')) {
+ return FALSE;
+ }
+ $token = request()->query->get('token');
+ if (!isset($token) || !drupal_valid_token($token, 'aggregator/update/' . $feed->fid)) {
+ return FALSE;
+ }
+ return TRUE;
+}
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.module b/core/modules/comment/comment.module
index 5b42861..1ba59ad 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -277,7 +277,8 @@ function comment_menu() {
'title' => 'Approve',
'page callback' => 'comment_approve',
'page arguments' => array(1),
- 'access arguments' => array('administer comments'),
+ 'access callback' => 'comment_approve_access',
+ 'access arguments' => array(1),
'file' => 'comment.pages.inc',
'weight' => 1,
);
@@ -2514,3 +2515,23 @@ function comment_file_download_access($field, $entity_type, $entity) {
return FALSE;
}
}
+
+/**
+ * Access callback: Determines if comment approval is accessible.
+ *
+ * @param $cid
+ * A comment identifier.
+ *
+ * @see comment_approve()
+ * @see comment_menu()
+ */
+function comment_approve_access($cid) {
+ if (!user_access('administer comments')) {
+ return FALSE;
+ }
+ $token = request()->query->get('token');
+ if (!isset($token) || !drupal_valid_token($token, "comment/$cid/approve")) {
+ return FALSE;
+ }
+ return TRUE;
+}
diff --git a/core/modules/comment/comment.pages.inc b/core/modules/comment/comment.pages.inc
index bac078b..59423ec 100644
--- a/core/modules/comment/comment.pages.inc
+++ b/core/modules/comment/comment.pages.inc
@@ -105,11 +105,9 @@ function comment_reply(Node $node, $pid = NULL) {
* A comment identifier.
*
* @see comment_menu()
+ * @see comment_approve_access()
*/
function comment_approve($cid) {
- if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], "comment/$cid/approve")) {
- return MENU_ACCESS_DENIED;
- }
if ($comment = comment_load($cid)) {
$comment->status = COMMENT_PUBLISHED;
comment_save($comment);
@@ -117,5 +115,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.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/locale/locale.test b/core/modules/locale/locale.test
index 5e252bf..dce77b2 100644
--- a/core/modules/locale/locale.test
+++ b/core/modules/locale/locale.test
@@ -1677,7 +1677,7 @@ class LocalePathFunctionalTest extends WebTestBase {
// Check that the "xx" front page is readily available because path prefix
// negotiation is pre-configured.
$this->drupalGet($prefix);
- $this->assertText(t('Welcome to Drupal'), t('The "xx" front page is readibly available.'));
+ $this->assertText(t('Welcome to Drupal'), t('The "xx" front page is readily available.'));
// Create a node.
$node = $this->drupalCreateNode(array('type' => 'page'));
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 5509830..597cb7a 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/node/node.test b/core/modules/node/node.test
index 9188bad..8512d27 100644
--- a/core/modules/node/node.test
+++ b/core/modules/node/node.test
@@ -1798,11 +1798,8 @@ class NodeFeedTestCase 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/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..2628260 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,44 @@ 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');
+ // Protect against cross-site request forgeries by validating a token.
+ $token = request()->query->get('token');
+ if (!isset($token) || !drupal_valid_token($token, 'overlay')) {
+ return FALSE;
}
+ 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;
+ $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 +688,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 +1001,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 eb7c5a3..0c19eec 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -1153,6 +1153,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));
}
@@ -1360,6 +1361,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 a10e246..f8fc280 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.
*/
@@ -2287,6 +2289,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 e685c16..c863e94 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('a', 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 40f590e..e8c8f65 100644
--- a/core/modules/system/system.test
+++ b/core/modules/system/system.test
@@ -821,7 +821,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/system/tests/modules/error_test/error_test.info b/core/modules/system/tests/modules/error_test/error_test.info
index d5db3ee..d00075d 100644
--- a/core/modules/system/tests/modules/error_test/error_test.info
+++ b/core/modules/system/tests/modules/error_test/error_test.info
@@ -3,4 +3,4 @@ description = "Support module for error and exception testing."
package = Testing
version = VERSION
core = 8.x
-hidden = TRUE
+hidden = FALSE
diff --git a/core/modules/system/tests/xmlrpc.test b/core/modules/system/tests/xmlrpc.test
index a77c38e..b5c07ca 100644
--- a/core/modules/system/tests/xmlrpc.test
+++ b/core/modules/system/tests/xmlrpc.test
@@ -19,6 +19,8 @@ class XMLRPCBasicTestCase extends WebTestBase {
* Ensure that a basic XML-RPC call with no parameters works.
*/
protected function testListMethods() {
+ global $base_url;
+
// Minimum list of methods that should be included.
$minimum = array(
'system.multicall',
@@ -29,7 +31,7 @@ class XMLRPCBasicTestCase extends WebTestBase {
);
// Invoke XML-RPC call to get list of methods.
- $url = url(NULL, array('absolute' => TRUE)) . 'core/xmlrpc.php';
+ $url = $base_url . '/core/xmlrpc.php';
$methods = xmlrpc($url, array('system.listMethods' => array()));
// Ensure that the minimum methods were found.
@@ -47,7 +49,9 @@ class XMLRPCBasicTestCase extends WebTestBase {
* Ensure that system.methodSignature returns an array of signatures.
*/
protected function testMethodSignature() {
- $url = url(NULL, array('absolute' => TRUE)) . 'core/xmlrpc.php';
+ global $base_url;
+
+ $url = $base_url . '/core/xmlrpc.php';
$signature = xmlrpc($url, array('system.methodSignature' => array('system.listMethods')));
$this->assert(is_array($signature) && !empty($signature) && is_array($signature[0]),
t('system.methodSignature returns an array of signature arrays.'));
@@ -99,7 +103,8 @@ class XMLRPCValidator1IncTestCase extends WebTestBase {
* Run validator1 tests.
*/
function testValidator1() {
- $xml_url = url(NULL, array('absolute' => TRUE)) . 'core/xmlrpc.php';
+ global $base_url;
+ $xml_url = $base_url . '/core/xmlrpc.php';
srand();
mt_srand();
@@ -213,7 +218,9 @@ class XMLRPCMessagesTestCase extends WebTestBase {
* Make sure that XML-RPC can transfer large messages.
*/
function testSizedMessages() {
- $xml_url = url(NULL, array('absolute' => TRUE)) . 'core/xmlrpc.php';
+ global $base_url;
+
+ $xml_url = $base_url . '/core/xmlrpc.php';
$sizes = array(8, 80, 160);
foreach ($sizes as $size) {
$xml_message_l = xmlrpc_test_message_sized_in_kb($size);
@@ -227,10 +234,11 @@ class XMLRPCMessagesTestCase extends WebTestBase {
* Ensure that hook_xmlrpc_alter() can hide even builtin methods.
*/
protected function testAlterListMethods() {
+ global $base_url;
// Ensure xmlrpc_test_xmlrpc_alter() is disabled and retrieve regular list of methods.
variable_set('xmlrpc_test_xmlrpc_alter', FALSE);
- $url = url(NULL, array('absolute' => TRUE)) . 'core/xmlrpc.php';
+ $url = $base_url . '/core/xmlrpc.php';
$methods1 = xmlrpc($url, array('system.listMethods' => array()));
// Enable the alter hook and retrieve the list of methods again.
diff --git a/core/modules/taxonomy/taxonomy.test b/core/modules/taxonomy/taxonomy.test
index 55b30fa..bf22852 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..b40f274 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'));
+ }
+ return new StreamedResponse(function() use ($file) {
+ // Transfer file in 1024 byte chunks to save memory usage.
+ if ($fd = fopen($file, 'rb')) {
+ while (!feof($fd)) {
+ print fread($fd, 1024);
+ }
+ fclose($fd);
+ }
+ }, 200, array('Content-Type' => 'text/xml; charset=utf-8'));
}
/**
diff --git a/core/modules/update/update.fetch.inc b/core/modules/update/update.fetch.inc
index 6de781d..06fe7f7 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) {
- $xml = drupal_http_request($url);
+ $xml = drupal_http_request($url, array('headers' => array('accept' => 'text/xml')));
if (!isset($xml->error) && isset($xml->data)) {
$data = $xml->data;
}
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..ab050a8 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,23 +439,16 @@ 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';
-
-update_fix_d8_requirements();
-
-// Now proceed with a full bootstrap.
-
+// Bootstrap, fix requirements, and set the maintenance theme.
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+update_fix_d8_requirements();
drupal_maintenance_theme();
// Turn error reporting back on. From now on, only fatal errors (which are
// 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 +462,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 +511,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..b63ba1f 100644
--- a/index.php
+++ b/index.php
@@ -11,11 +11,32 @@
* 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);
+
+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);