class MenuDevelGenerate

Same name and namespace in other branches
  1. 4.x devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php \Drupal\devel_generate\Plugin\DevelGenerate\MenuDevelGenerate

Provides a MenuDevelGenerate plugin.

Plugin annotation


@DevelGenerate(
  id = "menu",
  label = @Translation("menus"),
  description = @Translation("Generate a given number of menus and menu
  links. Optionally delete current menus."), url = "menu", permission =
  "administer devel_generate", settings = {
    "num_menus" = 2,
    "num_links" = 50,
    "title_length" = 12,
    "max_width" = 6,
    "kill" = FALSE,
  }
)

Hierarchy

Expanded class hierarchy of MenuDevelGenerate

File

devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php, line 32

Namespace

Drupal\devel_generate\Plugin\DevelGenerate
View source
class MenuDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
  
  /**
   * The menu tree service.
   */
  protected MenuLinkTreeInterface $menuLinkTree;
  
  /**
   * The menu storage.
   */
  protected EntityStorageInterface $menuStorage;
  
  /**
   * The menu link storage.
   */
  protected MenuLinkContentStorageInterface $menuLinkContentStorage;
  
  /**
   * Database connection.
   */
  protected Connection $database;
  
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) : static {
    $entity_type_manager = $container->get('entity_type.manager');
    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
    $instance->menuLinkTree = $container->get('menu.link_tree');
    $instance->menuStorage = $entity_type_manager->getStorage('menu');
    $instance->menuLinkContentStorage = $entity_type_manager->getStorage('menu_link_content');
    $instance->database = $container->get('database');
    return $instance;
  }
  
  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state) : array {
    $menus = array_map(static fn($menu) => $menu->label(), $this->menuStorage
      ->loadMultiple());
    asort($menus);
    $menus = [
      '__new-menu__' => $this->t('Create new menu(s)'),
    ] + $menus;
    $form['existing_menus'] = [
      '#type' => 'checkboxes',
      '#title' => $this->t('Generate links for these menus'),
      '#options' => $menus,
      '#default_value' => [
        '__new-menu__',
      ],
      '#required' => TRUE,
    ];
    $form['num_menus'] = [
      '#type' => 'number',
      '#title' => $this->t('Number of new menus to create'),
      '#default_value' => $this->getSetting('num_menus'),
      '#min' => 0,
      '#states' => [
        'visible' => [
          ':input[name="existing_menus[__new-menu__]"]' => [
            'checked' => TRUE,
          ],
        ],
      ],
    ];
    $form['num_links'] = [
      '#type' => 'number',
      '#title' => $this->t('Number of links to generate'),
      '#default_value' => $this->getSetting('num_links'),
      '#required' => TRUE,
      '#min' => 0,
    ];
    $form['title_length'] = [
      '#type' => 'number',
      '#title' => $this->t('Maximum length for menu titles and menu links'),
      '#description' => $this->t('Text will be generated at random lengths up to this value. Enter a number between 2 and 128.'),
      '#default_value' => $this->getSetting('title_length'),
      '#required' => TRUE,
      '#min' => 2,
      '#max' => 128,
    ];
    $form['link_types'] = [
      '#type' => 'checkboxes',
      '#title' => $this->t('Types of links to generate'),
      '#options' => [
        'node' => $this->t('Nodes'),
        'front' => $this->t('Front page'),
        'external' => $this->t('External'),
      ],
      '#default_value' => [
        'node',
        'front',
        'external',
      ],
      '#required' => TRUE,
    ];
    $form['max_depth'] = [
      '#type' => 'select',
      '#title' => $this->t('Maximum link depth'),
      '#options' => range(0, $this->menuLinkTree
        ->maxDepth()),
      '#default_value' => floor($this->menuLinkTree
        ->maxDepth() / 2),
      '#required' => TRUE,
    ];
    unset($form['max_depth']['#options'][0]);
    $form['max_width'] = [
      '#type' => 'number',
      '#title' => $this->t('Maximum menu width'),
      '#default_value' => $this->getSetting('max_width'),
      '#description' => $this->t("Limit the width of the generated menu's first level of links to a certain number of items."),
      '#required' => TRUE,
      '#min' => 0,
    ];
    $form['kill'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Delete existing custom generated menus and menu links before generating new ones.'),
      '#default_value' => $this->getSetting('kill'),
    ];
    return $form;
  }
  
  /**
   * {@inheritdoc}
   */
  protected function generateElements(array $values) : void {
    // If the create new menus checkbox is off, set the number of menus to 0.
    if (!isset($values['existing_menus']['__new-menu__']) || !$values['existing_menus']['__new-menu__']) {
      $values['num_menus'] = 0;
    }
    else {
      // Unset the aux menu to avoid attach menu new items.
      unset($values['existing_menus']['__new-menu__']);
    }
    // Delete custom menus.
    if ($values['kill']) {
      [
        $menus_deleted,
        $links_deleted,
      ] = $this->deleteMenus();
      $this->setMessage($this->t('Deleted @menus_deleted menu(s) and @links_deleted other link(s).', [
        '@menus_deleted' => $menus_deleted,
        '@links_deleted' => $links_deleted,
      ]));
    }
    // Generate new menus.
    $new_menus = $this->generateMenus($values['num_menus'], $values['title_length']);
    if ($new_menus !== []) {
      $this->setMessage($this->formatPlural(count($new_menus), 'Created the following 1 new menu: @menus', 'Created the following @count new menus: @menus', [
        '@menus' => implode(', ', $new_menus),
      ]));
    }
    // Generate new menu links.
    $menus = $new_menus;
    if (isset($values['existing_menus'])) {
      $menus += $values['existing_menus'];
    }
    $new_links = $this->generateLinks($values['num_links'], $menus, $values['title_length'], $values['link_types'], $values['max_depth'], $values['max_width']);
    $this->setMessage($this->formatPlural(count($new_links), 'Created 1 new menu link.', 'Created @count new menu links.'));
  }
  
  /**
   * {@inheritdoc}
   */
  public function validateDrushParams(array $args, array $options = []) : array {
    $link_types = [
      'node',
      'front',
      'external',
    ];
    $values = [
      'num_menus' => array_shift($args),
      'num_links' => array_shift($args),
      'kill' => $options['kill'],
      'link_types' => array_combine($link_types, $link_types),
    ];
    $max_depth = array_shift($args);
    $max_width = array_shift($args);
    $values['max_depth'] = $max_depth ?: 3;
    $values['max_width'] = $max_width ?: 8;
    $values['title_length'] = $this->getSetting('title_length');
    $values['existing_menus']['__new-menu__'] = TRUE;
    if ($this->isNumber($values['num_menus']) == FALSE) {
      throw new \Exception(dt('Invalid number of menus'));
    }
    if ($this->isNumber($values['num_links']) == FALSE) {
      throw new \Exception(dt('Invalid number of links'));
    }
    if ($this->isNumber($values['max_depth']) == FALSE || $values['max_depth'] > 9 || $values['max_depth'] < 1) {
      throw new \Exception(dt('Invalid maximum link depth. Use a value between 1 and 9'));
    }
    if ($this->isNumber($values['max_width']) == FALSE || $values['max_width'] < 1) {
      throw new \Exception(dt('Invalid maximum menu width. Use a positive numeric value.'));
    }
    return $values;
  }
  
  /**
   * Deletes custom generated menus.
   */
  protected function deleteMenus() : array {
    $menu_ids = [];
    if ($this->moduleHandler
      ->moduleExists('menu_ui')) {
      $all = $this->menuStorage
        ->loadMultiple();
      foreach ($all as $menu) {
        if (str_starts_with($menu->id(), 'devel-')) {
          $menu_ids[] = $menu->id();
        }
      }
      if ($menu_ids !== []) {
        $menus = $this->menuStorage
          ->loadMultiple($menu_ids);
        $this->menuStorage
          ->delete($menus);
      }
    }
    // Delete menu links in other menus, but generated by devel.
    $link_ids = $this->menuLinkContentStorage
      ->getQuery()
      ->condition('menu_name', 'devel', '<>')
      ->condition('link__options', '%' . $this->database
      ->escapeLike('s:5:"devel";b:1') . '%', 'LIKE')
      ->accessCheck(FALSE)
      ->execute();
    if ($link_ids) {
      $links = $this->menuLinkContentStorage
        ->loadMultiple($link_ids);
      $this->menuLinkContentStorage
        ->delete($links);
    }
    return [
      count($menu_ids),
      count($link_ids),
    ];
  }
  
  /**
   * Generates new menus.
   *
   * @param int $num_menus
   *   Number of menus to create.
   * @param int $title_length
   *   (optional) Maximum length of menu name.
   *
   * @return array
   *   Array containing the generated menus.
   */
  protected function generateMenus(int $num_menus, int $title_length = 12) : array {
    $menus = [];
    for ($i = 1; $i <= $num_menus; ++$i) {
      $name = $this->randomSentenceOfLength(mt_rand(2, $title_length));
      // Create a random string of random length for the menu id. The maximum
      // machine-name length is 32, so allowing for prefix 'devel-' we can have
      // up to 26 here. For safety avoid accidentally reusing the same id.
      do {
        $id = 'devel-' . $this->getRandom()
          ->word(mt_rand(2, 26));
      } while (array_key_exists($id, $menus));
      $menu = $this->menuStorage
        ->create([
        'label' => $name,
        'id' => $id,
        'description' => $this->t('Description of @name', [
          '@name' => $name,
        ]),
      ]);
      $menu->save();
      $menus[$menu->id()] = $menu->label();
    }
    return $menus;
  }
  
  /**
   * Generates menu links in a tree structure.
   *
   * @return array<int|string, string>
   *   Array containing the titles of the generated menu links.
   */
  protected function generateLinks(int $num_links, array $menus, int $title_length, array $link_types, int $max_depth, int $max_width) : array {
    $links = [];
    $menus = array_keys(array_filter($menus));
    $link_types = array_keys(array_filter($link_types));
    $nids = [];
    for ($i = 1; $i <= $num_links; ++$i) {
      // Pick a random menu.
      $menu_name = $menus[array_rand($menus)];
      // Build up our link.
      $link_title = $this->getRandom()
        ->word(mt_rand(2, max(2, $title_length)));
      /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menuLinkContent */
      $menuLinkContent = $this->menuLinkContentStorage
        ->create([
        'menu_name' => $menu_name,
        'weight' => mt_rand(-50, 50),
        'title' => $link_title,
        'bundle' => 'menu_link_content',
        'description' => $this->t('Description of @title.', [
          '@title' => $link_title,
        ]),
      ]);
      $link = $menuLinkContent->get('link');
      $options['devel'] = TRUE;
      $link->setValue([
        'options' => $options,
      ]);
      // For the first $max_width items, make first level links, otherwise, get
      // a random parent menu depth.
      $max_link_depth = $i <= $max_width ? 0 : mt_rand(1, max(1, $max_depth - 1));
      // Get a random parent link from the proper depth.
      for ($depth = $max_link_depth; $depth >= 0; --$depth) {
        $parameters = new MenuTreeParameters();
        $parameters->setMinDepth($depth);
        $parameters->setMaxDepth($depth);
        $tree = $this->menuLinkTree
          ->load($menu_name, $parameters);
        if ($tree === []) {
          continue;
        }
        $menuLinkContent->set('parent', array_rand($tree));
        break;

      }
      $link_type = array_rand($link_types);
      switch ($link_types[$link_type]) {
        case 'node':
          // Grab a random node ID.
          $select = $this->database
            ->select('node_field_data', 'n')
            ->fields('n', [
            'nid',
            'title',
          ])
            ->condition('n.status', 1)
            ->range(0, 1)
            ->orderRandom();
          // Don't put a node into the menu twice.
          if (isset($nids[$menu_name])) {
            $select->condition('n.nid', $nids[$menu_name], 'NOT IN');
          }
          $node = $select->execute()
            ->fetchAssoc();
          if (isset($node['nid'])) {
            $nids[$menu_name][] = $node['nid'];
            $link->setValue([
              'uri' => 'entity:node/' . $node['nid'],
            ]);
            $menuLinkContent->set('title', $node['title']);
            break;

          }
        case 'external':
          $link->setValue([
            'uri' => 'https://www.example.com/',
          ]);
          break;

        case 'front':
          $link->setValue([
            'uri' => 'internal:/<front>',
          ]);
          break;

        default:
          break;

      }
      $menuLinkContent->save();
      $links[$menuLinkContent->id()] = $menuLinkContent->getTitle();
    }
    return $links;
  }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 2
DependencySerializationTrait::__wakeup public function #[\ReturnTypeWillChange] 2
DevelGenerateBase::$entityFieldManager protected property The entity field manager.
DevelGenerateBase::$entityTypeManager protected property The entity type manager service.
DevelGenerateBase::$languageManager protected property The language manager. 1
DevelGenerateBase::$moduleHandler protected property The module handler. 1
DevelGenerateBase::$random protected property The random data generator.
DevelGenerateBase::$settings protected property The plugin settings.
DevelGenerateBase::csvToArray public static function Convert a csv string into an array of items.
DevelGenerateBase::generate public function Execute the instructions in common for all DevelGenerate plugin. Overrides DevelGenerateBaseInterface::generate
DevelGenerateBase::getDefaultSettings public function Returns the default settings for the plugin. Overrides DevelGenerateBaseInterface::getDefaultSettings
DevelGenerateBase::getLangcode protected function Return a language code. 1
DevelGenerateBase::getLanguageForm protected function Creates the language and translation section of the form.
DevelGenerateBase::getRandom protected function Returns the random data generator.
DevelGenerateBase::getSetting public function Returns the array of settings, including defaults for missing settings. Overrides DevelGenerateBaseInterface::getSetting
DevelGenerateBase::getSettings public function Returns the current settings for the plugin. Overrides DevelGenerateBaseInterface::getSettings
DevelGenerateBase::handleDrushParams public function
DevelGenerateBase::isNumber public static function Check if a given param is a number.
DevelGenerateBase::populateFields public function Populate the fields on a given entity with sample values.
DevelGenerateBase::randomSentenceOfLength protected function Generates a random sentence of specific length.
DevelGenerateBase::setMessage protected function Set a message for either drush or the web interface.
DevelGenerateBase::settingsFormValidate public function Form validation handler. Overrides DevelGenerateBaseInterface::settingsFormValidate 3
MenuDevelGenerate::$database protected property Database connection.
MenuDevelGenerate::$menuLinkContentStorage protected property The menu link storage.
MenuDevelGenerate::$menuLinkTree protected property The menu tree service.
MenuDevelGenerate::$menuStorage protected property The menu storage.
MenuDevelGenerate::create public static function Instantiates a new instance of this class. Overrides DevelGenerateBase::create
MenuDevelGenerate::deleteMenus protected function Deletes custom generated menus.
MenuDevelGenerate::generateElements protected function Business logic relating with each DevelGenerate plugin. Overrides DevelGenerateBase::generateElements
MenuDevelGenerate::generateLinks protected function Generates menu links in a tree structure.
MenuDevelGenerate::generateMenus protected function Generates new menus.
MenuDevelGenerate::settingsForm public function Returns the form for the plugin. Overrides DevelGenerateBase::settingsForm
MenuDevelGenerate::validateDrushParams public function Responsible for validating Drush params. Overrides DevelGenerateBaseInterface::validateDrushParams
MessengerTrait::$messenger protected property The messenger. 25
MessengerTrait::messenger public function Gets the messenger. 25
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin ID.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 2
PluginBase::getPluginId public function Gets the plugin ID of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PluginBase::__construct public function Constructs a \Drupal\Component\Plugin\PluginBase object. 87
StringTranslationTrait::$stringTranslation protected property The string translation service. 3
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.