simple_grouped_content-2.0.x-dev/modules/sgc_support_module/src/EventSubscriber/MenuFieldFormEventSubscriber.php
modules/sgc_support_module/src/EventSubscriber/MenuFieldFormEventSubscriber.php
<?php
namespace Drupal\sgc_support_module\EventSubscriber;
use Drupal\core_event_dispatcher\Event\Form\FormAlterEvent;
use Drupal\core_event_dispatcher\Event\Form\FormBaseAlterEvent;
use Drupal\core_event_dispatcher\FormHookEvents;
use Drupal\group\Entity\GroupRelationship;
use Drupal\group\Entity\GroupInterface;
use Drupal\value_fetcher\ValueFetcher;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuLinkTree;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Routing\CurrentRouteMatch;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Class ExampleFormEventSubscribers.
*
* Don't forget to define your class as a service and tag it as
* an "event_subscriber":
*
* services:
* hook_event_dispatcher.example_form_subscribers:
* class: Drupal\hook_event_dispatcher\ExampleFormEventSubscribers
* tags:
* - { name: event_subscriber }
*/
class MenuFieldFormEventSubscriber implements EventSubscriberInterface {
/**
* Route matcher.
*/
protected CurrentRouteMatch $routeMatch;
/**
* Menu link tree service.
*/
protected MenuLinkTree $menuLinkTree;
/**
* Form ID's which we should set it to clear group menu cache tags on submit.
*
* @var array
*/
private $formsToClearMenuCacheTagOn = [
'group_content_menu_default_group_menu_edit_form',
'menu_link_content_menu_link_content_form',
];
/**
* {@inheritdoc}
*/
public function __construct(
CurrentRouteMatch $routeMatch,
MenuLinkTree $menuLinkTree
) {
$this->routeMatch = $routeMatch;
$this->menuLinkTree = $menuLinkTree;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [
FormHookEvents::FORM_ALTER => 'alterForm',
];
}
/**
* Alter form.
*
* PURPOSE: Set group menu as base of menu on menu link field.
*
* @param \Drupal\core_event_dispatcher\Event\Form\FormAlterEvent $event
* The event.
*/
public function alterForm(FormAlterEvent $event): void {
$form = &$event->getForm();
$form_state =$event->getFormState();
$build_info = $form_state->getBuildInfo();
// If we're dealing with a node base form, disable menu form if in group.
if (
isset($build_info['base_form_id'])
&& $build_info['base_form_id'] == 'node_form'
) {
$this->disableMenuFieldOnNodesInGroup($form, $form_state);
}
// Require menu title on menu links.
if (isset($form['field_group_content_menu_link'])) {
// Require title if the menu link is added.
$form['field_group_content_menu_link']['widget'][0]['title']['#element_validate'][] = [
$this,
'validateTitleWhenChecked'
];
}
// Clear menu cache on any form submit that can change a group's menu.
if (
in_array($form['#form_id'], $this->formsToClearMenuCacheTagOn)
|| isset($form['field_group_content_menu_link'])
) {
$form['actions']['submit']['#submit'][] = get_class($this) . '::invalidateGroupMenuBlockTag';
}
if (isset($form['field_group_content_menu_link'])) {
// Change the description field title to "hover text" for easy reference.
$form['field_group_content_menu_link']['widget'][0]['description']['#title'] = t('Hover text');
$this->adjustGroupContentMenuLinkparent($form, $form_state);
}
}
/**
* Adjusts a group content menu link fields parent menu to the groups menu.
*
* @param array $form
* The form array from the form_alter.
* @param FormStateInterface $form_state
* The form state object as passed from the form_later.
*/
protected function adjustGroupContentMenuLinkparent(array &$form, FormStateInterface $form_state) : void {
$element = &$form['field_group_content_menu_link']['widget'][0];
$group = $this->routeMatch->getParameters()->get('group');
$group_content = $form_state->getformObject()->getEntity();
$group_menus = group_content_menu_get_menus_per_group($group);
$primary_group_menu = $group_menus[0]->getEntity();
$current_menu_item_uuid = NULL;
// If we're dealing with group content, establish the group menu to place this in.
if ($group_content) {
$current_menu_values = ValueFetcher::getAllValues($group_content, 'field_group_content_menu_link');
if ($current_menu_values) {
$parent_key = $this->getGroupMenuName($group). ':' . $current_menu_values['parent'];
$element['menu_parent']['#default_value'] = $parent_key;
$current_menu_item_uuid = $group_content->field_group_content_menu_link->first()->getMenuPluginId();
}
}
$element['menu']['#open'] = TRUE;
// Rebuild menu parent options from scratch!
$element['menu_parent']['#options'] = [];
$element['menu_parent']['#options'][$this->getGroupMenuName($group). ':'] = '<base>';
$element['menu_parent']['#options'] = array_merge(
$element['menu_parent']['#options'],
$this->ConvertMenuTreeToOptionsArray(
$this->getGroupMenuName($group),
$this->fetchMenuTreeForGroup($group),
$current_menu_item_uuid
)
);
}
/**
* Returns the group menu name string to help DRY that bit up.
*
* @param \Drupal\group\Entity\GroupInterface $group
*
* @return string
*/
protected static function getGroupMenuName(GroupInterface $group) {
$group_menu = group_content_menu_get_menus_per_group($group);
if (count($group_menu) > 1) {
\Drupal::logger('sgc_support_module')->error('No Group Menu found for %group (%gid)', [
'%group' => $group->label(),
'%gid' => $group->id()
]);
return FALSE;
}
$menu_id = ValueFetcher::getFirstValue(reset($group_menu), 'entity_id');
return 'group_menu_link_content-' . $menu_id;
}
/**
* Get menu tree for menu with group as parent menu item.
*
* @param GroupInterface $group
* A group entity we can pull the group ID from.
*
* @return
* Menu tree.
*/
protected function fetchMenuTreeForGroup(GroupInterface $group) {
$menu_parameters = new MenuTreeParameters();
$menu_parameters->onlyEnabledLinks();
return $this->menuLinkTree->load($this->getGroupMenuName($group), $menu_parameters);
}
/**
* Convert Menu Tree to options array
*
* @param string $menu_name
* The machine name of the menu to link to.
* @param array $menu_tree
* The menu tree array as returned by the service menu.link_tree.
* @param string $current_uuid
* The current item UUID to skip over.
* @param int $depth
* Do not use. Depth into menu tree. Recursion var for number of dashes.
*
* @return array
* An array of form options for the #options attribute.
*/
protected function ConvertMenuTreeToOptionsArray(string $menu_name, array $menu_tree, $current_uuid, $depth = 1, $max_depth = 9) {
$menu_options = [];
foreach ($menu_tree as $value => $tree_data) {
if (is_null($current_uuid) || ($current_uuid && strpos($value, $current_uuid) === FALSE)) {
$option_key = $menu_name . ':' . $value;
$menu_options[$option_key] =
str_repeat('--', $depth - 1) . ' ' . $tree_data->link->getTitle();
if (!empty($tree_data->subtree) && $depth < $max_depth) {
$depth = $depth + 1;
$menu_options = array_merge(
$menu_options,
$this->ConvertMenuTreeToOptionsArray(
$menu_name,
$tree_data->subtree,
$current_uuid,
$depth,
$max_depth
)
);
$depth = $depth - 1;
}
}
}
return $menu_options;
}
/**
* Form validation hook checking that a user has at least a contributor role.
*
* NOTE: Group may not be set on non-group menu items.
*/
public static function invalidateGroupMenuBlockTag(&$form, FormStateInterface $form_state) {
$group = \Drupal::service('current_route_match')->getParameter('group');
if ($group) {
$group_menu_name = self::getGroupMenuName($group);
if (is_string($group_menu_name)) {
Cache::invalidateTags([
$group_menu_name
]);
}
}
}
/**
* Static element validation method for checking menu title has been entered.
*
* @param array $element
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form validation hook checking that a user has at least a contributor role.
*/
public static function validateTitleWhenChecked(array $element, FormStateInterface $form_state) {
if (
$form_state->getValue('field_group_content_menu_link')[0]['enabled']
&& strlen($element['#value']) === 0
) {
$form_state->setError($element, t('A menu title is required'));
}
}
/**
* Disables the menu field on nodes that are in a group
*
* @param array $form
* The form array to alter.
*/
protected function disableMenuFieldOnNodesInGroup(&$form, FormStateInterface $form_state) : void{
$group_from_route = \Drupal::routeMatch()->getParameters()->get('group');
if (!is_null($group_from_route)) {
$form['menu']['#access'] = FALSE;
return;
}
$node = $form_state->getformObject()->getEntity();
if (!$node->isNew()) {
if(!empty(GroupRelationship::loadByEntity($node))) {
$form['menu']['#access'] = FALSE;
return;
}
}
}
}
