automatic_updates-8.x-2.x-dev/src/Commands/AutomaticUpdatesCommandBase.php
src/Commands/AutomaticUpdatesCommandBase.php
<?php
namespace Drupal\automatic_updates\Commands;
use Drupal\automatic_updates\ConsoleUpdateSandboxManager;
use Drupal\automatic_updates\CronUpdateRunner;
use Drupal\automatic_updates\StatusCheckMailer;
use Drupal\automatic_updates\Validation\StatusChecker;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\DrupalKernel;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Base class for Automatic Updates console commands that boot Drupal.
*
* @internal
* This is an internal part of Automatic Updates and may be changed or removed
* at any time without warning. External code should not interact with this
* class.
*/
abstract class AutomaticUpdatesCommandBase extends Command {
/**
* The I/O handler.
*
* @var \Symfony\Component\Console\Style\SymfonyStyle
*/
protected SymfonyStyle $io;
/**
* The Drupal service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected ContainerInterface $container;
/**
* The console update stage.
*
* @var \Drupal\automatic_updates\ConsoleUpdateSandboxManager
*/
protected ConsoleUpdateSandboxManager $sandboxManager;
/**
* Constructs an AutomaticUpdatesCommandBase object.
*
* @param object $autoloader
* The autoloader, passed by reference so it can be decorated during
* Drupal's bootstrap process.
*/
public function __construct(private object &$autoloader) {
parent::__construct();
}
/**
* {@inheritdoc}
*/
protected function configure(): void {
parent::configure();
$this->addOption('uri', mode: InputOption::VALUE_REQUIRED, description: 'The URI of the Drupal site, e.g. https://example.com or https://example.com/mysite.', default: 'https://default');
$this->addOption('is-from-web', mode: InputOption::VALUE_NONE, description: 'This option is for internal use only and should not be passed.');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
if (function_exists('posix_getuid') && posix_getuid() === 0) {
throw new \DomainException('For security reasons, this command cannot be run as the superuser (root).');
}
$this->io = new SymfonyStyle($input, $output);
// Detect the Drupal application root based on the location of the \Drupal
// class.
$drupal_root = dirname((new \ReflectionClass('\Drupal'))->getFileName(), 3);
// We need to be in the Drupal root for everything to boot up properly.
chdir($drupal_root);
$uri = $input->getOption('uri');
// If the --uri option did not include a scheme, prepend one.
if (parse_url($uri, PHP_URL_SCHEME) === NULL) {
$uri = 'https://' . $uri;
}
$base_path = parse_url($uri, PHP_URL_PATH) ?? '/';
// Ensure the SCRIPT_FILENAME and SCRIPT_NAME variables are accurate so that
// Drupal can generate URLs correctly.
$request = Request::create($uri, server: [
'SCRIPT_FILENAME' => $drupal_root . '/index.php',
'SCRIPT_NAME' => $base_path . 'index.php',
]);
$kernel = DrupalKernel::createFromRequest($request, $this->autoloader, 'prod', app_root: $drupal_root)
->boot();
$kernel->preHandle($request);
$this->container = $kernel->getContainer();
$this->sandboxManager = $this->container->get(ConsoleUpdateSandboxManager::class);
$this->sandboxManager->output = $output;
$this->sandboxManager->isFromWeb = $input->getOption('is-from-web');
return static::SUCCESS;
}
/**
* Runs status checks, and sends failure notifications if necessary.
*/
protected function runStatusChecks(): void {
assert($this->container instanceof ContainerInterface, 'Drupal is not booted.');
/** @var \Drupal\Component\Datetime\TimeInterface $time */
$time = $this->container->get(TimeInterface::class);
/** @var \Drupal\automatic_updates\Validation\StatusChecker $status_checker */
$status_checker = $this->container->get(StatusChecker::class);
$last_results = $status_checker->getResults();
$last_run_time = $status_checker->getLastRunTime();
// Do not run status checks more than once an hour unless there are no
// results available.
$needs_run = $last_results === NULL || !$last_run_time || $time->getRequestTime() - $last_run_time > 3600;
$settings = $this->container->get('config.factory')
->get('automatic_updates.settings')
->get('unattended');
// To ensure consistent results, only run the status checks if we're
// explicitly configured to do unattended updates on the command line.
if ($needs_run && (($settings['method'] === 'web' && $this->sandboxManager->isFromWeb) || $settings['method'] === 'console')) {
// Only send failure notifications if unattended updates are enabled.
if ($settings['level'] !== CronUpdateRunner::DISABLED) {
$this->container->get(StatusCheckMailer::class)
->sendFailureNotifications($last_results, $status_checker->run()->getResults());
}
}
}
}
