How can I overwrite existing menu tree manipulators? Or: how can I prevent unpublished nodes' menu items to be removed in the menu select when the user does not have the bypass node access checks permission, but may actually edit various unpublished nodes?

Im facing the problem that in the node form, the menu item options are missing some menu items because the related node is unpublished although the user may actually see and edit those nodes.

In /core/lib/Drupal/Core/Menu/MenuParentFormSelector.php the function getParentSelectOptions builds the options list which is then used in the node edit forms.

The function is:

  /**
   * {@inheritdoc}
   */
  public function getParentSelectOptions($id = '', array $menus = NULL, CacheableMetadata &amp;$cacheability = NULL) {
    if (!isset($menus)) {
      $menus = $this-&gt;getMenuOptions();
    }

    $options = array();
    $depth_limit = $this-&gt;getParentDepthLimit($id);
    foreach ($menus as $menu_name =&gt; $menu_title) {
      $options[$menu_name . ':'] = '&lt;' . $menu_title . '&gt;';

      $parameters = new MenuTreeParameters();
      $parameters-&gt;setMaxDepth($depth_limit);
      $tree = $this-&gt;menuLinkTree-&gt;load($menu_name, $parameters);
      $manipulators = array(
        array('callable' =&gt; 'menu.default_tree_manipulators:checkNodeAccess'),
        array('callable' =&gt; 'menu.default_tree_manipulators:checkAccess'),
        array('callable' =&gt; 'menu.default_tree_manipulators:generateIndexAndSort'),
      );
      $tree = $this-&gt;menuLinkTree-&gt;transform($tree, $manipulators);
      $this-&gt;parentSelectOptionsTreeWalk($tree, $menu_name, '--', $options, $id, $depth_limit, $cacheability);
    }
    return $options;
  }


The critical part in that function is the manipulator
 'menu.default_tree_manipulators:checkNodeAccess'.

The manipulator (/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php) does the following thing (public function checkNodeAccess):

if ($this-&gt;account-&gt;hasPermission('bypass node access')) {
  $query-&gt;accessCheck(FALSE);
}
else {
  $access_result-&gt;addCacheContexts(['user.node_grants:view']);
  $query-&gt;condition('status', NODE_PUBLISHED); // &lt;&lt;&lt; wtf?
}


TL;DR

So it assumes "either a user may bypass all node access checks or hide all unpublished nodes from the menu select" - which is weird...

How can I overwrite this?
I was unable to find a way to alter tree manipulators of an existing menu.
But it is possible if you render menu, let's say in a custom block.

Create your own tree manipulator service.

Copied from core.services.yml: 

  menu.default_tree_manipulators:
    class: Drupal\Core\Menu\DefaultMenuLinkTreeManipulators
    arguments: ['@access_manager', '@current_user', '@entity_type.manager']


you can define your service in a similar way:

  mymodule.my_custom_tree_manipulators:
    class: Drupal\mymodule\MyCustomMenuLinkTreeManipulators
    arguments: ['@access_manager', '@current_user', '@entity_type.manager']


Then create MyCustomMenuLinkTreeManipulators class that extends DefaultMenuLinkTreeManipulators, define custom method, let's say checkAccessIgnoreStatus() and implement logic you need.

There are many ways to use it, I'd create a new block plugin for that.
Refer to my answer for details on how to render menu with manipulators.

Use your manipulators:

$manipulators = array(
        array('callable' =&gt; 'mymodule.my_custom_tree_manipulators:checkAccessIgnoreStatus'),
        // ...
      );