MenuDevelGenerate.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. <?php
  2. namespace Drupal\devel_generate\Plugin\DevelGenerate;
  3. use Drupal\Component\Utility\Unicode;
  4. use Drupal\Core\Entity\EntityStorageInterface;
  5. use Drupal\Core\Extension\ModuleHandlerInterface;
  6. use Drupal\Core\Form\FormStateInterface;
  7. use Drupal\Core\Menu\MenuLinkTreeInterface;
  8. use Drupal\Core\Menu\MenuTreeParameters;
  9. use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
  10. use Drupal\devel_generate\DevelGenerateBase;
  11. use Symfony\Component\DependencyInjection\ContainerInterface;
  12. /**
  13. * Provides a MenuDevelGenerate plugin.
  14. *
  15. * @DevelGenerate(
  16. * id = "menu",
  17. * label = @Translation("menus"),
  18. * description = @Translation("Generate a given number of menus and menu links. Optionally delete current menus."),
  19. * url = "menu",
  20. * permission = "administer devel_generate",
  21. * settings = {
  22. * "num_menus" = 2,
  23. * "num_links" = 50,
  24. * "title_length" = 12,
  25. * "max_width" = 6,
  26. * "kill" = FALSE,
  27. * }
  28. * )
  29. */
  30. class MenuDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
  31. /**
  32. * The menu tree service.
  33. *
  34. * @var \Drupal\Core\Menu\MenuLinkTreeInterface
  35. */
  36. protected $menuLinkTree;
  37. /**
  38. * The menu storage.
  39. *
  40. * @var \Drupal\Core\Entity\EntityStorageInterface
  41. */
  42. protected $menuStorage;
  43. /**
  44. * The menu link storage.
  45. *
  46. * @var \Drupal\Core\Entity\EntityStorageInterface
  47. */
  48. protected $menuLinkContentStorage;
  49. /**
  50. * The module handler.
  51. *
  52. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  53. */
  54. protected $moduleHandler;
  55. /**
  56. * Constructs a MenuDevelGenerate object.
  57. *
  58. * @param array $configuration
  59. * A configuration array containing information about the plugin instance.
  60. * @param string $plugin_id
  61. * The plugin ID for the plugin instance.
  62. * @param mixed $plugin_definition
  63. * The plugin implementation definition.
  64. * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
  65. * The menu tree service.
  66. * @param \Drupal\Core\Entity\EntityStorageInterface $menu_storage
  67. * The menu storage.
  68. * @param \Drupal\Core\Entity\EntityStorageInterface $menu_link_storage
  69. * The menu storage.
  70. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  71. * The module handler.
  72. */
  73. public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuLinkTreeInterface $menu_tree, EntityStorageInterface $menu_storage, EntityStorageInterface $menu_link_storage, ModuleHandlerInterface $module_handler) {
  74. parent::__construct($configuration, $plugin_id, $plugin_definition);
  75. $this->menuLinkTree = $menu_tree;
  76. $this->menuStorage = $menu_storage;
  77. $this->menuLinkContentStorage = $menu_link_storage;
  78. $this->moduleHandler = $module_handler;
  79. }
  80. /**
  81. * {@inheritdoc}
  82. */
  83. public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
  84. $entity_manager = $container->get('entity.manager');
  85. return new static(
  86. $configuration, $plugin_id, $plugin_definition,
  87. $container->get('menu.link_tree'),
  88. $entity_manager->getStorage('menu'),
  89. $entity_manager->getStorage('menu_link_content'),
  90. $container->get('module_handler')
  91. );
  92. }
  93. /**
  94. * {@inheritdoc}
  95. */
  96. public function settingsForm(array $form, FormStateInterface $form_state) {
  97. $menu_enabled = $this->moduleHandler->moduleExists('menu_ui');
  98. if ($menu_enabled) {
  99. $menus = array('__new-menu__' => $this->t('Create new menu(s)')) + menu_ui_get_menus();
  100. }
  101. else {
  102. $menus = menu_list_system_menus();
  103. }
  104. $form['existing_menus'] = array(
  105. '#type' => 'checkboxes',
  106. '#title' => $this->t('Generate links for these menus'),
  107. '#options' => $menus,
  108. '#default_value' => array('__new-menu__'),
  109. '#required' => TRUE,
  110. );
  111. if ($menu_enabled) {
  112. $form['num_menus'] = array(
  113. '#type' => 'number',
  114. '#title' => $this->t('Number of new menus to create'),
  115. '#default_value' => $this->getSetting('num_menus'),
  116. '#min' => 0,
  117. '#states' => array(
  118. 'visible' => array(
  119. ':input[name="existing_menus[__new-menu__]"]' => array('checked' => TRUE),
  120. ),
  121. ),
  122. );
  123. }
  124. $form['num_links'] = array(
  125. '#type' => 'number',
  126. '#title' => $this->t('Number of links to generate'),
  127. '#default_value' => $this->getSetting('num_links'),
  128. '#required' => TRUE,
  129. '#min' => 0,
  130. );
  131. $form['title_length'] = array(
  132. '#type' => 'number',
  133. '#title' => $this->t('Maximum number of characters in menu and menu link names'),
  134. '#description' => $this->t('The minimum length is 2.'),
  135. '#default_value' => $this->getSetting('title_length'),
  136. '#required' => TRUE,
  137. '#min' => 2,
  138. '#max' => 128,
  139. );
  140. $form['link_types'] = array(
  141. '#type' => 'checkboxes',
  142. '#title' => $this->t('Types of links to generate'),
  143. '#options' => array(
  144. 'node' => $this->t('Nodes'),
  145. 'front' => $this->t('Front page'),
  146. 'external' => $this->t('External'),
  147. ),
  148. '#default_value' => array('node', 'front', 'external'),
  149. '#required' => TRUE,
  150. );
  151. $form['max_depth'] = array(
  152. '#type' => 'select',
  153. '#title' => $this->t('Maximum link depth'),
  154. '#options' => range(0, $this->menuLinkTree->maxDepth()),
  155. '#default_value' => floor($this->menuLinkTree->maxDepth() / 2),
  156. '#required' => TRUE,
  157. );
  158. unset($form['max_depth']['#options'][0]);
  159. $form['max_width'] = array(
  160. '#type' => 'number',
  161. '#title' => $this->t('Maximum menu width'),
  162. '#default_value' => $this->getSetting('max_width'),
  163. '#description' => $this->t('Limit the width of the generated menu\'s first level of links to a certain number of items.'),
  164. '#required' => TRUE,
  165. '#min' => 0,
  166. );
  167. $form['kill'] = array(
  168. '#type' => 'checkbox',
  169. '#title' => $this->t('Delete existing custom generated menus and menu links before generating new ones.'),
  170. '#default_value' => $this->getSetting('kill'),
  171. );
  172. return $form;
  173. }
  174. /**
  175. * {@inheritdoc}
  176. */
  177. public function generateElements(array $values) {
  178. // If the create new menus checkbox is off, set the number of new menus to 0.
  179. if (!isset($values['existing_menus']['__new-menu__']) || !$values['existing_menus']['__new-menu__']) {
  180. $values['num_menus'] = 0;
  181. }
  182. else {
  183. // Unset the aux menu to avoid attach menu new items.
  184. unset($values['existing_menus']['__new-menu__']);
  185. }
  186. // Delete custom menus.
  187. if ($values['kill']) {
  188. $this->deleteMenus();
  189. $this->setMessage($this->t('Deleted existing menus and links.'));
  190. }
  191. // Generate new menus.
  192. $new_menus = $this->generateMenus($values['num_menus'], $values['title_length']);
  193. if (!empty($new_menus)) {
  194. $this->setMessage($this->t('Created the following new menus: @menus', array('@menus' => implode(', ', $new_menus))));
  195. }
  196. // Generate new menu links.
  197. $menus = $new_menus;
  198. if (isset($values['existing_menus'])) {
  199. $menus = $menus + $values['existing_menus'];
  200. }
  201. $new_links = $this->generateLinks($values['num_links'], $menus, $values['title_length'], $values['link_types'], $values['max_depth'], $values['max_width']);
  202. $this->setMessage($this->t('Created @count new menu links.', array('@count' => count($new_links))));
  203. }
  204. /**
  205. * {@inheritdoc}
  206. */
  207. public function validateDrushParams($args) {
  208. $link_types = array('node', 'front', 'external');
  209. $values = array(
  210. 'num_menus' => array_shift($args),
  211. 'num_links' => array_shift($args),
  212. 'kill' => drush_get_option('kill'),
  213. 'pipe' => drush_get_option('pipe'),
  214. 'link_types' => array_combine($link_types, $link_types),
  215. );
  216. $max_depth = array_shift($args);
  217. $max_width = array_shift($args);
  218. $values['max_depth'] = $max_depth ? $max_depth : 3;
  219. $values['max_width'] = $max_width ? $max_width : 8;
  220. $values['title_length'] = $this->getSetting('title_length');
  221. $values['existing_menus']['__new-menu__'] = TRUE;
  222. if ($this->isNumber($values['num_menus']) == FALSE) {
  223. return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of menus'));
  224. }
  225. if ($this->isNumber($values['num_links']) == FALSE) {
  226. return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of links'));
  227. }
  228. if ($this->isNumber($values['max_depth']) == FALSE || $values['max_depth'] > 9 || $values['max_depth'] < 1) {
  229. return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid maximum link depth. Use a value between 1 and 9'));
  230. }
  231. if ($this->isNumber($values['max_width']) == FALSE || $values['max_width'] < 1) {
  232. return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid maximum menu width. Use a positive numeric value.'));
  233. }
  234. return $values;
  235. }
  236. /**
  237. * Deletes custom generated menus.
  238. */
  239. protected function deleteMenus() {
  240. if ($this->moduleHandler->moduleExists('menu_ui')) {
  241. $menu_ids = array();
  242. foreach (menu_ui_get_menus(FALSE) as $menu => $menu_title) {
  243. if (strpos($menu, 'devel-') === 0) {
  244. $menu_ids[] = $menu;
  245. }
  246. }
  247. if ($menu_ids) {
  248. $menus = $this->menuStorage->loadMultiple($menu_ids);
  249. $this->menuStorage->delete($menus);
  250. }
  251. }
  252. // Delete menu links generated by devel.
  253. $link_ids = $this->menuLinkContentStorage->getQuery()
  254. ->condition('menu_name', 'devel', '<>')
  255. ->condition('link__options', '%' . db_like('s:5:"devel";b:1') . '%', 'LIKE')
  256. ->execute();
  257. if ($link_ids) {
  258. $links = $this->menuLinkContentStorage->loadMultiple($link_ids);
  259. $this->menuLinkContentStorage->delete($links);
  260. }
  261. }
  262. /**
  263. * Generates new menus.
  264. *
  265. * @param int $num_menus
  266. * Number of menus to create.
  267. * @param int $title_length
  268. * (optional) Maximum length per menu name.
  269. *
  270. * @return array
  271. * Array containing the generated vocabularies id.
  272. */
  273. protected function generateMenus($num_menus, $title_length = 12) {
  274. $menus = array();
  275. if ($this->moduleHandler->moduleExists('menu_ui')) {
  276. for ($i = 1; $i <= $num_menus; $i++) {
  277. $name = $this->getRandom()->word(mt_rand(2, max(2, $title_length)));
  278. $menu = $this->menuStorage->create(array(
  279. 'label' => $name,
  280. 'id' => 'devel-' . Unicode::strtolower($name),
  281. 'description' => $this->t('Description of @name', array('@name' => $name)),
  282. ));
  283. $menu->save();
  284. $menus[$menu->id()] = $menu->label();
  285. }
  286. }
  287. return $menus;
  288. }
  289. /**
  290. * Generates menu links in a tree structure.
  291. */
  292. protected function generateLinks($num_links, $menus, $title_length, $link_types, $max_depth, $max_width) {
  293. $links = array();
  294. $menus = array_keys(array_filter($menus));
  295. $link_types = array_keys(array_filter($link_types));
  296. $nids = array();
  297. for ($i = 1; $i <= $num_links; $i++) {
  298. // Pick a random menu.
  299. $menu_name = $menus[array_rand($menus)];
  300. // Build up our link.
  301. $link_title = $this->getRandom()->word(mt_rand(2, max(2, $title_length)));
  302. $link = $this->menuLinkContentStorage->create(array(
  303. 'menu_name' => $menu_name,
  304. 'weight' => mt_rand(-50, 50),
  305. 'title' => $link_title,
  306. 'bundle' => 'menu_link_content',
  307. 'description' => $this->t('Description of @title.', array('@title' => $link_title)),
  308. ));
  309. $link->link->options = array('devel' => TRUE);
  310. // For the first $max_width items, make first level links.
  311. if ($i <= $max_width) {
  312. $depth = 0;
  313. }
  314. else {
  315. // Otherwise, get a random parent menu depth.
  316. $depth = mt_rand(1, max(1, $max_depth - 1));
  317. }
  318. // Get a random parent link from the proper depth.
  319. do {
  320. $parameters = new MenuTreeParameters();
  321. $parameters->setMinDepth($depth);
  322. $parameters->setMaxDepth($depth);
  323. $tree = $this->menuLinkTree->load($menu_name, $parameters);
  324. if ($tree) {
  325. $link->parent = array_rand($tree);
  326. }
  327. $depth--;
  328. } while (!$link->parent && $depth > 0);
  329. $link_type = array_rand($link_types);
  330. switch ($link_types[$link_type]) {
  331. case 'node':
  332. // Grab a random node ID.
  333. $select = db_select('node_field_data', 'n')
  334. ->fields('n', array('nid', 'title'))
  335. ->condition('n.status', 1)
  336. ->range(0, 1)
  337. ->orderRandom();
  338. // Don't put a node into the menu twice.
  339. if (!empty($nids[$menu_name])) {
  340. $select->condition('n.nid', $nids[$menu_name], 'NOT IN');
  341. }
  342. $node = $select->execute()->fetchAssoc();
  343. if (isset($node['nid'])) {
  344. $nids[$menu_name][] = $node['nid'];
  345. $link->link->uri = 'entity:node/' . $node['nid'];
  346. $link->title = $node['title'];
  347. break;
  348. }
  349. case 'external':
  350. $link->link->uri = 'http://www.example.com/';
  351. break;
  352. case 'front':
  353. $link->link->uri = 'internal:/<front>';
  354. break;
  355. default:
  356. $link->devel_link_type = $link_type;
  357. break;
  358. }
  359. $link->save();
  360. $links[$link->id()] = $link->link_title;
  361. }
  362. return $links;
  363. }
  364. }