Initial Drupal 11 with DDEV setup

This commit is contained in:
gluebox
2025-10-08 11:39:17 -04:00
commit 89ef74b305
25344 changed files with 2599172 additions and 0 deletions

View File

@ -0,0 +1,50 @@
<?php
namespace Drupal\system\EventSubscriber;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Alters routes to add necessary requirements.
*
* @see \Drupal\system\Access\SystemAdminMenuBlockAccessCheck
* @see \Drupal\system\Controller\SystemController::systemAdminMenuBlockPage()
*/
class AccessRouteAlterSubscriber implements EventSubscriberInterface {
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events[RoutingEvents::ALTER][] = 'accessAdminMenuBlockPage';
return $events;
}
/**
* Adds requirements to some System Controller routes.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The event to process.
*/
public function accessAdminMenuBlockPage(RouteBuildEvent $event) {
$routes = $event->getRouteCollection();
foreach ($routes as $route) {
// Do not use a leading slash when comparing to the _controller string
// because the leading slash in a fully-qualified method name is optional.
if ($route->hasDefault('_controller')) {
switch (ltrim($route->getDefault('_controller'), '\\')) {
case 'Drupal\system\Controller\SystemController::systemAdminMenuBlockPage':
$route->setRequirement('_access_admin_menu_block_page', 'TRUE');
break;
case 'Drupal\system\Controller\SystemController::overview':
$route->setRequirement('_access_admin_overview_page', 'TRUE');
break;
}
}
}
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Drupal\system\EventSubscriber;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Adds the _admin_route option to each admin HTML route.
*/
class AdminRouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
foreach ($collection->all() as $route) {
$path = $route->getPath();
if (($path == '/admin' || str_starts_with($path, '/admin/')) && !$route->hasOption('_admin_route') && static::isHtmlRoute($route)) {
$route->setOption('_admin_route', TRUE);
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events = parent::getSubscribedEvents();
// Use a lower priority than \Drupal\field_ui\Routing\RouteSubscriber or
// \Drupal\views\EventSubscriber\RouteSubscriber to ensure we add the option
// to their routes.
$events[RoutingEvents::ALTER] = ['onAlterRoutes', -200];
return $events;
}
/**
* Determines whether the given route is an HTML route.
*
* @param \Symfony\Component\Routing\Route $route
* The route to analyze.
*
* @return bool
* TRUE if HTML is a valid format for this route.
*/
protected static function isHtmlRoute(Route $route) {
// If a route has no explicit format, then HTML is valid.
$format = $route->hasRequirement('_format') ? explode('|', $route->getRequirement('_format')) : ['html'];
return in_array('html', $format, TRUE);
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace Drupal\system\EventSubscriber;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Defines a config subscriber for changes to 'system.advisories'.
*/
class AdvisoriesConfigSubscriber implements EventSubscriberInterface {
/**
* The security advisory fetcher service.
*
* @var \Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher
*/
protected $securityAdvisoriesFetcher;
/**
* Constructs a new ConfigSubscriber object.
*
* @param \Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher $security_advisories_fetcher
* The security advisory fetcher service.
*/
public function __construct(SecurityAdvisoriesFetcher $security_advisories_fetcher) {
$this->securityAdvisoriesFetcher = $security_advisories_fetcher;
}
/**
* Deletes the stored response from the security advisories feed, if needed.
*
* The stored response will only be deleted if the 'interval_hours' config
* setting is reduced from the previous value.
*
* @param \Drupal\Core\Config\ConfigCrudEvent $event
* The configuration event.
*/
public function onConfigSave(ConfigCrudEvent $event): void {
$saved_config = $event->getConfig();
if ($saved_config->getName() === 'system.advisories' && $event->isChanged('interval_hours')) {
$original_interval = $saved_config->getOriginal('interval_hours');
if ($original_interval && $saved_config->get('interval_hours') < $original_interval) {
// If the new interval is less than the original interval, delete the
// stored results.
$this->securityAdvisoriesFetcher->deleteStoredResponse();
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events[ConfigEvents::SAVE][] = ['onConfigSave'];
return $events;
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace Drupal\system\EventSubscriber;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Theme\Registry;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* A subscriber invalidating cache tags when system config objects are saved.
*/
class ConfigCacheTag implements EventSubscriberInterface {
/**
* Constructs a ConfigCacheTag object.
*/
public function __construct(
protected ThemeHandlerInterface $themeHandler,
protected CacheTagsInvalidatorInterface $cacheTagsInvalidator,
protected Registry $themeRegistry,
) {
}
/**
* Invalidate cache tags when particular system config objects are saved.
*
* @param \Drupal\Core\Config\ConfigCrudEvent $event
* The Event to process.
*/
public function onSave(ConfigCrudEvent $event) {
$config_name = $event->getConfig()->getName();
// Changing the site settings may mean a different route is selected for the
// front page. Additionally a change to the site name or similar must
// invalidate the render cache since this could be used anywhere.
if ($config_name === 'system.site') {
$this->cacheTagsInvalidator->invalidateTags(['route_match', 'rendered']);
}
// Theme configuration and global theme settings.
if (in_array($config_name, ['system.theme', 'system.theme.global'], TRUE)) {
$this->cacheTagsInvalidator->invalidateTags(['rendered']);
}
// Library and template overrides potentially change for the default theme
// when the admin theme is changed.
if ($config_name === 'system.theme' && $event->isChanged('admin')) {
$this->themeRegistry->reset();
$this->cacheTagsInvalidator->invalidateTags(['library_info']);
}
// Theme-specific settings, check if this matches a theme settings
// configuration object (THEME_NAME.settings), in that case, clear the
// rendered cache tag.
if (preg_match('/^([^\.]*)\.settings$/', $config_name, $matches)) {
if ($this->themeHandler->themeExists($matches[1])) {
$this->cacheTagsInvalidator->invalidateTags(['rendered']);
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events[ConfigEvents::SAVE][] = ['onSave'];
return $events;
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace Drupal\system\EventSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\File\Event\FileUploadSanitizeNameEvent;
use Drupal\Core\File\FileSystemInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* The final subscriber to 'file.upload.sanitize.name'.
*
* This prevents insecure filenames.
*/
class SecurityFileUploadEventSubscriber implements EventSubscriberInterface {
/**
* Constructs a new file event listener.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The config factory.
*/
public function __construct(
protected ConfigFactoryInterface $configFactory,
) {}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
// This event must be run last to ensure the filename obeys the security
// rules.
$events[FileUploadSanitizeNameEvent::class][] = [
'sanitizeName',
PHP_INT_MIN,
];
return $events;
}
/**
* Sanitizes the upload's filename to make it secure.
*
* @param \Drupal\Core\File\Event\FileUploadSanitizeNameEvent $event
* File upload sanitize name event.
*/
public function sanitizeName(FileUploadSanitizeNameEvent $event): void {
$filename = $event->getFilename();
// Dot files are renamed regardless of security settings.
$filename = trim($filename, '.');
// Remove any null bytes. See
// http://php.net/manual/security.filesystem.nullbytes.php
$filename = str_replace(chr(0), '', $filename);
// Split up the filename by periods. The first part becomes the basename,
// the last part the final extension.
$filename_parts = explode('.', $filename);
// Remove file basename.
$filename = array_shift($filename_parts);
// Remove final extension.
$final_extension = (string) array_pop($filename_parts);
// Check if we're dealing with a dot file that is also an insecure extension
// e.g. .htaccess. In this scenario there is only one 'part' and the
// extension becomes the filename. We use the original filename from the
// event rather than the trimmed version above.
$insecure_uploads = $this->configFactory->get('system.file')->get('allow_insecure_uploads');
if (!$insecure_uploads && $final_extension === '' && str_contains($event->getFilename(), '.') && in_array(strtolower($filename), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) {
$final_extension = $filename;
$filename = '';
}
$extensions = $event->getAllowedExtensions();
if (!empty($extensions) && !in_array(strtolower($final_extension), $extensions, TRUE)) {
// This upload will be rejected by FileExtension constraint anyway so do
// not make any alterations to the filename. This prevents a file named
// 'example.php' being renamed to 'example.php_.txt' and uploaded if the
// .txt extension is allowed but .php is not. It is the responsibility of
// the function that dispatched the event to ensure
// FileValidator::validate() is called with 'FileExtension' in the list of
// validators if $extensions is not empty.
return;
}
if (!$insecure_uploads && in_array(strtolower($final_extension), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) {
if (empty($extensions) || in_array('txt', $extensions, TRUE)) {
// Add .txt to potentially executable files prior to munging to help
// prevent exploits. This results in a filenames like filename.php being
// changed to filename.php.txt prior to munging.
$filename_parts[] = $final_extension;
$final_extension = 'txt';
}
else {
// Since .txt is not an allowed extension do not rename the file. The
// file will be rejected by FileValidator::validate().
return;
}
}
// If there are any insecure extensions in the filename munge all the
// internal extensions.
$munge_everything = !empty(array_intersect(array_map('strtolower', $filename_parts), FileSystemInterface::INSECURE_EXTENSIONS));
// Munge the filename to protect against possible malicious extension hiding
// within an unknown file type (i.e. filename.html.foo). This was introduced
// as part of SA-2006-006 to fix Apache's risky fallback behavior.
// Loop through the middle parts of the name and add an underscore to the
// end of each section that could be a file extension but isn't in the
// list of allowed extensions.
foreach ($filename_parts as $filename_part) {
$filename .= '.' . $filename_part;
if ($munge_everything) {
$filename .= '_';
}
elseif (!empty($extensions) && !in_array(strtolower($filename_part), $extensions) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
$filename .= '_';
}
}
if ($final_extension !== '') {
$filename .= '.' . $final_extension;
}
if ($filename !== $event->getFilename()) {
$event->setFilename($filename)->setSecurityRename();
}
}
}