diff --git a/core/core.services.yml b/core/core.services.yml index 5877a38..6f6046d 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1169,6 +1169,11 @@ services: tags: - { name: event_subscriber } arguments: ['@config.manager', '@config.storage', '@config.storage.snapshot'] + exception.neds_installer: + class: Drupal\Core\EventSubscriber\ExceptionDetectNeedsInstallSubscriber + arguments: ['@database'] + tags: + - { name: event_subscriber } exception.default_json: class: Drupal\Core\EventSubscriber\ExceptionJsonSubscriber tags: diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 1a3b943..a0adb9e 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -9,6 +9,7 @@ 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; @@ -621,7 +622,7 @@ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = // installed yet (i.e., if no $databases array has been defined in the // settings.php file) and we are not already installing. if (!Database::getConnectionInfo() && !drupal_installation_attempted() && PHP_SAPI !== 'cli') { - $response = new RedirectResponse($request->getBasePath() . '/core/install.php'); + $response = new RedirectResponse($request->getBasePath() . '/core/install.php', 302, ['Cache-Control' => 'no-cache']); } else { $this->boot(); @@ -665,9 +666,49 @@ protected function handleException(\Exception $e, $request, $type) { $response->headers->add($e->getHeaders()); return $response; } - else { - throw $e; + + 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; } /** diff --git a/core/lib/Drupal/Core/EventSubscriber/ExceptionDetectNeedsInstallSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ExceptionDetectNeedsInstallSubscriber.php new file mode 100644 index 0000000..0a9f58d --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/ExceptionDetectNeedsInstallSubscriber.php @@ -0,0 +1,88 @@ +connection = $connection; + } + + /** + * {@inheritdoc} + */ + protected function getHandledFormats() { + return ['html']; + } + + /** + * {@inheritdoc} + */ + protected static function getPriority() { + return 100; + } + + /** + * Determines if Drupal still needs to be installed. + * + * @return bool + * TRUE if the system still needs to be installed, FALSE otherwise. + */ + 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'); + } + + /** + * 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. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * The event to process. + */ + public function on404(GetResponseForExceptionEvent $event) { + return $this->on400($event); + } + + /** + * Checks if a 404 error is really a sign of an uninstalled Drupal. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * + */ + public function on400(GetResponseForExceptionEvent $event) { + if ($this->systemNeedsInstall()) { + $event->setResponse(new RedirectResponse($event->getRequest()->getBasePath() . '/core/install.php', 302, ['Cache-Control' => 'no-cache'])); + } + } +}