diff --git a/core/core.services.yml b/core/core.services.yml index 6f6046d..88f026f 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1169,7 +1169,7 @@ services: tags: - { name: event_subscriber } arguments: ['@config.manager', '@config.storage', '@config.storage.snapshot'] - exception.neds_installer: + exception.needs_installer: class: Drupal\Core\EventSubscriber\ExceptionDetectNeedsInstallSubscriber arguments: ['@database'] tags: diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index a0adb9e..db2df4f5 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -9,7 +9,6 @@ use Drupal\Core\Config\BootstrapConfigStorageFactory; use Drupal\Core\Config\NullStorage; use Drupal\Core\Database\Database; -use Drupal\Core\Database\DatabaseException; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceModifierInterface; use Drupal\Core\DependencyInjection\ServiceProviderInterface; @@ -17,6 +16,7 @@ use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\File\MimeType\MimeTypeGuesser; use Drupal\Core\Http\TrustedHostsRequestFactory; +use Drupal\Core\Installer\CheckInstalledTrait; use Drupal\Core\Language\Language; use Drupal\Core\Site\Settings; use Symfony\Cmf\Component\Routing\RouteObjectInterface; @@ -45,6 +45,7 @@ * container, or modify existing services. */ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { + use CheckInstalledTrait; /** * Holds the class used for dumping the container to a PHP array. @@ -661,57 +662,20 @@ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = * If the passed in exception cannot be turned into a response. */ protected function handleException(\Exception $e, $request, $type) { + if ($this->shouldRedirectToInstaller($e, $this->container ? $this->container->get('database') : NULL)) { + return new RedirectResponse($request->getBasePath() . '/core/install.php', 302, ['Cache-Control' => 'no-cache']); + } + if ($e instanceof HttpExceptionInterface) { $response = new Response($e->getMessage(), $e->getStatusCode()); $response->headers->add($e->getHeaders()); return $response; } - if (PHP_SAPI !== 'cli' && $this->isNotInstalledException($e)) { - return new RedirectResponse($request->getBasePath() . '/core/install.php', 302, ['Cache-Control' => 'no-cache']); - } - throw $e; } /** - * Determines if the exception is for a missing or not installed database. - * - * @param \Exception $e - * The exception to check. - * - * @return bool - * TRUE if the exception is for a missing or not fully installed database, - * FALSE otherwise. - */ - protected function isNotInstalledException(\Exception $e) { - if (!($e instanceof \PDOException || $e instanceof DatabaseException)) { - return FALSE; - } - - // If we're already in the installer, don't worry about Drupal not being - // installed. - if (drupal_installation_attempted()) { - return FALSE; - } - - // Code 1049 is "missing database". - if ($e->getCode() == 1049) { - return TRUE; - } - - // SQLSTATE[42S02] indicates a missing table. Since we should only get here - // in case a bootstrap step failed, we assume that whatever table is missing - // is missing because the installation is broken, incomplete, or not even - // started. - if ($e->getCode() == 0 && strpos($e->getMessage(), 'SQLSTATE[42S02]') !== FALSE) { - return TRUE; - } - - return FALSE; - } - - /** * {@inheritdoc} */ public function prepareLegacyRequest(Request $request) { diff --git a/core/lib/Drupal/Core/EventSubscriber/ExceptionDetectNeedsInstallSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ExceptionDetectNeedsInstallSubscriber.php index 0a9f58d..b94a0dc 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ExceptionDetectNeedsInstallSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ExceptionDetectNeedsInstallSubscriber.php @@ -3,19 +3,17 @@ namespace Drupal\Core\EventSubscriber; use Drupal\Core\Database\Connection; +use Drupal\Core\Installer\CheckInstalledTrait; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; /** * Exception handler to determine if an exception indicates an uninstalled site. - * - * Note: The technique here assumes Drupal is installed using SQL, and that - * sessions are being stored in the database. If sessions are stored elsewhere - * or Drupal is being run entirely non-SQL then this detection mechanism may - * fail. In such environments it may be necessary to disable this service - * via the container. */ -class ExceptionDetectNeedsInstallSubscriber extends HttpExceptionSubscriberBase { +class ExceptionDetectNeedsInstallSubscriber implements EventSubscriberInterface { + use CheckInstalledTrait; /** * The default database connection. @@ -24,65 +22,44 @@ class ExceptionDetectNeedsInstallSubscriber extends HttpExceptionSubscriberBase */ protected $connection; - public function __construct(Connection $connection) { - $this->connection = $connection; - } - - /** - * {@inheritdoc} - */ - protected function getHandledFormats() { - return ['html']; - } - - /** - * {@inheritdoc} - */ - protected static function getPriority() { - return 100; - } - /** - * Determines if Drupal still needs to be installed. + * Constructs a new ExceptionDetectNeedsInstallSubscriber. * - * @return bool - * TRUE if the system still needs to be installed, FALSE otherwise. + * @param \Drupal\Core\Database\Connection $connection + * The default database connection. */ - protected function systemNeedsInstall() { - // If we're already in the installer, don't worry about Drupal not being - // installed. - if (PHP_SAPI === 'cli' || drupal_installation_attempted()) { - return FALSE; - } - - // If there's no sessions table, we assume it means Drupal was never - // installed. This may be an incorrect assumption on non-SQL-based - // installations. - return !$this->connection->schema()->tableExists('session'); + public function __construct(Connection $connection) { + $this->connection = $connection; } /** - * Checks if a 404 error is really a sign of an uninstalled Drupal. - * - * @todo Remove this method in favor of on400() once - * https://www.drupal.org/node/2739617 is committed. + * Handles errors for this subscriber. * * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event * The event to process. */ - public function on404(GetResponseForExceptionEvent $event) { - return $this->on400($event); + public function onException(GetResponseForExceptionEvent $event) { + $exception = $event->getException(); + if ($this->shouldRedirectToInstaller($exception, $this->connection)) { + // Only redirect if this is an HTML response (i.e., a user trying to view + // the site in a web browser before installing it). + $request = $event->getRequest(); + $format = $request->query->get(MainContentViewSubscriber::WRAPPER_FORMAT, $request->getRequestFormat()); + if ($format == 'html') { + $event->setResponse(new RedirectResponse($request->getBasePath() . '/core/install.php', 302, ['Cache-Control' => 'no-cache'])); + } + } } /** - * Checks if a 404 error is really a sign of an uninstalled Drupal. - * - * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * Registers the methods in this class that should be listeners. * + * @return array + * An array of event listener definitions. */ - public function on400(GetResponseForExceptionEvent $event) { - if ($this->systemNeedsInstall()) { - $event->setResponse(new RedirectResponse($event->getRequest()->getBasePath() . '/core/install.php', 302, ['Cache-Control' => 'no-cache'])); - } + public static function getSubscribedEvents() { + $events[KernelEvents::EXCEPTION][] = ['onException', 100]; + return $events; } + } diff --git a/core/lib/Drupal/Core/Installer/CheckInstalledTrait.php b/core/lib/Drupal/Core/Installer/CheckInstalledTrait.php new file mode 100644 index 0000000..d00d6d2 --- /dev/null +++ b/core/lib/Drupal/Core/Installer/CheckInstalledTrait.php @@ -0,0 +1,80 @@ +schema()->tableExists('sessions'); + } + catch (\Exception $e) { + // If the query failed, use the exception it failed with for the rest + // of the checks in this function; it will be more reliable since + // unlike the original exception, we know exactly where it came from. + $exception_to_check = $e; + } + } + + // If we still have an exception at this point, we need to be careful since + // we should not redirect if the exception represents an error on an + // already-installed site (for example, if the database server went down). + // However a database error with code 1049 means "missing database", and it + // should be safe to redirect in that case. + return ($exception_to_check instanceof \PDOException || $exception_to_check instanceof DatabaseException) && $exception_to_check->getCode() == 1049; + } + +}