"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.9/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php" (18 Nov 2020, 9543 Bytes) of package /linux/www/drupal-8.9.9.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "DefaultMenuLinkTreeManipulators.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 namespace Drupal\Core\Menu;
    4 
    5 use Drupal\Core\Access\AccessManagerInterface;
    6 use Drupal\Core\Access\AccessResult;
    7 use Drupal\Core\Entity\EntityTypeManagerInterface;
    8 use Drupal\Core\Session\AccountInterface;
    9 use Drupal\node\NodeInterface;
   10 
   11 /**
   12  * Provides a couple of menu link tree manipulators.
   13  *
   14  * This class provides menu link tree manipulators to:
   15  * - perform render cached menu-optimized access checking
   16  * - optimized node access checking
   17  * - generate a unique index for the elements in a tree and sorting by it
   18  * - flatten a tree (i.e. a 1-dimensional tree)
   19  */
   20 class DefaultMenuLinkTreeManipulators {
   21 
   22   /**
   23    * The access manager.
   24    *
   25    * @var \Drupal\Core\Access\AccessManagerInterface
   26    */
   27   protected $accessManager;
   28 
   29   /**
   30    * The current user.
   31    *
   32    * @var \Drupal\Core\Session\AccountInterface
   33    */
   34   protected $account;
   35 
   36   /**
   37    * The entity type manager.
   38    *
   39    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   40    */
   41   protected $entityTypeManager;
   42 
   43   /**
   44    * Constructs a \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators object.
   45    *
   46    * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
   47    *   The access manager.
   48    * @param \Drupal\Core\Session\AccountInterface $account
   49    *   The current user.
   50    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   51    *   The entity type manager.
   52    */
   53   public function __construct(AccessManagerInterface $access_manager, AccountInterface $account, EntityTypeManagerInterface $entity_type_manager) {
   54     $this->accessManager = $access_manager;
   55     $this->account = $account;
   56     $this->entityTypeManager = $entity_type_manager;
   57   }
   58 
   59   /**
   60    * Performs access checks of a menu tree.
   61    *
   62    * Sets the 'access' property to AccessResultInterface objects on menu link
   63    * tree elements. Descends into subtrees if the root of the subtree is
   64    * accessible. Inaccessible subtrees are deleted, except the top-level
   65    * inaccessible link, to be compatible with render caching.
   66    *
   67    * (This means that top-level inaccessible links are *not* removed; it is up
   68    * to the code doing something with the tree to exclude inaccessible links,
   69    * just like MenuLinkTree::build() does. This allows those things to specify
   70    * the necessary cacheability metadata.)
   71    *
   72    * This is compatible with render caching, because of cache context bubbling:
   73    * conditionally defined cache contexts (i.e. subtrees that are only
   74    * accessible to some users) will bubble just like they do for render arrays.
   75    * This is why inaccessible subtrees are deleted, except at the top-level
   76    * inaccessible link: if we didn't keep the first (depth-wise) inaccessible
   77    * link, we wouldn't be able to know which cache contexts would cause those
   78    * subtrees to become accessible again, thus forcing us to conclude that the
   79    * subtree is unconditionally inaccessible.
   80    *
   81    * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
   82    *   The menu link tree to manipulate.
   83    *
   84    * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
   85    *   The manipulated menu link tree.
   86    */
   87   public function checkAccess(array $tree) {
   88     foreach ($tree as $key => $element) {
   89       // Other menu tree manipulators may already have calculated access, do not
   90       // overwrite the existing value in that case.
   91       if (!isset($element->access)) {
   92         $tree[$key]->access = $this->menuLinkCheckAccess($element->link);
   93       }
   94       if ($tree[$key]->access->isAllowed()) {
   95         if ($tree[$key]->subtree) {
   96           $tree[$key]->subtree = $this->checkAccess($tree[$key]->subtree);
   97         }
   98       }
   99       else {
  100         // Replace the link with an InaccessibleMenuLink object, so that if it
  101         // is accidentally rendered, no sensitive information is divulged.
  102         $tree[$key]->link = new InaccessibleMenuLink($tree[$key]->link);
  103         // Always keep top-level inaccessible links: their cacheability metadata
  104         // that indicates why they're not accessible by the current user must be
  105         // bubbled. Otherwise, those subtrees will not be varied by any cache
  106         // contexts at all, therefore forcing them to remain empty for all users
  107         // unless some other part of the menu link tree accidentally varies by
  108         // the same cache contexts.
  109         // For deeper levels, we *can* remove the subtrees and therefore also
  110         // not perform access checking on the subtree, thanks to bubbling/cache
  111         // redirects. This therefore allows us to still do significantly less
  112         // work in case of inaccessible subtrees, which is the entire reason why
  113         // this deletes subtrees in the first place.
  114         $tree[$key]->subtree = [];
  115       }
  116     }
  117     return $tree;
  118   }
  119 
  120   /**
  121    * Performs access checking for nodes in an optimized way.
  122    *
  123    * This manipulator should be added before the generic ::checkAccess() one,
  124    * because it provides a performance optimization for ::checkAccess().
  125    *
  126    * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
  127    *   The menu link tree to manipulate.
  128    *
  129    * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
  130    *   The manipulated menu link tree.
  131    */
  132   public function checkNodeAccess(array $tree) {
  133     $node_links = [];
  134     $this->collectNodeLinks($tree, $node_links);
  135     if ($node_links) {
  136       $nids = array_keys($node_links);
  137 
  138       $query = $this->entityTypeManager->getStorage('node')->getQuery();
  139       $query->condition('nid', $nids, 'IN');
  140 
  141       // Allows admins to view all nodes, by both disabling node_access
  142       // query rewrite as well as not checking for the node status. The
  143       // 'view own unpublished nodes' permission is ignored to not require cache
  144       // entries per user.
  145       $access_result = AccessResult::allowed()->cachePerPermissions();
  146       if ($this->account->hasPermission('bypass node access')) {
  147         $query->accessCheck(FALSE);
  148       }
  149       else {
  150         $access_result->addCacheContexts(['user.node_grants:view']);
  151         $query->condition('status', NodeInterface::PUBLISHED);
  152       }
  153 
  154       $nids = $query->execute();
  155       foreach ($nids as $nid) {
  156         foreach ($node_links[$nid] as $key => $link) {
  157           $node_links[$nid][$key]->access = $access_result;
  158         }
  159       }
  160     }
  161 
  162     return $tree;
  163   }
  164 
  165   /**
  166    * Collects the node links in the menu tree.
  167    *
  168    * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
  169    *   The menu link tree to manipulate.
  170    * @param array $node_links
  171    *   Stores references to menu link elements to effectively set access.
  172    *
  173    * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
  174    *   The manipulated menu link tree.
  175    */
  176   protected function collectNodeLinks(array &$tree, array &$node_links) {
  177     foreach ($tree as $key => &$element) {
  178       if ($element->link->getRouteName() == 'entity.node.canonical') {
  179         $nid = $element->link->getRouteParameters()['node'];
  180         $node_links[$nid][$key] = $element;
  181         // Deny access by default. checkNodeAccess() will re-add it.
  182         $element->access = AccessResult::neutral();
  183       }
  184       if ($element->hasChildren) {
  185         $this->collectNodeLinks($element->subtree, $node_links);
  186       }
  187     }
  188   }
  189 
  190   /**
  191    * Checks access for one menu link instance.
  192    *
  193    * @param \Drupal\Core\Menu\MenuLinkInterface $instance
  194    *   The menu link instance.
  195    *
  196    * @return \Drupal\Core\Access\AccessResultInterface
  197    *   The access result.
  198    */
  199   protected function menuLinkCheckAccess(MenuLinkInterface $instance) {
  200     $access_result = NULL;
  201     if ($this->account->hasPermission('link to any page')) {
  202       $access_result = AccessResult::allowed();
  203     }
  204     else {
  205       $url = $instance->getUrlObject();
  206 
  207       // When no route name is specified, this must be an external link.
  208       if (!$url->isRouted()) {
  209         $access_result = AccessResult::allowed();
  210       }
  211       else {
  212         $access_result = $this->accessManager->checkNamedRoute($url->getRouteName(), $url->getRouteParameters(), $this->account, TRUE);
  213       }
  214     }
  215     return $access_result->cachePerPermissions();
  216   }
  217 
  218   /**
  219    * Generates a unique index and sorts by it.
  220    *
  221    * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
  222    *   The menu link tree to manipulate.
  223    *
  224    * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
  225    *   The manipulated menu link tree.
  226    */
  227   public function generateIndexAndSort(array $tree) {
  228     $new_tree = [];
  229     foreach ($tree as $key => $v) {
  230       if ($tree[$key]->subtree) {
  231         $tree[$key]->subtree = $this->generateIndexAndSort($tree[$key]->subtree);
  232       }
  233       $instance = $tree[$key]->link;
  234       // The weights are made a uniform 5 digits by adding 50000 as an offset.
  235       // After $this->menuLinkCheckAccess(), $instance->getTitle() has the
  236       // localized or translated title. Adding the plugin id to the end of the
  237       // index insures that it is unique.
  238       $new_tree[(50000 + $instance->getWeight()) . ' ' . $instance->getTitle() . ' ' . $instance->getPluginId()] = $tree[$key];
  239     }
  240     ksort($new_tree);
  241     return $new_tree;
  242   }
  243 
  244   /**
  245    * Flattens the tree to a single level.
  246    *
  247    * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
  248    *   The menu link tree to manipulate.
  249    *
  250    * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
  251    *   The manipulated menu link tree.
  252    */
  253   public function flatten(array $tree) {
  254     foreach ($tree as $key => $element) {
  255       if ($tree[$key]->subtree) {
  256         $tree += $this->flatten($tree[$key]->subtree);
  257       }
  258       $tree[$key]->subtree = [];
  259     }
  260     return $tree;
  261   }
  262 
  263 }