3 namespace Drupal\devel_generate\Plugin\DevelGenerate;
5 use Drupal\Component\Utility\Unicode;
6 use Drupal\Core\Entity\EntityStorageInterface;
7 use Drupal\Core\Extension\ModuleHandlerInterface;
8 use Drupal\Core\Form\FormStateInterface;
9 use Drupal\Core\Menu\MenuLinkTreeInterface;
10 use Drupal\Core\Menu\MenuTreeParameters;
11 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
12 use Drupal\devel_generate\DevelGenerateBase;
13 use Symfony\Component\DependencyInjection\ContainerInterface;
16 * Provides a MenuDevelGenerate plugin.
20 * label = @Translation("menus"),
21 * description = @Translation("Generate a given number of menus and menu links. Optionally delete current menus."),
23 * permission = "administer devel_generate",
27 * "title_length" = 12,
33 class MenuDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
36 * The menu tree service.
38 * @var \Drupal\Core\Menu\MenuLinkTreeInterface
40 protected $menuLinkTree;
45 * @var \Drupal\Core\Entity\EntityStorageInterface
47 protected $menuStorage;
50 * The menu link storage.
52 * @var \Drupal\Core\Entity\EntityStorageInterface
54 protected $menuLinkContentStorage;
59 * @var \Drupal\Core\Extension\ModuleHandlerInterface
61 protected $moduleHandler;
64 * Constructs a MenuDevelGenerate object.
66 * @param array $configuration
67 * A configuration array containing information about the plugin instance.
68 * @param string $plugin_id
69 * The plugin ID for the plugin instance.
70 * @param mixed $plugin_definition
71 * The plugin implementation definition.
72 * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
73 * The menu tree service.
74 * @param \Drupal\Core\Entity\EntityStorageInterface $menu_storage
76 * @param \Drupal\Core\Entity\EntityStorageInterface $menu_link_storage
78 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
81 public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuLinkTreeInterface $menu_tree, EntityStorageInterface $menu_storage, EntityStorageInterface $menu_link_storage, ModuleHandlerInterface $module_handler) {
82 parent::__construct($configuration, $plugin_id, $plugin_definition);
84 $this->menuLinkTree = $menu_tree;
85 $this->menuStorage = $menu_storage;
86 $this->menuLinkContentStorage = $menu_link_storage;
87 $this->moduleHandler = $module_handler;
93 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
94 $entity_manager = $container->get('entity.manager');
96 $configuration, $plugin_id, $plugin_definition,
97 $container->get('menu.link_tree'),
98 $entity_manager->getStorage('menu'),
99 $entity_manager->getStorage('menu_link_content'),
100 $container->get('module_handler')
107 public function settingsForm(array $form, FormStateInterface $form_state) {
108 $menu_enabled = $this->moduleHandler->moduleExists('menu_ui');
110 $menus = array('__new-menu__' => $this->t('Create new menu(s)')) + menu_ui_get_menus();
113 $menus = menu_list_system_menus();
115 $form['existing_menus'] = array(
116 '#type' => 'checkboxes',
117 '#title' => $this->t('Generate links for these menus'),
118 '#options' => $menus,
119 '#default_value' => array('__new-menu__'),
123 $form['num_menus'] = array(
125 '#title' => $this->t('Number of new menus to create'),
126 '#default_value' => $this->getSetting('num_menus'),
130 ':input[name="existing_menus[__new-menu__]"]' => array('checked' => TRUE),
135 $form['num_links'] = array(
137 '#title' => $this->t('Number of links to generate'),
138 '#default_value' => $this->getSetting('num_links'),
142 $form['title_length'] = array(
144 '#title' => $this->t('Maximum number of characters in menu and menu link names'),
145 '#description' => $this->t('The minimum length is 2.'),
146 '#default_value' => $this->getSetting('title_length'),
151 $form['link_types'] = array(
152 '#type' => 'checkboxes',
153 '#title' => $this->t('Types of links to generate'),
155 'node' => $this->t('Nodes'),
156 'front' => $this->t('Front page'),
157 'external' => $this->t('External'),
159 '#default_value' => array('node', 'front', 'external'),
162 $form['max_depth'] = array(
164 '#title' => $this->t('Maximum link depth'),
165 '#options' => range(0, $this->menuLinkTree->maxDepth()),
166 '#default_value' => floor($this->menuLinkTree->maxDepth() / 2),
169 unset($form['max_depth']['#options'][0]);
170 $form['max_width'] = array(
172 '#title' => $this->t('Maximum menu width'),
173 '#default_value' => $this->getSetting('max_width'),
174 '#description' => $this->t('Limit the width of the generated menu\'s first level of links to a certain number of items.'),
178 $form['kill'] = array(
179 '#type' => 'checkbox',
180 '#title' => $this->t('Delete existing custom generated menus and menu links before generating new ones.'),
181 '#default_value' => $this->getSetting('kill'),
190 public function generateElements(array $values) {
191 // If the create new menus checkbox is off, set the number of new menus to 0.
192 if (!isset($values['existing_menus']['__new-menu__']) || !$values['existing_menus']['__new-menu__']) {
193 $values['num_menus'] = 0;
196 // Unset the aux menu to avoid attach menu new items.
197 unset($values['existing_menus']['__new-menu__']);
200 // Delete custom menus.
201 if ($values['kill']) {
202 $this->deleteMenus();
203 $this->setMessage($this->t('Deleted existing menus and links.'));
206 // Generate new menus.
207 $new_menus = $this->generateMenus($values['num_menus'], $values['title_length']);
208 if (!empty($new_menus)) {
209 $this->setMessage($this->t('Created the following new menus: @menus', array('@menus' => implode(', ', $new_menus))));
212 // Generate new menu links.
214 if (isset($values['existing_menus'])) {
215 $menus = $menus + $values['existing_menus'];
217 $new_links = $this->generateLinks($values['num_links'], $menus, $values['title_length'], $values['link_types'], $values['max_depth'], $values['max_width']);
218 $this->setMessage($this->t('Created @count new menu links.', array('@count' => count($new_links))));
224 public function validateDrushParams($args) {
226 $link_types = array('node', 'front', 'external');
228 'num_menus' => array_shift($args),
229 'num_links' => array_shift($args),
230 'kill' => drush_get_option('kill'),
231 'pipe' => drush_get_option('pipe'),
232 'link_types' => array_combine($link_types, $link_types),
235 $max_depth = array_shift($args);
236 $max_width = array_shift($args);
237 $values['max_depth'] = $max_depth ? $max_depth : 3;
238 $values['max_width'] = $max_width ? $max_width : 8;
239 $values['title_length'] = $this->getSetting('title_length');
240 $values['existing_menus']['__new-menu__'] = TRUE;
242 if ($this->isNumber($values['num_menus']) == FALSE) {
243 return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of menus'));
245 if ($this->isNumber($values['num_links']) == FALSE) {
246 return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of links'));
248 if ($this->isNumber($values['max_depth']) == FALSE || $values['max_depth'] > 9 || $values['max_depth'] < 1) {
249 return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid maximum link depth. Use a value between 1 and 9'));
251 if ($this->isNumber($values['max_width']) == FALSE || $values['max_width'] < 1) {
252 return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid maximum menu width. Use a positive numeric value.'));
259 * Deletes custom generated menus.
261 protected function deleteMenus() {
262 if ($this->moduleHandler->moduleExists('menu_ui')) {
264 foreach (menu_ui_get_menus(FALSE) as $menu => $menu_title) {
265 if (strpos($menu, 'devel-') === 0) {
271 $menus = $this->menuStorage->loadMultiple($menu_ids);
272 $this->menuStorage->delete($menus);
276 // Delete menu links generated by devel.
277 $link_ids = $this->menuLinkContentStorage->getQuery()
278 ->condition('menu_name', 'devel', '<>')
279 ->condition('link__options', '%' . db_like('s:5:"devel";b:1') . '%', 'LIKE')
283 $links = $this->menuLinkContentStorage->loadMultiple($link_ids);
284 $this->menuLinkContentStorage->delete($links);
290 * Generates new menus.
292 * @param int $num_menus
293 * Number of menus to create.
294 * @param int $title_length
295 * (optional) Maximum length per menu name.
298 * Array containing the generated vocabularies id.
300 protected function generateMenus($num_menus, $title_length = 12) {
303 if ($this->moduleHandler->moduleExists('menu_ui')) {
304 for ($i = 1; $i <= $num_menus; $i++) {
305 $name = $this->getRandom()->word(mt_rand(2, max(2, $title_length)));
307 $menu = $this->menuStorage->create(array(
309 'id' => 'devel-' . Unicode::strtolower($name),
310 'description' => $this->t('Description of @name', array('@name' => $name)),
314 $menus[$menu->id()] = $menu->label();
322 * Generates menu links in a tree structure.
324 protected function generateLinks($num_links, $menus, $title_length, $link_types, $max_depth, $max_width) {
326 $menus = array_keys(array_filter($menus));
327 $link_types = array_keys(array_filter($link_types));
330 for ($i = 1; $i <= $num_links; $i++) {
331 // Pick a random menu.
332 $menu_name = $menus[array_rand($menus)];
333 // Build up our link.
334 $link_title = $this->getRandom()->word(mt_rand(2, max(2, $title_length)));
335 $link = $this->menuLinkContentStorage->create(array(
336 'menu_name' => $menu_name,
337 'weight' => mt_rand(-50, 50),
338 'title' => $link_title,
339 'bundle' => 'menu_link_content',
340 'description' => $this->t('Description of @title.', array('@title' => $link_title)),
342 $link->link->options = array('devel' => TRUE);
344 // For the first $max_width items, make first level links.
345 if ($i <= $max_width) {
349 // Otherwise, get a random parent menu depth.
350 $depth = mt_rand(1, max(1, $max_depth - 1));
352 // Get a random parent link from the proper depth.
354 $parameters = new MenuTreeParameters();
355 $parameters->setMinDepth($depth);
356 $parameters->setMaxDepth($depth);
357 $tree = $this->menuLinkTree->load($menu_name, $parameters);
360 $link->parent = array_rand($tree);
363 } while (!$link->parent && $depth > 0);
365 $link_type = array_rand($link_types);
366 switch ($link_types[$link_type]) {
368 // Grab a random node ID.
369 $select = db_select('node_field_data', 'n')
370 ->fields('n', array('nid', 'title'))
371 ->condition('n.status', 1)
374 // Don't put a node into the menu twice.
375 if (!empty($nids[$menu_name])) {
376 $select->condition('n.nid', $nids[$menu_name], 'NOT IN');
378 $node = $select->execute()->fetchAssoc();
379 if (isset($node['nid'])) {
380 $nids[$menu_name][] = $node['nid'];
381 $link->link->uri = 'entity:node/' . $node['nid'];
382 $link->title = $node['title'];
387 $link->link->uri = 'http://www.example.com/';
391 $link->link->uri = 'internal:/<front>';
395 $link->devel_link_type = $link_type;
401 $links[$link->id()] = $link->link_title;