association-1.0.0-alpha2/modules/association_menu/src/Form/MenuItemEditForm.php
modules/association_menu/src/Form/MenuItemEditForm.php
<?php namespace Drupal\association_menu\Form; use Drupal\association\Entity\AssociationInterface; use Drupal\association_menu\AssociatedEntityMenuItem; use Drupal\association_menu\AssociationMenuStorageInterface; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Path\PathValidatorInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Form for editing and adding items to an association menu. */ class MenuItemEditForm extends FormBase { /** * Menu item being edited by this form. * * @var \Drupal\association_menu\MenuItemInterface */ protected $item; /** * The association which this menu item belongs to. * * @var \Drupal\association\Entity\AssociationInterface */ protected $association; /** * Provides path resolution and validation services. * * @var \Drupal\Core\Path\PathValidatorInterface */ protected $pathValidator; /** * Association menu item storage manager. * * @var \Drupal\association_menu\AssociationMenuStorageInterface */ protected $menuStorage; /** * Create a new instance of the association menu item edit form. * * @param \Drupal\Core\Path\PathValidatorInterface $path_validator * Provides path resolution and validation services. * @param \Drupal\association_menu\AssociationMenuStorageInterface $association_menu_storage * Association navigation storage manager. */ public function __construct(PathValidatorInterface $path_validator, AssociationMenuStorageInterface $association_menu_storage) { $this->pathValidator = $path_validator; $this->menuStorage = $association_menu_storage; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('path.validator'), $container->get('association_menu.storage') ); } /** * {@inheritdoc} */ public function getFormId() { return 'association_menu_edit_item_form'; } /** * Builds the form elements for the association menu item edit form. * * @param array $form * Current form structure and elements. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state, values and build information. * @param \Drupal\association\Entity\AssociationInterface $association * The entity association which this menu item is being built for. * @param int|null $menu_item_id * The ID of the menu item to edit, or NULL if creating a new menu item. * * @return array * The add or edit menu item form elements. */ public function buildForm(array $form, FormStateInterface $form_state, AssociationInterface $association = NULL, $menu_item_id = NULL) { if (!$association) { throw new NotFoundHttpException(); } // Set the association and the menu item this form is working with for // use in the validate and submit form callbacks. $this->association = $association; try { if ($menu_item_id) { /** @var \Drupal\association_menu\MenuItemBase */ $item = $this->menuStorage->getMenuItem($association, $menu_item_id); $this->item = $item; $values = [ 'title' => $this->item->title, 'enabled' => $this->item->isEnabled(), 'expanded' => $this->item->isExpanded(), 'options' => $this->item->getOptions(), ]; } else { $values = [ 'title' => '', 'enabled' => TRUE, 'expanded' => TRUE, 'options' => [], ]; } } catch (\Exception $e) { throw new NotFoundHttpException(); } // Build the form elements. $form['title'] = [ '#type' => 'textfield', '#title' => $this->t('Menu link title'), '#default_value' => $values['title'], '#description' => $this->t('Text to be used for the menu link title.'), ]; $form['actions'] = [ '#type' => 'actions', 'save' => [ '#type' => 'submit', '#value' => $this->t('Save'), ], ]; if ($this->item instanceof AssociatedEntityMenuItem) { $entity = $this->item->getEntity(); $entityLabel = $entity->label(); $assocTypeLabel = $association->getType()->label(); $form['title']['#placeholder'] = $entityLabel; $form['title']['#description'] = $this->t('Text to be used for the menu link title. If left blank the @bundle_label content title will be used.', [ '@bundle_label' => $assocTypeLabel, ]); $form['url'] = [ '#theme_wrappers' => ['form_element'], '#title' => $this->t('Link URL'), '#description' => $this->t('This link is autogenerated from @bundle_label content, and cannot be changed. Disable if you do not want this link to appear in the menu', [ '@bundle_label' => $assocTypeLabel, ]), 'value' => [ '#type' => 'link', '#title' => $entityLabel, '#url' => $this->item->getUrl(), ], ]; } else { // For custom URL links, a title is required. $form['title']['#required'] = TRUE; $form['url'] = [ '#type' => 'textfield', '#title' => $this->t('Link URL'), '#required' => TRUE, '#default_value' => '', '#description' => $this->t('Enter the location this menu link points to. Enter internal paths starting with "/" like "/node" or "<nolink>" if the menu item does not link anywhere. External URLs should start with "https://".'), ]; if ($this->item) { if ($url = $this->item->getUrl()) { $form['url']['#default_value'] = $url->toString(); } $form['actions']['delete'] = [ '#type' => 'link', '#title' => $this->t('Delete'), '#url' => Url::fromRoute('association_menu.delete_item_confirm', [ 'association' => $association->id(), 'menu_item_id' => $this->item->id(), ]), '#attributes' => [ 'class' => ['button', 'button--danger'], ], ]; } } $form['enabled'] = [ '#type' => 'checkbox', '#title' => $this->t('Enabled'), '#default_value' => $values['enabled'], ]; $form['expanded'] = [ '#type' => 'checkbox', '#title' => $this->t('Show sub-menu items'), '#default_value' => $values['expanded'], ]; $attrs = $values['options']['attributes'] ?? []; $form['attributes'] = [ '#type' => 'details', '#title' => $this->t('Link attributes'), '#open' => FALSE, '#tree' => TRUE, 'target' => [ '#type' => 'select', '#title' => $this->t('Link target'), '#options' => [ '' => $this->t('- None -'), '_self' => $this->t('Same window (_self)'), '_blank' => $this->t('New windown (_blank)'), ], '#default_value' => $attrs['target'] ?? NULL, ], 'rel' => [ '#type' => 'textfield', '#title' => $this->t('Rel'), '#pattern' => '[a-zA-Z\-\s]+', '#default_value' => $attrs['rel'] ?? '', ], 'class' => [ '#type' => 'css_class', '#title' => $this->t('Class'), '#default_value' => $attrs['class'] ?? [], ], ]; return $form; } /** * Check if the URL string is one of the allowed special routes. * * This method checks for the following route names: * - <front> * - <nolink> * - <none> * - <button> * Which as of this writing are all allowed for URL route names. * * @param string $url_string * URL string to check for a match to the specially allowed route names. * * @return bool * TRUE if the string is one of the special Drupal recognized route names. */ protected function isAllowedRouteName($url_string) { return (bool) preg_match('/^<(?:nolink|none|front|button>)>$/i', $url_string); } /** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { $urlString = $form_state->getValue('url'); if ($urlString) { $urlString = trim($urlString); // First check if for special routes and check if it fits the format of // external or internal URLs. if (!$this->isAllowedRouteName($urlString) && !UrlHelper::isValid($urlString, TRUE)) { if ($urlString[0] != '/' || !UrlHelper::isValid($urlString)) { $form_state->setError($form['url'], $this->t('URL specified is not in the correct format. Start your URL with "https://" and use the full address for external links, or start internal links with "/".')); } } } } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { $item = $this->item; $values = $form_state->getValues(); $values['attributes'] = array_filter($values['attributes']); // Update the common settings for all menu links. $data = [ 'title' => $values['title'], 'enabled' => (bool) $values['enabled'], 'expanded' => (bool) $values['expanded'], // Only populate the attribute changes here if there were any. // For custom links, the other URL options such as query and fragment // are determined later, but shouldn't retain any previous data. 'options' => $values['attributes'] ? ['attributes' => $values['attributes']] : [], ]; if ($item) { $data['id'] = $item->id(); } // If pointing to an entity, the URL cannot be changed, the title is // optional, and only the attributes are allowed for URL options. if ($item instanceof AssociatedEntityMenuItem) { $data['entity'] = $item->getEntity(); if (empty($data['title'])) { unset($data['title']); } } else { $urlStr = trim($values['url']); if ($this->isAllowedRouteName($urlStr)) { $data['route'] = [ 'route_name' => strtolower($urlStr), ]; } else { $parsed = UrlHelper::parse($urlStr); // Add the query or fragment to the URL options if there were any. if ($parsed['query']) { $data['options']['query'] = $parsed['query']; } if ($parsed['fragment']) { $data['options']['fragment'] = $parsed['fragment']; } // Absolute path, use the URI menu field. if (UrlHelper::isExternal($parsed['path'])) { $data['uri'] = $parsed['path']; } elseif ($url = $this->pathValidator->getUrlIfValid($parsed['path'])) { if ($url->isRouted()) { $data['route'] = [ 'route_name' => $url->getRouteName(), 'route_parameters' => $url->getRouteParameters(), ]; } else { $data['uri'] = $url->getUri(); } } else { $this->messenger()->addError($this->t('Unable to save menu link due to an invalid URL.')); $form_state->setRebuild(); return; } } } $this->menuStorage->saveMenuItem($this->association, $data); // Go back to the association menu tree overview page. $form_state->setRedirectUrl(Url::fromRoute( 'association_menu.entity.menu_form', ['association' => $this->association->id()], )); } }