monster_menus-9.0.x-dev/src/Routing/RouteSubscriber.php

src/Routing/RouteSubscriber.php
<?php

/**
 * @file
 * Contains \Drupal\monster_menus\Routing\RouteSubscriber.
 */

namespace Drupal\monster_menus\Routing;

use Drupal\Core\Database\Database;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\monster_menus\Constants;
use Symfony\Component\Routing\RouteCollection;
use Drupal\Core\Routing\RoutingEvents;
use Drupal\Core\Url;
use Drupal\Component\Serialization\Yaml;

/**
 * Listens to dynamic route events. Alters existing menu routes to include
 * special handing for MM.
 */
class RouteSubscriber extends RouteSubscriberBase {

  use MessengerTrait;

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    $events[RoutingEvents::ALTER] = ['onAlterRoutes', -9999];  // negative value means "late"
    return $events;
  }

  /**
   * {@inheritdoc}
   */
  public function alterRoutes(RouteCollection $collection) {
    mm_module_invoke_all_array('mm_routing_alter', [&$collection]);

    // Alter routes based on transformations found in ::alterations().
    $alterations = Yaml::decode($this->alterations());
    foreach ($alterations as $name => $changes) {
      if ($route = $collection->get($name)) {
        // Read current options and hold for future alterations.
        $options = $route->getOptions();
        if (isset($changes['options'])) {
          foreach (array_keys($options) as $key) {
            if ($key[0] != '_') {
              unset($options[$key]);
            }
          }
        }

        // Alter defaults.
        if (isset($changes['defaults'])) {
          $route->setDefaults($changes['defaults']);
        }

        // Alter requirements.
        $bare_path = $route->getPath();
        if (isset($changes['requirements'])) {
          if (str_contains($bare_path, '{node}')) {
            $changes['requirements']['node'] = '\d+';
          }
          $route->setRequirements($changes['requirements']);
          $route->setMethods(['GET', 'POST']);
        }

        // Alter options.
        if (isset($changes['options'])) {
          $options = $changes['options'] + $options;
        }

        // Alter path, adding MM's prefix.
        $route->setPath('/mm/{mm_tree}' . $bare_path);
        // Add parameter converters, as needed.
        $params = $options['parameters'] ?? [];
        $params['mm_tree'] = ['type' => 'entity:mm_tree', 'converter' => 'paramconverter.entity'];
        if (str_contains($bare_path, '{node}') && !isset($params['node'])) {
          $params['node'] = ['type' => 'entity:node', 'converter' => 'paramconverter.entity'];
          $route->setRequirement('node', '\d+');
        }
        if (str_contains($bare_path, '{node_revision}') && !isset($params['node_revision'])) {
          $params['node_revision'] = ['type' => 'entity_revision:node', 'converter' => 'paramconverter.entity_revision'];
          $route->setRequirement('node_revision', '\d+');
        }
        $options['parameters'] = $params;

        // Set final options.
        $route->setOptions($options);
        // Set the parameter requirement for {mm_tree}.
        $route->setRequirement('mm_tree', '-?\d+');
      }
    }

    // Alter remaining routes which contain {node}, prefixing them with
    // "/mm/{mm_tree}".
    foreach ($collection->all() as $name => $route) {
      // Make sure this isn't already one of our routes.
      // Skip the path if the requirement "_not_mm_path" is set.
      if (strncmp($name, 'monster_menus.', 14) && !$route->getRequirement('_not_mm_path')) {
        $route_path = $route->getPath();
        if (!isset($alterations[$name]) && str_contains($route_path, '{node}') && !str_starts_with($route_path, '/mm/{mm_tree}') && !$this->noMMPrefix($route_path)) {
          $route->setPath('/mm/{mm_tree}' . $route_path);
          $route->setOption('_admin_route', FALSE);
        }
      }
    }

    // Modify some built-in routes to point to MM's equivalent.
    $aliases = [
      'node.add_page' => 'monster_menus.add_node',
      'node.add' => 'monster_menus.add_node_with_type',
    ];
    foreach ($aliases as $from => $to) {
      if ($collection->get($from) && ($to_route = $collection->get($to))) {
        $collection->remove($from);
        $collection->add($from, clone $to_route);
      }
    }

    // Generate the list of keywords that are not allowed in URL aliases, and
    // give an error message if there already is something in mm_tree using one
    // of the menu keywords.
    $checked = $reserved_alias = $top_level_reserved = [];
    foreach ($collection->all() as $name => $route) {
      // Remove leading or trailing slashes, then squish any multiple slashes in
      // a row.
      $path = $route->getPath();
      $elems = explode('/', preg_replace('{//+}', '/', trim($path, '/')));

      if ($elems[0] && $elems[0][0] != '{' && $elems[0][0] != '<') {
        $top_level_reserved[$elems[0]] = TRUE;
      }

      if (count($elems) >= 3 && $elems[0] == 'mm' && $elems[1] == '{mm_tree}') {
        $failed_elems = [];
        for ($i = 2; $i < count($elems); $i++) {
          // Only reserve the first non-token after mm/{mm_tree}
          if ($elems[$i][0] != '{') {
            if (empty($reserved_alias[$elems[$i]])) {
              if (!isset($checked[$elems[$i]])) {
                $checked[$elems[$i]] = mm_content_get(['alias' => $elems[$i]], [], 10);
              }

              if (!empty($checked[$elems[$i]])) {
                $failed_elems[] = $elems[$i];
              }
              $reserved_alias[$elems[$i]] = TRUE;
            }
            break;
          }
        }

        foreach ($failed_elems as $elem) {
          [$error, $list] = $this->addErrors($checked[$elem], 'The menu entry %entry contains the element %element. This conflicts with the URL names that are already assigned to these MM pages:<br />');
          $error .= '<br />The menu entry has been disabled. You must change the URL name(s) and rebuild the menus.';
          $err_arr = array_merge([
            '%entry' => $path,
            '%element' => $elem,
          ], $list);
          if (\Drupal::currentUser()->hasPermission('administer all menus')) {
            $this->messenger()->addError(t($error, $err_arr));
          }
          \Drupal::logger('mm')->error($error, $err_arr);
          $collection->remove($name);
        }
      }
    }
    \Drupal::state()->set('monster_menus.reserved_alias', array_merge(array_keys($reserved_alias), mm_content_reserved_aliases_base()));
    \Drupal::state()->set('monster_menus.top_level_reserved', array_keys($top_level_reserved));

    // Emit an error message if there already is something in mm_tree that would
    // match one of the system menu entries.
    $db = Database::getConnection();
    $found = [];
    // Skip /mm/{mm_tree}...
    $not_found = ['/mm/{mm_tree}' => 1];
    foreach ($collection->all() as $name => $route) {
      // Remove leading or trailing slashes, then squish any multiple slashes in
      // a row.
      $path = $route->getPath();
      if ($path[0] == '<') {
        continue;
      }
      $elems = explode('/', preg_replace('{//+}', '/', trim($path, '/')));
      $test_path = '';
      $query = $where = '';
      $args = [':par0' => mm_home_mmtid()];
      foreach ($elems as $depth => $elem) {
        $test_path .= '/' . $elem;
        if (!empty($not_found[$test_path]) || !$elem) {
          break;
        }
        $prev = $depth - 1;
        if ($elem[0] == '{') {
          $query .= " INNER JOIN {mm_tree} t$depth ON t$depth.parent = t$prev.mmtid";
        }
        else {
          $args[":a$depth"] = $elem;
          if ($query) {
            $query .= " INNER JOIN {mm_tree} t$depth ON t$depth.parent = t$prev.mmtid";
            $where .= " AND t$depth.alias = :a$depth";
          }
          else {
            $query = "FROM {mm_tree} t$depth";
            $where = "WHERE t$depth.parent = :par0 AND t$depth.alias = :a$depth";
          }
        }

        $is_last = $depth == count($elems) - 1;
        if (empty($found[$test_path]) || $is_last) {
          $result = $db->queryRange("SELECT t$depth.mmtid $query $where", 0, 10, $args)->fetchCol();
          if ($result) {
            if ($is_last) {
              [$error, $list] = $this->addErrors($result, 'The menu entry %entry conflicts with these MM pages:<br />');
              $error .= '<br />The menu entry has been disabled. Change either the URL name(s) or the menu path and rebuild the menus.';
              $err_arr = array_merge(['%entry' => $path], $list);
              if (\Drupal::currentUser()->hasPermission('administer all menus')) {
                $this->messenger()->addError(t($error, $err_arr));
              }
              \Drupal::logger('mm')->error($error, $err_arr);
              $collection->remove($name);
            }
            $found[$test_path] = 1;
          }
          else {
            $not_found[$test_path] = 1;
            break;
          }
        }
      }
    }

    // Regenerate the list of MM tree entry names to hide from non-admin users
    $hidden_names = [Constants::MM_ENTRY_NAME_DEFAULT_USER, Constants::MM_ENTRY_NAME_DISABLED_USER];
    $hidden_names = array_merge($hidden_names, mm_module_invoke_all('mm_hidden_user_names'));
    \Drupal::state()->set('monster_menus.hidden_user_names', $hidden_names);

    // Find the path of the contextual.render route, for processing in
    // mm_active_menu_item().
    $path = '';
    if ($route = $collection->get('contextual.render')) {
      $path = $route->getPath();
    }
    \Drupal::state()->set('monster_menus.contextual_render_path', $path);

    // Regenerate the custom page display list
    _mm_showpage_router(TRUE);
  }

  private function addErrors($pages, $error) {
    $list = [];
    foreach ($pages as $index => $tree) {
      $error .= '<a href=":link' . $index . '">@title' . $index . '</a>' . ($index > 0 ? '<br />' : '');
      $list['@title' . $index] = '&nbsp;&nbsp;' . mm_content_get_name($tree);
      $list[':link' . $index] = Url::fromRoute('monster_menus.handle_page_settings', ['mm_tree' => is_object($tree) ? $tree->mmtid : $tree])->toString();
    }
    return [$error, $list];
  }

  private function noMMPrefix($path) {
    $skips = ['/history/' => 9];
    foreach ($skips as $prefix => $len) {
      if (!strncmp($path, $prefix, $len)) {
        return TRUE;
      }
    }
    return FALSE;
  }

  private function alterations() {
    return <<<YAML
entity.node.edit_form:
  defaults:
    _title_callback: '\Drupal\monster_menus\Controller\DefaultController::editNodeGetTitle'
    _controller: '\Drupal\monster_menus\Controller\DefaultController::editNode'
  options:
    _admin_route: FALSE
entity.node.delete_form:
  defaults:
    _title_callback: '\Drupal\monster_menus\Form\DeleteNodeConfirmForm::getMenuTitle'
    _form: \Drupal\monster_menus\Form\DeleteNodeConfirmForm
  requirements:
    _custom_access: '\Drupal\monster_menus\Form\DeleteNodeConfirmForm::access'
  options:
    _admin_route: FALSE
entity.node.preview: {}
entity.node.version_history:
  defaults:
    _title: Revisions
    _controller: '\Drupal\monster_menus\Controller\NodeRevisionsController::revisionOverview'
  requirements:
    _custom_access: '\Drupal\monster_menus\Controller\NodeRevisionsController::menuAccessNodeRevisions'
  options:
    _admin_route: FALSE
node.revision_revert_confirm:
  defaults:
    _title: 'Revert to earlier revision'
    _controller: '\Drupal\monster_menus\Controller\NodeRevisionsController::revisionRevertConfirm'
  requirements:
    _custom_access: '\Drupal\monster_menus\Controller\NodeRevisionsController::menuAccessNodeRevisions'
  options:
    _admin_route: FALSE
node.revision_delete_confirm:
  defaults:
    op: delete
    _title: 'Delete earlier revision'
    _controller: '\Drupal\monster_menus\Controller\NodeRevisionsController::revisionDeleteConfirm'
  requirements:
    _custom_access: '\Drupal\monster_menus\Controller\NodeRevisionsController::menuAccessNodeRevisions'
  options:
    _admin_route: FALSE
entity.node.revision:
  defaults:
    _title_callback: '\Drupal\\node\Controller\NodeController::revisionPageTitle'
    _controller: '\Drupal\monster_menus\Controller\NodeRevisionsController::revisionShow'
  requirements:
    _custom_access: '\Drupal\monster_menus\Controller\NodeRevisionsController::menuAccessNodeRevisions'
diff.revisions_diff:
  defaults:
    op: compare
    _title: 'Compare revisions'
    _controller: '\Drupal\monster_menus\Controller\NodeRevisionsController::compareRevisions'
  requirements:
    _custom_access: '\Drupal\monster_menus\Controller\NodeRevisionsController::menuAccessNodeRevisions'
entity.comment.canonical: {}
entity.comment.delete_form:
  defaults:
    _title: 'Delete comment'
    _entity_form: 'comment.delete'
entity.comment.edit_form:
  defaults:
    _title: 'Edit comment'
    _entity_form: 'comment.default'
comment.reply:
  defaults:
    _title: 'Reply to comment'
    _controller: '\Drupal\comment\Controller\CommentController::getReplyForm'
    _entity_form: 'comment.default'
    pid: null
YAML;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc