Unverified Commit c7edcf2f authored by Alex Pott's avatar Alex Pott
Browse files

feat: #1503146 Aliased paths cannot be set as front page

By: pendashteh
By: gambry
By: raphaeltbm
By: dieterholvoet
By: damienmckenna
By: rodrigoaguilera
By: neclimdul
By: anruether
By: amateescu
By: joachim
By: catch
By: phenaproxima
By: godotislate
By: mstrelan
By: alexpott
By: longwave
(cherry picked from commit 33db3097)
parent 5b9e9153
Loading
Loading
Loading
Loading
Loading
+0 −6
Original line number Diff line number Diff line
@@ -29761,12 +29761,6 @@
	'count' => 1,
	'path' => __DIR__ . '/modules/system/src/Form/SiteInformationForm.php',
];
$ignoreErrors[] = [
	'message' => '#^Method Drupal\\\\system\\\\Form\\\\SiteInformationForm\\:\\:submitForm\\(\\) has no return type specified\\.$#',
	'identifier' => 'missingType.return',
	'count' => 1,
	'path' => __DIR__ . '/modules/system/src/Form/SiteInformationForm.php',
];
$ignoreErrors[] = [
	'message' => '#^Method Drupal\\\\system\\\\Form\\\\SiteInformationForm\\:\\:validateForm\\(\\) has no return type specified\\.$#',
	'identifier' => 'missingType.return',
+4 −0
Original line number Diff line number Diff line
@@ -34,3 +34,7 @@ services:
      - { name: needs_destruction }
    arguments: [path_alias_prefix_list, '@cache.bootstrap', '@lock', '@state', '@path_alias.repository']
  Drupal\path_alias\AliasPrefixListInterface: '@path_alias.prefix_list'
  Drupal\path_alias\AliasPathMatcher:
    public: false
    autowire: true
    decorates: path.matcher
+64 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\path_alias;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;

/**
 * Extends the default path matcher to check aliases.
 */
class AliasPathMatcher implements PathMatcherInterface {

  /**
   * Whether the current page is the front page.
   */
  protected ?bool $isCurrentFrontPage = NULL;

  public function __construct(
    #[AutowireDecorated]
    protected PathMatcherInterface $decorated,
    protected RouteMatchInterface $routeMatch,
    protected AliasManagerInterface $aliasManager,
    protected ConfigFactoryInterface $configFactory,
  ) {}

  /**
   * {@inheritdoc}
   */
  public function matchPath($path, $patterns) {
    return $this->decorated->matchPath($path, $patterns);
  }

  /**
   * {@inheritdoc}
   */
  public function isFrontPage() {
    // Cache the result as this is called often.
    $this->isCurrentFrontPage ??= $this->decorated->isFrontPage() || $this->isAliasFrontPage();
    return $this->isCurrentFrontPage;
  }

  /**
   * Checks if the current page is the front page by comparing aliases.
   */
  protected function isAliasFrontPage(): bool {
    // Ensure that the code can also be executed when there is no active
    // route match, like on exception responses.
    if (!$this->routeMatch->getRouteName()) {
      return FALSE;
    }

    $url = Url::fromRouteMatch($this->routeMatch);
    $path = '/' . $url->getInternalPath();
    $frontPagePath = $this->configFactory
      ->get('system.site')
      ->get('page.front');

    return $this->aliasManager->getAliasByPath($path) === $frontPagePath;
  }

}
+4 −44
Original line number Diff line number Diff line
@@ -7,9 +7,9 @@
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\ConfigTarget;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\RedundantEditableConfigNamesTrait;
use Drupal\Core\Path\PathValidatorInterface;
use Drupal\Core\Routing\RequestContext;
use Drupal\path_alias\AliasManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
@@ -18,13 +18,7 @@
 * @internal
 */
class SiteInformationForm extends ConfigFormBase {

  /**
   * The path alias manager.
   *
   * @var \Drupal\path_alias\AliasManagerInterface
   */
  protected $aliasManager;
  use RedundantEditableConfigNamesTrait;

  /**
   * The path validator.
@@ -47,16 +41,13 @@ class SiteInformationForm extends ConfigFormBase {
   *   The factory for configuration objects.
   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
   *   The typed config manager.
   * @param \Drupal\path_alias\AliasManagerInterface $alias_manager
   *   The path alias manager.
   * @param \Drupal\Core\Path\PathValidatorInterface $path_validator
   *   The path validator.
   * @param \Drupal\Core\Routing\RequestContext $request_context
   *   The request context.
   */
  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, AliasManagerInterface $alias_manager, PathValidatorInterface $path_validator, RequestContext $request_context) {
  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, PathValidatorInterface $path_validator, RequestContext $request_context) {
    parent::__construct($config_factory, $typedConfigManager);
    $this->aliasManager = $alias_manager;
    $this->pathValidator = $path_validator;
    $this->requestContext = $request_context;
  }
@@ -68,7 +59,6 @@ public static function create(ContainerInterface $container) {
    return new static(
      $container->get('config.factory'),
      $container->get('config.typed'),
      $container->get('path_alias.manager'),
      $container->get('path.validator'),
      $container->get('router.request_context')
    );
@@ -81,19 +71,10 @@ public function getFormId() {
    return 'system_site_information_settings';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return ['system.site'];
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $site_config = $this->config('system.site');

    $form['site_information'] = [
      '#type' => 'details',
      '#title' => $this->t('Site details'),
@@ -128,11 +109,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
      '#title' => $this->t('Front page'),
      '#open' => TRUE,
    ];
    $frontPage = $site_config->get('page.front');
    $form['front_page']['site_frontpage'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Default front page'),
      '#default_value' => $frontPage ? $this->aliasManager->getAliasByPath($frontPage) : '',
      '#config_target' => 'system.site:page.front',
      '#required' => TRUE,
      '#size' => 40,
      '#description' => $this->t('Specify a relative URL to display as the front page.'),
@@ -165,23 +145,14 @@ public function buildForm(array $form, FormStateInterface $form_state) {
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    // Get the normal path of the front page.
    $form_state->setValueForElement($form['front_page']['site_frontpage'], $this->aliasManager->getPathByAlias($form_state->getValue('site_frontpage')));
    // Validate front page path.
    if (($value = $form_state->getValue('site_frontpage')) && $value[0] !== '/') {
      $form_state->setErrorByName('site_frontpage', $this->t("The path '%path' has to start with a slash.", ['%path' => $form_state->getValue('site_frontpage')]));

    }
    if (!$this->pathValidator->isValid($form_state->getValue('site_frontpage'))) {
      $form_state->setErrorByName('site_frontpage', $this->t("Either the path '%path' is invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_frontpage')]));
    }
    // Get the normal paths of both error pages.
    if (!$form_state->isValueEmpty('site_403')) {
      $form_state->setValueForElement($form['error_page']['site_403'], $this->aliasManager->getPathByAlias($form_state->getValue('site_403')));
    }
    if (!$form_state->isValueEmpty('site_404')) {
      $form_state->setValueForElement($form['error_page']['site_404'], $this->aliasManager->getPathByAlias($form_state->getValue('site_404')));
    }
    if (($value = $form_state->getValue('site_403')) && $value[0] !== '/') {
      $form_state->setErrorByName('site_403', $this->t("The path '%path' has to start with a slash.", ['%path' => $form_state->getValue('site_403')]));
    }
@@ -200,15 +171,4 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $this->config('system.site')
      ->set('page.front', $form_state->getValue('site_frontpage'))
      ->save();

    parent::submitForm($form, $form_state);
  }

}
+17 −1
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ class AccessDeniedTest extends BrowserTestBase {
  /**
   * {@inheritdoc}
   */
  protected static $modules = ['block', 'node', 'system_test'];
  protected static $modules = ['block', 'node', 'path', 'system_test'];

  /**
   * {@inheritdoc}
@@ -155,6 +155,22 @@ public function testAccessDeniedCustomPageWithAccessDenied(): void {
    $this->assertSession()->statusCodeEquals(403);
    // Verify the access cacheability metadata for custom 403 is bubbled.
    $this->assertCacheContext('user.roles');

    // Create a custom 403 page with a path alias.
    $this->drupalCreateContentType(['type' => 'page']);
    $this->drupalCreateNode([
      'body' => "Sorry Dave, I'm afraid you can't do that.",
      'path' => '/custom-access-denied',
    ]);
    $edit = ['site_403' => '/custom-access-denied'];
    $this->drupalGet('admin/config/system/site-information');
    $this->submitForm($edit, 'Save configuration');
    $assert_session = $this->assertSession();
    $assert_session->statusMessageContains('The configuration options have been saved.');
    $this->assertSame('/custom-access-denied', $this->config('system.site')->get('page.403'));
    $this->drupalGet('/system-test/always-denied');
    $assert_session->statusCodeEquals(403);
    $assert_session->pageTextContains("Sorry Dave, I'm afraid you can't do that.");
  }

}
Loading