diff --git a/og_menu.behat.inc b/og_menu.behat.inc index 11f4e1b..b64a907 100644 --- a/og_menu.behat.inc +++ b/og_menu.behat.inc @@ -65,7 +65,8 @@ class OgMenuSubContext extends DrupalSubContextBase implements DrupalSubContextI $query = $storage->getQuery() ->condition($entity->getKey('label'), $label) - ->range(0, 1); + ->range(0, 1) + ->accessCheck(TRUE); // Optionally filter by bundle. if ($bundle) { diff --git a/og_menu.module b/og_menu.module index 592263c..7b08484 100644 --- a/og_menu.module +++ b/og_menu.module @@ -11,6 +11,9 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Link; use Drupal\Core\Menu\MenuLinkInterface; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\menu_link_content\Entity\MenuLinkContent; +use Drupal\node\NodeInterface; +use Drupal\node\NodeTypeInterface; use Drupal\og\Og; use Drupal\og\OgGroupAudienceHelper; use Drupal\og\OgGroupAudienceHelperInterface; @@ -106,6 +109,45 @@ function og_menu_form_alter(&$form, FormStateInterface $form_state, $form_id) { } } +/** + * Implements hook_form_FORM_ID_alter() for \Drupal\node\NodeTypeForm. + * + * Adds og menu options to the node type form. + * + * @see og_menu_form_node_type_form_builder() + */ +function og_menu_form_node_type_form_alter(&$form, FormStateInterface $form_state) { + if (!isset($form['og'])) { + // There is no OG present here. + return; + } + + $type = $form_state->getFormObject()->getEntity(); + + $form['og']['allow_og_menu'] = [ + '#type' => 'checkbox', + '#title' => t('Allow OG Menu'), + '#default_value' => $type->getThirdPartySetting('og_menu', 'allow_og_menu'), + '#description' => t('Allow OG menu UI on edit forms for this content type.'), + '#states' => [ + 'visible' => [ + ':input[name="og_group_content_bundle"]' => array('checked' => TRUE), + ], + ], + ]; + + $form['#entity_builders'][] = 'og_menu_form_node_type_form_builder'; +} + +/** + * Entity builder for the node type form with og_menu options. + * + * @see og_menu_form_node_type_form_alter() + */ +function og_menu_form_node_type_form_builder($entity_type, NodeTypeInterface $type, &$form, FormStateInterface $form_state) { + $type->setThirdPartySetting('og_menu', 'allow_og_menu', $form_state->getValue('allow_og_menu')); +} + /** * Implements hook_system_breadcrumb_alter(). */ @@ -157,3 +199,419 @@ function og_menu_module_implements_alter(&$implementations, $hook) { break; } } +/** + * Implements hook_form_FORM_ID_alter(). + * + * Sets correct parent options for OG Menus. + */ +function og_menu_form_menu_link_content_form_alter(&$form, FormStateInterface $form_state) { + /** @var Drupal\menu_link_content\Entity\MenuLinkContent $menu_link */ + $menu_link = $form_state->getFormObject()->getEntity(); + $menu_name = $menu_link->getMenuName(); + + if (!preg_match('/^(ogmenu-)/', $menu_name, $matches)) { + return; + } + + // Get OG menu instance. + list($menu_type, $menu_id) = explode('-', $menu_name, 2); + $storage = \Drupal::entityTypeManager()->getStorage('ogmenu_instance'); + $og_menu_instance = $storage->load($menu_id); + $menus = [$menu_name => $og_menu_instance->label()]; + + // Prepare default. + $default = $menu_name . ':' . $menu_link->parent->value; + + // Get parent selector. + $menu_parent_selector = \Drupal::service('menu.parent_form_selector'); + $parent_element = $menu_parent_selector->parentSelectElement($default, '', $menus); + $form['menu_parent']['#options'] = $parent_element['#options']; + $form['menu_parent']['#default_value'] = $parent_element['#default_value']; +} + +/** + * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm. + * + * Alters node form menu selection for OG menus and sets default. + */ +function og_menu_form_node_form_alter(&$form, FormStateInterface $form_state) { + // Only do this for group content. Other content should not be present in + // group menus. + + /** @var \Drupal\node\NodeInterface $node */ + $node = $form_state->getFormObject()->getEntity(); + + /** @var \Drupal\node\NodeTypeInterface $node_type */ + $node_type = $node->type->entity; + $groupTypeManager = \Drupal::service('og.group_type_manager'); + if (!$groupTypeManager->isGroupContent($node->getEntityTypeId(), $node_type->id())) { + return; + } + + if (!$node_type->getThirdPartySetting('og_menu', 'allow_og_menu')) { + return; + } + + // Get the default menu item. + $defaults = og_menu_get_menu_link_defaults($node); + + // Since OG content is unlikely to appear in global menus, this is empty. + // This cannot be filled by MenuParentSelector service, as that one is + // not aware of the (og) context. + if (empty($form['menu'])) { + // We recreate the menu tab here, so we can fill in the menu parents later. + $form['#og_default'] = $defaults; + og_menu_recreate_menu_form_item($form, $form_state); + } + + // Fill parents up with ajax and the badly documented trigger_as property. + $form['og_audience']['widget']['#ajax'] = [ + 'callback' => '', + 'wrapper' => 'menu-ajax-wrapper', + 'progress' => [ + 'type' => 'throbber', + ], + 'trigger_as' => [ + 'name' => 'op', + 'value' => t('Update menu'), + ], + ]; + + $form['menu']['link']['menu_parent']['#prefix'] = ''; + + $form['update_menu'] = [ + '#type' => 'submit', + '#value' => t('Update menu'), + '#limit_validation_errors' => [['og_audience']], + '#attributes' => [ + 'class' => ['js-hide'], + ], + '#submit' => [ + 'og_menu_update_menu_parent_submit', + ], + '#ajax' => [ + 'callback' => 'og_menu_update_menu_parent_callback', + 'wrapper' => 'menu-ajax-wrapper', + ], + ]; + + // Set default for og menu select. + if (!empty($form['menu'])) { + // Get og audiences selection. + if (!$values = $form_state->get('og_audience')) { + $values = $node->get(OgGroupAudienceHelper::DEFAULT_FIELD)->getValue(); + } + + $target_ids = array_map(function ($value) { + return $value['target_id']; + }, $values); + + // Get the parent select. + $parent_element = og_menu_get_parent_select($node, $target_ids, $defaults); + if (empty($parent_element)) { + return; + } + foreach($parent_element as $key => $value) { + $form['menu']['link']['menu_parent'][$key] = $value; + } + + // Set default for OG menus, if not already set. + if ($form['menu']['enabled']['#default_value'] == FALSE) { + if (!empty($defaults['id'])) { + // Set the OG Menu defaults. + $form['menu']['#open'] = TRUE; + $form['menu']['enabled']['#default_value'] = TRUE; + foreach (['id', 'entity_id'] as $key) { + $form['menu']['link'][$key] = [ + '#type' => 'value', + '#value' => $defaults[$key] + ]; + } + $form['menu']['link']['title']['#default_value'] = $defaults['title']; + $form['menu']['link']['title']['#maxlength'] = $defaults['title_max_length']; + $form['menu']['link']['description']['#default_value'] = $defaults['description']; + $form['menu']['link']['weight']['#default_value'] = $defaults['weight']; + } + } + + $form['#validate'][] = 'og_menu_validate_parent_menu'; + } +} + +/** + * Form validation callback. + * Disables menu link when no parent is given. + */ +function og_menu_validate_parent_menu(array $form, FormStateInterface $form_state) { + $menu_parent = $form_state->getValue('menu')['menu_parent']; + if (empty($menu_parent)) { + $form_state->setValue('menu', ['enabled' => 0]); + } +} + +/** + * Form submit callback. + **/ +function og_menu_update_menu_parent_submit(array $form, FormStateInterface $form_state) { + $values = $form_state->getValue(OgGroupAudienceHelper::DEFAULT_FIELD); + $form_state->set('og_audience', $values); + $form_state->setRebuild(); +} + +/** + * Callback to update the menu parent select. + * + * @param $form + * The form array. + * @param $form_state + * The form state. + * + * @return array + * The updated form part. + */ +function og_menu_update_menu_parent_callback($form, FormStateInterface $form_state) { + + $form['menu']['link']['menu_parent']['#prefix'] = ''; + + return $form['menu']['link']['menu_parent']; +} + +/** + * Recreate the menu form item. + * + * @param $form + * The form array. + * @param $form_state + * The form state. + */ +function og_menu_recreate_menu_form_item(array &$form, FormStateInterface $form_state) { + $defaults = $form['#og_default']; + // @see menu_ui_form_node_form_alter(). + $form['menu'] = [ + '#type' => 'details', + '#title' => t('Menu settings'), + '#access' => \Drupal::currentUser()->hasPermission('administer menu'), + // This should not be hardcoded. + '#open' => FALSE, + '#group' => 'advanced', + '#attached' => [ + 'library' => ['menu_ui/drupal.menu_ui'], + ], + '#tree' => TRUE, + '#weight' => -2, + '#attributes' => ['class' => ['menu-link-form']], + ]; + $form['menu']['enabled'] = [ + '#type' => 'checkbox', + '#title' => t('Provide a menu link'), + '#default_value' => isset($defaults['id']) ? (int) (bool) $defaults['id'] : FALSE, + ]; + $form['menu']['link'] = [ + '#type' => 'container', + '#parents' => ['menu'], + '#states' => [ + 'invisible' => [ + 'input[name="menu[enabled]"]' => ['checked' => FALSE], + ], + ], + ]; + + foreach (['id', 'entity_id'] as $key) { + $form['menu']['link'][$key] = [ + '#type' => 'value', + '#value' => !empty($defaults[$key]) ? $defaults[$key] : '', + ]; + } + + $form['menu']['link']['title'] = [ + '#type' => 'textfield', + '#title' => t('Menu link title'), + '#maxlength' => $defaults['title_max_length'], + '#default_value' => !empty($defaults['title']) ? $defaults['title'] : '', + ]; + + $form['menu']['link']['description'] = [ + '#type' => 'textfield', + '#title' => t('Description'), + '#description' => t('Shown when hovering over the menu link.'), + '#maxlength' => $defaults['description_max_length'], + '#default_value' => !empty($defaults['description']) ? $defaults['description'] : '', + ]; + + $form['menu']['link']['weight'] = [ + '#type' => 'number', + '#title' => t('Weight'), + '#description' => t('Menu links with lower weights are displayed before links with higher weights.'), + '#default_value' => !empty($defaults['weight']) ? $defaults['weight'] : '', + ]; + + $form['menu']['link']['menu_parent'] = [ + '#title' => t('Parent item'), + '#attributes' => [ + 'class' => [ + 'menu-parent-select', + ], + ], + ]; + + foreach (array_keys($form['actions']) as $action) { + if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') { + $form['actions'][$action]['#submit'][] = 'menu_ui_form_node_form_submit'; + } + } + + $form['#entity_builders'][] = 'menu_ui_node_builder'; +} + +/** + * Returns the parent selector for the OG Menu(s) of the current group(s) + * + * The default menu.parent_form_selector does not have context and cannot help us here. + * + * @param NodeInterface $node + * The node for which to get the parents. + * @param array $target_ids + * The groups to use to load the menus. + * @param array $menu_defaults + * The default menu items to use. + * + * @return + * A render array with the select element. + */ +function og_menu_get_parent_select($node, array $target_ids, array $menu_defaults = []) { + // Recreate the parent selector widget for the OG menu. + + /** @var \Drupal\node\NodeTypeInterface $node_type */ + $node_type = $node->type->entity; + + /** @var \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector */ + // Get the default menus for the node type. As mentioned before, this will + // probably be empty. + $menu_parent_selector = \Drupal::service('menu.parent_form_selector'); + $menu_names = array_map(function ($menu) { return $menu->label(); }, Menu::loadMultiple()); + asort($menu_names); + $type_menus = $node_type->getThirdPartySetting('menu_ui', 'available_menus', ['main']); + $available_menus = []; + foreach ($type_menus as $menu) { + $available_menus[$menu] = $menu_names[$menu]; + } + + $placeholder = FALSE; + if (!empty($target_ids)) { + $storage = \Drupal::entityTypeManager()->getStorage('ogmenu_instance'); + $og_menu_instances = $storage->loadByProperties([OgGroupAudienceHelper::DEFAULT_FIELD => $target_ids]); + /** @var \Drupal\og\OgAccessInterface $og_access */ + $og_access = \Drupal::service('og.access'); + foreach ($og_menu_instances as $instance) { + // Use permission instead of operation, because OG Access checks entity permissions in a weird way. + // OG Access Controld Handler does not get called even. + if ($og_access->userAccessEntity('abc', $instance)) { + $available_menus['ogmenu-' . $instance->id()] = $instance->label(); + } + } + } + else { + // No target selected yet. If node type is OG content, add placeholder. + $placeholder = TRUE; + } + + if (!empty($available_menus)) { + $default = ''; + if (isset($menu_defaults['id'])) { + $default = $menu_defaults['menu_name'] . ':' . $menu_defaults['parent']; + } + else { + $menu_defaults['id'] = ''; + // Parent form selector expects a parent item, even empty. + $default = ':'; + } + $parent_element = $menu_parent_selector->parentSelectElement($default, $menu_defaults['id'], $available_menus); + } + elseif ($placeholder) { + $parent_element = [ + '#type' => 'select', + '#empty_option' => t('Choose a theme'), + ]; + } + else { + $parent_element = [ + '#type' => 'select', + '#empty_option' => t('No menu items for this theme.'), + ]; + } + + return $parent_element; +} + +/** + * Returns the definition for an OG menu link for the given node. + * + * @param \Drupal\node\NodeInterface $node + * The node entity. + * + * @return array + * An array that contains default values for the menu link form. + */ +function og_menu_get_menu_link_defaults(NodeInterface $node) { + // Prepare the definition for the edit form. + $defaults = []; + if ($node->id()) { + $id = FALSE; + $og_menu_names = []; + $og_menus = \Drupal::service('entity_type.manager')->getStorage('ogmenu_instance')->loadMultiple(); + /** @var \Drupal\og_menu\Entity\OgMenuInstance[] $og_menus */ + foreach ($og_menus as $og_menu) { + $og_menu_names[] = 'ogmenu-' . $og_menu->id(); + } + // Check if a link exists in one of the OG menus. + if (!empty($og_menus)) { + $query = \Drupal::entityQuery('menu_link_content') + ->condition('link.uri', 'entity:node/' . $node->id()) + ->condition('menu_name', array_values($og_menu_names), 'IN') + ->sort('id', 'ASC') + ->range(0, 1) + ->accessCheck(TRUE); + $result = $query->execute(); + + $id = !empty($result) ? reset($result) : FALSE; + } + if ($id) { + $menu_link = MenuLinkContent::load($id); + $menu_link = \Drupal::service('entity.repository')->getTranslationFromContext($menu_link); + $defaults = [ + 'entity_id' => $menu_link->id(), + 'id' => $menu_link->getPluginId(), + 'title' => $menu_link->getTitle(), + 'title_max_length' => $menu_link->getFieldDefinitions()['title']->getSetting('max_length') ?? 255, + 'description' => $menu_link->getDescription(), + 'description_max_length' => $menu_link->getFieldDefinitions()['description']->getSetting('max_length') ?? 255, + 'menu_name' => $menu_link->getMenuName(), + 'parent' => $menu_link->getParentId(), + 'weight' => $menu_link->getWeight(), + ]; + } + } + + if (!$defaults) { + // Get the default max_length of a menu link title from the base field + // definition. + $field_definitions = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('menu_link_content'); + $max_length = $field_definitions['title']->getSetting('max_length'); + $description_max_length = $field_definitions['description']->getSetting('max_length'); + $defaults = [ + 'entity_id' => 0, + 'id' => '', + 'title' => '', + 'title_max_length' => $max_length ?? 255, + 'description' => '', + 'description_max_length' => $description_max_length ?? 255, + 'menu_name' => '', + 'parent' => '', + 'weight' => 0, + ]; + } + + return $defaults; +} diff --git a/src/Form/OverviewMenuInstances.php b/src/Form/OverviewMenuInstances.php index bd7efa1..ed02a28 100644 --- a/src/Form/OverviewMenuInstances.php +++ b/src/Form/OverviewMenuInstances.php @@ -65,7 +65,8 @@ class OverviewMenuInstances extends FormBase { $query = $og_instance_storage->getQuery() ->pager(50) ->sort('id') - ->condition('type', $ogmenu->id()); + ->condition('type', $ogmenu->id()) + ->accessCheck(); $rids = $query->execute(); $entities = $og_instance_storage->loadMultiple($rids); diff --git a/src/OgMenuParentFormSelector.php b/src/OgMenuParentFormSelector.php deleted file mode 100644 index baedc00..0000000 --- a/src/OgMenuParentFormSelector.php +++ /dev/null @@ -1,53 +0,0 @@ -isOgMenu = TRUE; - } - return parent::parentSelectElement($menu_parent, $id, $menus); - } - - /** - * {@inheritdoc} - */ - protected function getMenuOptions(array $menu_names = NULL) { - $entity_type = 'menu'; - if ($this->isOgMenu) { - $entity_type = 'ogmenu_instance'; - } - - // @todo Here we'll need to do some access checks to see if which menus - // belong to the og. - $menus = $this->entityTypeManager->getStorage($entity_type)->loadMultiple($menu_names); - $options = []; - /** @var \Drupal\system\MenuInterface[] $menus */ - foreach ($menus as $menu) { - if ($this->isOgMenu) { - $options['ogmenu-' . $menu->id()] = $menu->label(); - } - else { - $options[$menu->id()] = $menu->label(); - } - - } - return $options; - } - -} diff --git a/src/OgMenuServiceProvider.php b/src/OgMenuServiceProvider.php deleted file mode 100644 index 5cd5b2f..0000000 --- a/src/OgMenuServiceProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -getDefinition('menu.parent_form_selector'); - $definition->setClass('Drupal\og_menu\OgMenuParentFormSelector'); - } - -}