Skip to content
MenuForm.php 14.8 KiB
Newer Older
use Drupal\menu_link\MenuLinkStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
  /**
   * The factory for entity queries.
   *
   * @var \Drupal\Core\Entity\Query\QueryFactory
   */
  protected $entityQueryFactory;

  /**
   * @var \Drupal\menu_link\MenuLinkStorageInterface
  /**
   * The menu tree service.
   *
   * @var \Drupal\menu_link\MenuTreeInterface
   */
  protected $menuTree;

  /**
   * The overview tree form.
   *
   * @var array
   */
  protected $overviewTreeForm = array('#tree' => TRUE);

  /**
   *
   * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query_factory
   *   The factory for entity queries.
   * @param \Drupal\menu_link\MenuLinkStorageInterface $menu_link_storage
   *   The menu link storage.
   * @param \Drupal\menu_link\MenuTreeInterface $menu_tree
   *   The menu tree service.
  public function __construct(QueryFactory $entity_query_factory, MenuLinkStorageInterface $menu_link_storage, MenuTreeInterface $menu_tree) {
    $this->entityQueryFactory = $entity_query_factory;
    $this->menuLinkStorage = $menu_link_storage;
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity.query'),
      $container->get('entity.manager')->getStorage('menu_link'),
  public function form(array $form, array &$form_state) {
    $menu = $this->entity;
      $form['#title'] = $this->t('Edit menu %label', array('%label' => $menu->label()));
    $form['label'] = array(
      '#type' => 'textfield',
      '#title' => t('Title'),
      '#default_value' => $menu->label(),
      '#required' => TRUE,
    );
    $form['id'] = array(
      '#type' => 'machine_name',
      '#title' => t('Menu name'),
      '#default_value' => $menu->id(),
      '#maxlength' => MENU_MAX_MENU_NAME_LENGTH_UI,
      '#description' => t('A unique name to construct the URL for the menu. It must only contain lowercase letters, numbers and hyphens.'),
      '#machine_name' => array(
        'source' => array('label'),
        'replace_pattern' => '[^a-z0-9-]+',
        'replace' => '-',
      ),
      // A menu's machine name cannot be changed.
      '#disabled' => !$menu->isNew() || $menu->isLocked(),
      '#type' => 'textfield',
      '#title' => t('Administrative summary'),
      '#maxlength' => 512,
    $form['langcode'] = array(
      '#type' => 'language_select',
      '#title' => t('Menu language'),
      '#languages' => Language::STATE_ALL,
      '#default_value' => $menu->langcode,
    );
    // Unlike the menu langcode, the default language configuration for menu
    // links only works with language module installed.
    if ($this->moduleHandler->moduleExists('language')) {
      $form['default_menu_links_language'] = array(
        '#type' => 'details',
        '#title' => t('Menu links language'),
      );
      $form['default_menu_links_language']['default_language'] = array(
        '#type' => 'language_configuration',
        '#entity_information' => array(
          'entity_type' => 'menu_link',
          'bundle' => $menu->id(),
        ),
        '#default_value' => language_get_default_configuration('menu_link', $menu->id()),
      );
    }

    // Add menu links administration form for existing menus.
      // Form API supports constructing and validating self-contained sections
      // within forms, but does not allow to handle the form section's submission
      // equally separated yet. Therefore, we use a $form_state key to point to
      // the parents of the form section.
      $form_state['menu_overview_form_parents'] = array('links');
      $form['links'] = array();
      $form['links'] = $this->buildOverviewForm($form['links'], $form_state);
    return parent::form($form, $form_state);
   * Returns whether a menu name already exists.
   *
   * @param string $value
   *   The name of the menu.
   *
   * @return bool
   *   Returns TRUE if the menu already exists, FALSE otherwise.
   */
  public function menuNameExists($value) {
    // Check first to see if a menu with this ID exists.
    if ($this->entityQueryFactory->get('menu')->condition('id', $value)->range(0, 1)->count()->execute()) {
      return TRUE;
    }

    // Check for a link assigned to this menu.
    return $this->entityQueryFactory->get('menu_link')->condition('menu_name', $value)->range(0, 1)->count()->execute();
   */
  protected function actions(array $form, array &$form_state) {
    $actions = parent::actions($form, $form_state);

    // Add the language configuration submit handler. This is needed because the
    // submit button has custom submit handlers.
    if ($this->moduleHandler->moduleExists('language')) {
      array_unshift($actions['submit']['#submit'],'language_configuration_element_submit');
      array_unshift($actions['submit']['#submit'], array($this, 'languageConfigurationSubmit'));
    }
    // We cannot leverage the regular submit handler definition because we have
    // button-specific ones here. Hence we need to explicitly set it for the
    // submit action, otherwise it would be ignored.
    if ($this->moduleHandler->moduleExists('content_translation')) {
      array_unshift($actions['submit']['#submit'], 'content_translation_language_configuration_element_submit');
  /**
   * Submit handler to update the bundle for the default language configuration.
   */
  public function languageConfigurationSubmit(array &$form, array &$form_state) {
    // Since the machine name is not known yet, and it can be changed anytime,
    // we have to also update the bundle property for the default language
    // configuration in order to have the correct bundle value.
    $form_state['language']['default_language']['bundle'] = $form_state['values']['id'];
    // Clear cache so new menus (bundles) show on the language settings admin
    // page.
    entity_info_cache_clear();
  }

   */
  public function save(array $form, array &$form_state) {
    $menu = $this->entity;
    $edit_link = \Drupal::linkGenerator()->generateFromUrl($this->t('Edit'), $this->entity->urlInfo());
    if ($status == SAVED_UPDATED) {
      drupal_set_message(t('Menu %label has been updated.', array('%label' => $menu->label())));
      watchdog('menu', 'Menu %label has been updated.', array('%label' => $menu->label()), WATCHDOG_NOTICE, $edit_link);
    }
    else {
      drupal_set_message(t('Menu %label has been added.', array('%label' => $menu->label())));
      watchdog('menu', 'Menu %label has been added.', array('%label' => $menu->label()), WATCHDOG_NOTICE, $edit_link);
    $form_state['redirect_route'] = $this->entity->urlInfo('edit-form');
  }

  /**
   * Form constructor to edit an entire menu tree at once.
   *
   * Shows for one menu the menu links accessible to the current user and
   * relevant operations.
   *
   * This form constructor can be integrated as a section into another form. It
   * relies on the following keys in $form_state:
   * - menu_overview_form_parents: An array containing the parent keys to this
   *   form.
   * Forms integrating this section should call menu_overview_form_submit() from
   * their form submit handler.
   */
  protected function buildOverviewForm(array &$form, array &$form_state) {
    // Ensure that menu_overview_form_submit() knows the parents of this form
    // section.
    $form['#tree'] = TRUE;
    $form['#theme'] = 'menu_overview_form';
    $form_state += array('menu_overview_form_parents' => array());

    $form['#attached']['css'] = array(drupal_get_path('module', 'menu') . '/css/menu.admin.css');

    $links = array();
    $query = $this->entityQueryFactory->get('menu_link')
      ->condition('menu_name', $this->entity->id());
    for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
      $query->sort('p' . $i, 'ASC');
    }
    $result = $query->execute();

    if (!empty($result)) {
      $links = $this->menuLinkStorage->loadMultiple($result);
    }

    $delta = max(count($links), 50);
    // We indicate that a menu administrator is running the menu access check.
    $this->getRequest()->attributes->set('_menu_admin', TRUE);
    $tree = $this->menuTree->buildTreeData($links);
    $this->getRequest()->attributes->set('_menu_admin', FALSE);

    $form = array_merge($form, $this->buildOverviewTreeForm($tree, $delta));
    $form['#empty_text'] = t('There are no menu links yet. <a href="@link">Add link</a>.', array('@link' => url('admin/structure/menu/manage/' . $this->entity->id() .'/add')));

    return $form;
  }

  /**
   * Recursive helper function for buildOverviewForm().
   *
   * @param $tree
   *   The menu_tree retrieved by menu_tree_data.
   * @param $delta
   *   The default number of menu items used in the menu weight selector is 50.
   *
   * @return array
   *   The overview tree form.
   */
  protected function buildOverviewTreeForm($tree, $delta) {
    $form = &$this->overviewTreeForm;
    foreach ($tree as $data) {
      $item = $data['link'];
      // Don't show callbacks; these have $item['hidden'] < 0.
      if ($item && $item['hidden'] >= 0) {
        $mlid = 'mlid:' . $item['mlid'];
        $form[$mlid]['#item'] = $item;
        $form[$mlid]['#attributes'] = $item['hidden'] ? array('class' => array('menu-disabled')) : array('class' => array('menu-enabled'));
        $form[$mlid]['title']['#markup'] = l($item['title'], $item['href'], $item['localized_options']);
        if ($item['hidden']) {
          $form[$mlid]['title']['#markup'] .= ' (' . t('disabled') . ')';
        }
        elseif ($item['link_path'] == 'user' && $item['module'] == 'user') {
          $form[$mlid]['title']['#markup'] .= ' (' . t('logged in users only') . ')';
        }

        $form[$mlid]['hidden'] = array(
          '#type' => 'checkbox',
          '#title' => t('Enable @title menu link', array('@title' => $item['title'])),
          '#title_display' => 'invisible',
          '#default_value' => !$item['hidden'],
        );
        $form[$mlid]['weight'] = array(
          '#type' => 'weight',
          '#delta' => $delta,
          '#default_value' => $item['weight'],
          '#title' => t('Weight for @title', array('@title' => $item['title'])),
        );
        $form[$mlid]['mlid'] = array(
          '#type' => 'hidden',
          '#value' => $item['mlid'],
        );
        $form[$mlid]['plid'] = array(
          '#type' => 'hidden',
          '#default_value' => $item['plid'],
        );
        // Build a list of operations.
        $operations = array();
          'title' => t('Edit'),
          'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/edit',
        );
        // Only items created by the Menu UI module can be deleted.
        if ($item->access('delete')) {
          $operations['delete'] = array(
            'title' => t('Delete'),
            'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/delete',
          );
        }
        // Set the reset column.
        elseif ($item->access('reset')) {
          $operations['reset'] = array(
            'title' => t('Reset'),
            'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/reset',
          );
        }
        $form[$mlid]['operations'] = array(
          '#type' => 'operations',
        );
      }

      if ($data['below']) {
        $this->buildOverviewTreeForm($data['below'], $delta);
      }
    }
    return $form;
  }

  /**
   * Submit handler for the menu overview form.
   *
   * This function takes great care in saving parent items first, then items
   * underneath them. Saving items in the incorrect order can break the menu tree.
   */
  protected function submitOverviewForm(array $complete_form, array &$form_state) {
    // Form API supports constructing and validating self-contained sections
    // within forms, but does not allow to handle the form section's submission
    // equally separated yet. Therefore, we use a $form_state key to point to
    // the parents of the form section.
    $parents = $form_state['menu_overview_form_parents'];
    $input = NestedArray::getValue($form_state['input'], $parents);
    $form = &NestedArray::getValue($complete_form, $parents);

    // When dealing with saving menu items, the order in which these items are
    // saved is critical. If a changed child item is saved before its parent,
    // the child item could be saved with an invalid path past its immediate
    // parent. To prevent this, save items in the form in the same order they
    // are sent, ensuring parents are saved first, then their children.
    // See http://drupal.org/node/181126#comment-632270
    $order = is_array($input) ? array_flip(array_keys($input)) : array();
    // Update our original form with the new order.
    $form = array_intersect_key(array_merge($order, $form), $form);

    $updated_items = array();
    $fields = array('weight', 'plid');
    foreach (Element::children($form) as $mlid) {
      if (isset($form[$mlid]['#item'])) {
        $element = $form[$mlid];
        // Update any fields that have changed in this menu item.
        foreach ($fields as $field) {
          if ($element[$field]['#value'] != $element[$field]['#default_value']) {
            $element['#item'][$field] = $element[$field]['#value'];
            $updated_items[$mlid] = $element['#item'];
          }
        }
        // Hidden is a special case, the value needs to be reversed.
        if ($element['hidden']['#value'] != $element['hidden']['#default_value']) {
          // Convert to integer rather than boolean due to PDO cast to string.
          $element['#item']['hidden'] = $element['hidden']['#value'] ? 0 : 1;
          $updated_items[$mlid] = $element['#item'];
        }
      }
    }

    // Save all our changed items to the database.
    foreach ($updated_items as $item) {
      $item['customized'] = 1;
      $item->save();
    }