ContentDevelGenerate.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. <?php
  2. namespace Drupal\devel_generate\Plugin\DevelGenerate;
  3. use Drupal\comment\CommentManagerInterface;
  4. use Drupal\Component\Render\FormattableMarkup;
  5. use Drupal\Core\Datetime\DateFormatterInterface;
  6. use Drupal\Core\Entity\EntityStorageInterface;
  7. use Drupal\Core\Extension\ModuleHandlerInterface;
  8. use Drupal\Core\Form\FormStateInterface;
  9. use Drupal\Core\Language\LanguageInterface;
  10. use Drupal\Core\Language\LanguageManagerInterface;
  11. use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
  12. use Drupal\Core\Routing\UrlGeneratorInterface;
  13. use Drupal\devel_generate\DevelGenerateBase;
  14. use Drupal\field\Entity\FieldConfig;
  15. use Symfony\Component\DependencyInjection\ContainerInterface;
  16. /**
  17. * Provides a ContentDevelGenerate plugin.
  18. *
  19. * @DevelGenerate(
  20. * id = "content",
  21. * label = @Translation("content"),
  22. * description = @Translation("Generate a given number of content. Optionally delete current content."),
  23. * url = "content",
  24. * permission = "administer devel_generate",
  25. * settings = {
  26. * "num" = 50,
  27. * "kill" = FALSE,
  28. * "max_comments" = 0,
  29. * "title_length" = 4
  30. * }
  31. * )
  32. */
  33. class ContentDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
  34. /**
  35. * The node storage.
  36. *
  37. * @var \Drupal\Core\Entity\EntityStorageInterface
  38. */
  39. protected $nodeStorage;
  40. /**
  41. * The node type storage.
  42. *
  43. * @var \Drupal\Core\Entity\EntityStorageInterface
  44. */
  45. protected $nodeTypeStorage;
  46. /**
  47. * The module handler.
  48. *
  49. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  50. */
  51. protected $moduleHandler;
  52. /**
  53. * The comment manager service.
  54. *
  55. * @var \Drupal\comment\CommentManagerInterface
  56. */
  57. protected $commentManager;
  58. /**
  59. * The language manager.
  60. *
  61. * @var \Drupal\Core\Language\LanguageManagerInterface
  62. */
  63. protected $languageManager;
  64. /**
  65. * The url generator service.
  66. *
  67. * @var \Drupal\Core\Routing\UrlGeneratorInterface
  68. */
  69. protected $urlGenerator;
  70. /**
  71. * The date formatter service.
  72. *
  73. * @var \Drupal\Core\Datetime\DateFormatterInterface
  74. */
  75. protected $dateFormatter;
  76. /**
  77. * @param array $configuration
  78. * A configuration array containing information about the plugin instance.
  79. * @param string $plugin_id
  80. * The plugin ID for the plugin instance.
  81. * @param array $plugin_definition
  82. * @param \Drupal\Core\Entity\EntityStorageInterface $node_storage
  83. * The node storage.
  84. * @param \Drupal\Core\Entity\EntityStorageInterface $node_type_storage
  85. * The node type storage.
  86. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  87. * The module handler.
  88. * @param \Drupal\comment\CommentManagerInterface $comment_manager
  89. * The comment manager service.
  90. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
  91. * The language manager.
  92. * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
  93. * The url generator service.
  94. * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
  95. * The date formatter service.
  96. */
  97. public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityStorageInterface $node_storage, EntityStorageInterface $node_type_storage, ModuleHandlerInterface $module_handler, CommentManagerInterface $comment_manager = NULL, LanguageManagerInterface $language_manager, UrlGeneratorInterface $url_generator, DateFormatterInterface $date_formatter) {
  98. parent::__construct($configuration, $plugin_id, $plugin_definition);
  99. $this->moduleHandler = $module_handler;
  100. $this->nodeStorage = $node_storage;
  101. $this->nodeTypeStorage = $node_type_storage;
  102. $this->commentManager = $comment_manager;
  103. $this->languageManager = $language_manager;
  104. $this->urlGenerator = $url_generator;
  105. $this->dateFormatter = $date_formatter;
  106. }
  107. /**
  108. * {@inheritdoc}
  109. */
  110. public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
  111. $entity_manager = $container->get('entity.manager');
  112. return new static(
  113. $configuration, $plugin_id, $plugin_definition,
  114. $entity_manager->getStorage('node'),
  115. $entity_manager->getStorage('node_type'),
  116. $container->get('module_handler'),
  117. $container->has('comment.manager') ? $container->get('comment.manager') : NULL,
  118. $container->get('language_manager'),
  119. $container->get('url_generator'),
  120. $container->get('date.formatter')
  121. );
  122. }
  123. /**
  124. * {@inheritdoc}
  125. */
  126. public function settingsForm(array $form, FormStateInterface $form_state) {
  127. $types = $this->nodeTypeStorage->loadMultiple();
  128. if (empty($types)) {
  129. $create_url = $this->urlGenerator->generateFromRoute('node.type_add');
  130. $this->setMessage($this->t('You do not have any content types that can be generated. <a href=":create-type">Go create a new content type</a>', array(':create-type' => $create_url)), 'error', FALSE);
  131. return;
  132. }
  133. $options = array();
  134. foreach ($types as $type) {
  135. $options[$type->id()] = array(
  136. 'type' => array('#markup' => $type->label()),
  137. );
  138. if ($this->commentManager) {
  139. $comment_fields = $this->commentManager->getFields('node');
  140. $map = array($this->t('Hidden'), $this->t('Closed'), $this->t('Open'));
  141. $fields = array();
  142. foreach ($comment_fields as $field_name => $info) {
  143. // Find all comment fields for the bundle.
  144. if (in_array($type->id(), $info['bundles'])) {
  145. $instance = FieldConfig::loadByName('node', $type->id(), $field_name);
  146. $default_value = $instance->getDefaultValueLiteral();
  147. $default_mode = reset($default_value);
  148. $fields[] = new FormattableMarkup('@field: @state', array(
  149. '@field' => $instance->label(),
  150. '@state' => $map[$default_mode['status']],
  151. ));
  152. }
  153. }
  154. // @todo Refactor display of comment fields.
  155. if (!empty($fields)) {
  156. $options[$type->id()]['comments'] = array(
  157. 'data' => array(
  158. '#theme' => 'item_list',
  159. '#items' => $fields,
  160. ),
  161. );
  162. }
  163. else {
  164. $options[$type->id()]['comments'] = $this->t('No comment fields');
  165. }
  166. }
  167. }
  168. $header = array(
  169. 'type' => $this->t('Content type'),
  170. );
  171. if ($this->commentManager) {
  172. $header['comments'] = array(
  173. 'data' => $this->t('Comments'),
  174. 'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
  175. );
  176. }
  177. $form['node_types'] = array(
  178. '#type' => 'tableselect',
  179. '#header' => $header,
  180. '#options' => $options,
  181. );
  182. $form['kill'] = array(
  183. '#type' => 'checkbox',
  184. '#title' => $this->t('<strong>Delete all content</strong> in these content types before generating new content.'),
  185. '#default_value' => $this->getSetting('kill'),
  186. );
  187. $form['num'] = array(
  188. '#type' => 'number',
  189. '#title' => $this->t('How many nodes would you like to generate?'),
  190. '#default_value' => $this->getSetting('num'),
  191. '#required' => TRUE,
  192. '#min' => 0,
  193. );
  194. $options = array(1 => $this->t('Now'));
  195. foreach (array(3600, 86400, 604800, 2592000, 31536000) as $interval) {
  196. $options[$interval] = $this->dateFormatter->formatInterval($interval, 1) . ' ' . $this->t('ago');
  197. }
  198. $form['time_range'] = array(
  199. '#type' => 'select',
  200. '#title' => $this->t('How far back in time should the nodes be dated?'),
  201. '#description' => $this->t('Node creation dates will be distributed randomly from the current time, back to the selected time.'),
  202. '#options' => $options,
  203. '#default_value' => 604800,
  204. );
  205. $form['max_comments'] = array(
  206. '#type' => $this->moduleHandler->moduleExists('comment') ? 'number' : 'value',
  207. '#title' => $this->t('Maximum number of comments per node.'),
  208. '#description' => $this->t('You must also enable comments for the content types you are generating. Note that some nodes will randomly receive zero comments. Some will receive the max.'),
  209. '#default_value' => $this->getSetting('max_comments'),
  210. '#min' => 0,
  211. '#access' => $this->moduleHandler->moduleExists('comment'),
  212. );
  213. $form['title_length'] = array(
  214. '#type' => 'number',
  215. '#title' => $this->t('Maximum number of words in titles'),
  216. '#default_value' => $this->getSetting('title_length'),
  217. '#required' => TRUE,
  218. '#min' => 1,
  219. '#max' => 255,
  220. );
  221. $form['add_alias'] = array(
  222. '#type' => 'checkbox',
  223. '#disabled' => !$this->moduleHandler->moduleExists('path'),
  224. '#description' => $this->t('Requires path.module'),
  225. '#title' => $this->t('Add an url alias for each node.'),
  226. '#default_value' => FALSE,
  227. );
  228. $form['add_statistics'] = array(
  229. '#type' => 'checkbox',
  230. '#title' => $this->t('Add statistics for each node (node_counter table).'),
  231. '#default_value' => TRUE,
  232. '#access' => $this->moduleHandler->moduleExists('statistics'),
  233. );
  234. $options = array();
  235. // We always need a language.
  236. $languages = $this->languageManager->getLanguages(LanguageInterface::STATE_ALL);
  237. foreach ($languages as $langcode => $language) {
  238. $options[$langcode] = $language->getName();
  239. }
  240. $form['add_language'] = array(
  241. '#type' => 'select',
  242. '#title' => $this->t('Set language on nodes'),
  243. '#multiple' => TRUE,
  244. '#description' => $this->t('Requires locale.module'),
  245. '#options' => $options,
  246. '#default_value' => array(
  247. $this->languageManager->getDefaultLanguage()->getId(),
  248. ),
  249. );
  250. $form['#redirect'] = FALSE;
  251. return $form;
  252. }
  253. /**
  254. * {@inheritdoc}
  255. */
  256. function settingsFormValidate(array $form, FormStateInterface $form_state) {
  257. if (!array_filter($form_state->getValue('node_types'))) {
  258. $form_state->setErrorByName('node_types', $this->t('Please select at least one content type'));
  259. }
  260. }
  261. /**
  262. * {@inheritdoc}
  263. */
  264. protected function generateElements(array $values) {
  265. if ($values['num'] <= 50 && $values['max_comments'] <= 10) {
  266. $this->generateContent($values);
  267. }
  268. else {
  269. $this->generateBatchContent($values);
  270. }
  271. }
  272. /**
  273. * Method responsible for creating content when
  274. * the number of elements is less than 50.
  275. */
  276. private function generateContent($values) {
  277. $values['node_types'] = array_filter($values['node_types']);
  278. if (!empty($values['kill']) && $values['node_types']) {
  279. $this->contentKill($values);
  280. }
  281. if (!empty($values['node_types'])) {
  282. // Generate nodes.
  283. $this->develGenerateContentPreNode($values);
  284. $start = time();
  285. for ($i = 1; $i <= $values['num']; $i++) {
  286. $this->develGenerateContentAddNode($values);
  287. if (function_exists('drush_log') && $i % drush_get_option('feedback', 1000) == 0) {
  288. $now = time();
  289. drush_log(dt('Completed @feedback nodes (@rate nodes/min)', array('@feedback' => drush_get_option('feedback', 1000), '@rate' => (drush_get_option('feedback', 1000) * 60) / ($now - $start))), 'ok');
  290. $start = $now;
  291. }
  292. }
  293. }
  294. $this->setMessage($this->formatPlural($values['num'], '1 node created.', 'Finished creating @count nodes'));
  295. }
  296. /**
  297. * Method responsible for creating content when
  298. * the number of elements is greater than 50.
  299. */
  300. private function generateBatchContent($values) {
  301. // Setup the batch operations and save the variables.
  302. $operations[] = array('devel_generate_operation', array($this, 'batchContentPreNode', $values));
  303. // Add the kill operation.
  304. if ($values['kill']) {
  305. $operations[] = array('devel_generate_operation', array($this, 'batchContentKill', $values));
  306. }
  307. // Add the operations to create the nodes.
  308. for ($num = 0; $num < $values['num']; $num ++) {
  309. $operations[] = array('devel_generate_operation', array($this, 'batchContentAddNode', $values));
  310. }
  311. // Start the batch.
  312. $batch = array(
  313. 'title' => $this->t('Generating Content'),
  314. 'operations' => $operations,
  315. 'finished' => 'devel_generate_batch_finished',
  316. 'file' => drupal_get_path('module', 'devel_generate') . '/devel_generate.batch.inc',
  317. );
  318. batch_set($batch);
  319. }
  320. public function batchContentPreNode($vars, &$context) {
  321. $context['results'] = $vars;
  322. $context['results']['num'] = 0;
  323. $this->develGenerateContentPreNode($context['results']);
  324. }
  325. public function batchContentAddNode($vars, &$context) {
  326. $this->develGenerateContentAddNode($context['results']);
  327. $context['results']['num']++;
  328. }
  329. public function batchContentKill($vars, &$context) {
  330. $this->contentKill($context['results']);
  331. }
  332. /**
  333. * {@inheritdoc}
  334. */
  335. public function validateDrushParams($args) {
  336. $add_language = drush_get_option('languages');
  337. if (!empty($add_language)) {
  338. $add_language = explode(',', str_replace(' ', '', $add_language));
  339. // Intersect with the enabled languages to make sure the language args
  340. // passed are actually enabled.
  341. $values['values']['add_language'] = array_intersect($add_language, array_keys($this->languageManager->getLanguages(LanguageInterface::STATE_ALL)));
  342. }
  343. $values['kill'] = drush_get_option('kill');
  344. $values['title_length'] = 6;
  345. $values['num'] = array_shift($args);
  346. $values['max_comments'] = array_shift($args);
  347. $all_types = array_keys(node_type_get_names());
  348. $default_types = array_intersect(array('page', 'article'), $all_types);
  349. $selected_types = _convert_csv_to_array(drush_get_option('types', $default_types));
  350. // Validates the input format for content types option.
  351. if (drush_get_option('types', $default_types) === TRUE) {
  352. return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Wrong syntax or no content type selected. The correct syntax uses "=", eg.: --types=page,article'));
  353. }
  354. if (empty($selected_types)) {
  355. return drush_set_error('DEVEL_GENERATE_NO_CONTENT_TYPES', dt('No content types available'));
  356. }
  357. $values['node_types'] = array_combine($selected_types, $selected_types);
  358. $node_types = array_filter($values['node_types']);
  359. if (!empty($values['kill']) && empty($node_types)) {
  360. return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Please provide content type (--types) in which you want to delete the content.'));
  361. }
  362. // Checks for any missing content types before generating nodes.
  363. if (array_diff($node_types, $all_types)) {
  364. return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('One or more content types have been entered that don\'t exist on this site'));
  365. }
  366. return $values;
  367. }
  368. /**
  369. * Deletes all nodes of given node types.
  370. *
  371. * @param array $values
  372. * The input values from the settings form.
  373. */
  374. protected function contentKill($values) {
  375. $nids = $this->nodeStorage->getQuery()
  376. ->condition('type', $values['node_types'], 'IN')
  377. ->execute();
  378. if (!empty($nids)) {
  379. $nodes = $this->nodeStorage->loadMultiple($nids);
  380. $this->nodeStorage->delete($nodes);
  381. $this->setMessage($this->t('Deleted %count nodes.', array('%count' => count($nids))));
  382. }
  383. }
  384. /**
  385. * Return the same array passed as parameter
  386. * but with an array of uids for the key 'users'.
  387. */
  388. protected function develGenerateContentPreNode(&$results) {
  389. // Get user id.
  390. $users = $this->getUsers();
  391. $users = array_merge($users, array('0'));
  392. $results['users'] = $users;
  393. }
  394. /**
  395. * Create one node. Used by both batch and non-batch code branches.
  396. */
  397. protected function develGenerateContentAddNode(&$results) {
  398. if (!isset($results['time_range'])) {
  399. $results['time_range'] = 0;
  400. }
  401. $users = $results['users'];
  402. $node_type = array_rand(array_filter($results['node_types']));
  403. $uid = $users[array_rand($users)];
  404. $node = $this->nodeStorage->create(array(
  405. 'nid' => NULL,
  406. 'type' => $node_type,
  407. 'title' => $this->getRandom()->sentences(mt_rand(1, $results['title_length']), TRUE),
  408. 'uid' => $uid,
  409. 'revision' => mt_rand(0, 1),
  410. 'status' => TRUE,
  411. 'promote' => mt_rand(0, 1),
  412. 'created' => REQUEST_TIME - mt_rand(0, $results['time_range']),
  413. 'langcode' => $this->getLangcode($results),
  414. ));
  415. // A flag to let hook_node_insert() implementations know that this is a
  416. // generated node.
  417. $node->devel_generate = $results;
  418. // Populate all fields with sample values.
  419. $this->populateFields($node);
  420. // See devel_generate_node_insert() for actions that happen before and after
  421. // this save.
  422. $node->save();
  423. }
  424. /**
  425. * Determine language based on $results.
  426. */
  427. protected function getLangcode($results) {
  428. if (isset($results['add_language'])) {
  429. $langcodes = $results['add_language'];
  430. $langcode = $langcodes[array_rand($langcodes)];
  431. }
  432. else {
  433. $langcode = $this->languageManager->getDefaultLanguage()->getId();
  434. }
  435. return $langcode;
  436. }
  437. /**
  438. * Retrive 50 uids from the database.
  439. */
  440. protected function getUsers() {
  441. $users = array();
  442. $result = db_query_range("SELECT uid FROM {users}", 0, 50);
  443. foreach ($result as $record) {
  444. $users[] = $record->uid;
  445. }
  446. return $users;
  447. }
  448. }