/web/modules/contrib/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php
PHP | 729 lines | 441 code | 76 blank | 212 comment | 49 complexity | e5b0c86ad910b1895d1f651cb7f37269 MD5 | raw file
- <?php
- namespace Drupal\devel_generate\Plugin\DevelGenerate;
- use Drupal\comment\CommentManagerInterface;
- use Drupal\Component\Datetime\Time;
- use Drupal\Component\Render\FormattableMarkup;
- use Drupal\content_translation\ContentTranslationManagerInterface;
- use Drupal\Core\Database\Connection;
- use Drupal\Core\Datetime\DateFormatterInterface;
- use Drupal\Core\Entity\EntityStorageInterface;
- use Drupal\Core\Extension\ModuleHandlerInterface;
- use Drupal\Core\Form\FormStateInterface;
- use Drupal\Core\Language\LanguageInterface;
- use Drupal\Core\Language\LanguageManagerInterface;
- use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
- use Drupal\Core\Routing\UrlGeneratorInterface;
- use Drupal\devel_generate\DevelGenerateBase;
- use Drupal\field\Entity\FieldConfig;
- use Drupal\node\NodeInterface;
- use Drupal\path_alias\PathAliasStorage;
- use Drupal\user\UserStorageInterface;
- use Drush\Utils\StringUtils;
- use Symfony\Component\DependencyInjection\ContainerInterface;
- /**
- * Provides a ContentDevelGenerate plugin.
- *
- * @DevelGenerate(
- * id = "content",
- * label = @Translation("content"),
- * description = @Translation("Generate a given number of content. Optionally delete current content."),
- * url = "content",
- * permission = "administer devel_generate",
- * settings = {
- * "num" = 50,
- * "kill" = FALSE,
- * "max_comments" = 0,
- * "title_length" = 4,
- * "add_type_label" = FALSE
- * }
- * )
- */
- class ContentDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
- /**
- * The node storage.
- *
- * @var \Drupal\Core\Entity\EntityStorageInterface
- */
- protected $nodeStorage;
- /**
- * The node type storage.
- *
- * @var \Drupal\Core\Entity\EntityStorageInterface
- */
- protected $nodeTypeStorage;
- /**
- * The user storage.
- *
- * @var \Drupal\user\UserStorageInterface
- */
- protected $userStorage;
- /**
- * The module handler.
- *
- * @var \Drupal\Core\Extension\ModuleHandlerInterface
- */
- protected $moduleHandler;
- /**
- * The comment manager service.
- *
- * @var \Drupal\comment\CommentManagerInterface
- */
- protected $commentManager;
- /**
- * The language manager.
- *
- * @var \Drupal\Core\Language\LanguageManagerInterface
- */
- protected $languageManager;
- /**
- * The content translation manager.
- *
- * @var \Drupal\content_translation\ContentTranslationManagerInterface
- */
- protected $contentTranslationManager;
- /**
- * The url generator service.
- *
- * @var \Drupal\Core\Routing\UrlGeneratorInterface
- */
- protected $urlGenerator;
- /**
- * The alias storage.
- *
- * @var \Drupal\path_alias\PathAliasStorage
- */
- protected $aliasStorage;
- /**
- * The date formatter service.
- *
- * @var \Drupal\Core\Datetime\DateFormatterInterface
- */
- protected $dateFormatter;
- /**
- * The Drush batch flag.
- *
- * @var bool
- */
- protected $drushBatch;
- /**
- * Provides system time.
- *
- * @var \Drupal\Core\Datetime\Time
- */
- protected $time;
- /**
- * Database connection.
- *
- * @var \Drupal\Core\Database\Connection
- */
- protected $database;
- /**
- * The construct.
- *
- * @param array $configuration
- * A configuration array containing information about the plugin instance.
- * @param string $plugin_id
- * The plugin ID for the plugin instance.
- * @param array $plugin_definition
- * The plugin implementation definition.
- * @param \Drupal\Core\Entity\EntityStorageInterface $node_storage
- * The node storage.
- * @param \Drupal\Core\Entity\EntityStorageInterface $node_type_storage
- * The node type storage.
- * @param \Drupal\user\UserStorageInterface $user_storage
- * The user storage.
- * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
- * The module handler.
- * @param \Drupal\comment\CommentManagerInterface $comment_manager
- * The comment manager service.
- * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
- * The language manager.
- * @param \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager
- * The content translation manager service.
- * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
- * The url generator service.
- * @param \Drupal\path_alias\PathAliasStorage $alias_storage
- * The alias storage.
- * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
- * The date formatter service.
- * @param \Drupal\Core\Datetime\Time $time
- * Provides system time.
- * @param \Drupal\Core\Database\Connection $database
- * Database connection.
- */
- public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityStorageInterface $node_storage, EntityStorageInterface $node_type_storage, UserStorageInterface $user_storage, ModuleHandlerInterface $module_handler, CommentManagerInterface $comment_manager = NULL, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $content_translation_manager = NULL, UrlGeneratorInterface $url_generator, PathAliasStorage $alias_storage, DateFormatterInterface $date_formatter, Time $time, Connection $database) {
- parent::__construct($configuration, $plugin_id, $plugin_definition);
- $this->moduleHandler = $module_handler;
- $this->nodeStorage = $node_storage;
- $this->nodeTypeStorage = $node_type_storage;
- $this->userStorage = $user_storage;
- $this->commentManager = $comment_manager;
- $this->languageManager = $language_manager;
- $this->contentTranslationManager = $content_translation_manager;
- $this->urlGenerator = $url_generator;
- $this->aliasStorage = $alias_storage;
- $this->dateFormatter = $date_formatter;
- $this->time = $time;
- $this->database = $database;
- }
- /**
- * {@inheritdoc}
- */
- public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
- $entity_type_manager = $container->get('entity_type.manager');
- return new static(
- $configuration, $plugin_id, $plugin_definition,
- $entity_type_manager->getStorage('node'),
- $entity_type_manager->getStorage('node_type'),
- $entity_type_manager->getStorage('user'),
- $container->get('module_handler'),
- $container->has('comment.manager') ? $container->get('comment.manager') : NULL,
- $container->get('language_manager'),
- $container->has('content_translation.manager') ? $container->get('content_translation.manager') : NULL,
- $container->get('url_generator'),
- $entity_type_manager->getStorage('path_alias'),
- $container->get('date.formatter'),
- $container->get('datetime.time'),
- $container->get('database')
- );
- }
- /**
- * {@inheritdoc}
- */
- public function settingsForm(array $form, FormStateInterface $form_state) {
- $types = $this->nodeTypeStorage->loadMultiple();
- if (empty($types)) {
- $create_url = $this->urlGenerator->generateFromRoute('node.type_add');
- $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>', [':create-type' => $create_url]), 'error', FALSE);
- return;
- }
- $options = [];
- foreach ($types as $type) {
- $options[$type->id()] = [
- 'type' => ['#markup' => $type->label()],
- ];
- if ($this->commentManager) {
- $comment_fields = $this->commentManager->getFields('node');
- $map = [$this->t('Hidden'), $this->t('Closed'), $this->t('Open')];
- $fields = [];
- foreach ($comment_fields as $field_name => $info) {
- // Find all comment fields for the bundle.
- if (in_array($type->id(), $info['bundles'])) {
- $instance = FieldConfig::loadByName('node', $type->id(), $field_name);
- $default_value = $instance->getDefaultValueLiteral();
- $default_mode = reset($default_value);
- $fields[] = new FormattableMarkup('@field: @state', [
- '@field' => $instance->label(),
- '@state' => $map[$default_mode['status']],
- ]);
- }
- }
- // @todo Refactor display of comment fields.
- if (!empty($fields)) {
- $options[$type->id()]['comments'] = [
- 'data' => [
- '#theme' => 'item_list',
- '#items' => $fields,
- ],
- ];
- }
- else {
- $options[$type->id()]['comments'] = $this->t('No comment fields');
- }
- }
- }
- $header = [
- 'type' => $this->t('Content type'),
- ];
- if ($this->commentManager) {
- $header['comments'] = [
- 'data' => $this->t('Comments'),
- 'class' => [RESPONSIVE_PRIORITY_MEDIUM],
- ];
- }
- $form['node_types'] = [
- '#type' => 'tableselect',
- '#header' => $header,
- '#options' => $options,
- ];
- $form['kill'] = [
- '#type' => 'checkbox',
- '#title' => $this->t('<strong>Delete all content</strong> in these content types before generating new content.'),
- '#default_value' => $this->getSetting('kill'),
- ];
- $form['num'] = [
- '#type' => 'number',
- '#title' => $this->t('How many nodes would you like to generate?'),
- '#default_value' => $this->getSetting('num'),
- '#required' => TRUE,
- '#min' => 0,
- ];
- $options = [1 => $this->t('Now')];
- foreach ([3600, 86400, 604800, 2592000, 31536000] as $interval) {
- $options[$interval] = $this->dateFormatter->formatInterval($interval, 1) . ' ' . $this->t('ago');
- }
- $form['time_range'] = [
- '#type' => 'select',
- '#title' => $this->t('How far back in time should the nodes be dated?'),
- '#description' => $this->t('Node creation dates will be distributed randomly from the current time, back to the selected time.'),
- '#options' => $options,
- '#default_value' => 604800,
- ];
- $form['max_comments'] = [
- '#type' => $this->moduleHandler->moduleExists('comment') ? 'number' : 'value',
- '#title' => $this->t('Maximum number of comments per node.'),
- '#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.'),
- '#default_value' => $this->getSetting('max_comments'),
- '#min' => 0,
- '#access' => $this->moduleHandler->moduleExists('comment'),
- ];
- $form['title_length'] = [
- '#type' => 'number',
- '#title' => $this->t('Maximum number of words in titles'),
- '#default_value' => $this->getSetting('title_length'),
- '#required' => TRUE,
- '#min' => 1,
- '#max' => 255,
- ];
- $form['add_type_label'] = [
- '#type' => 'checkbox',
- '#title' => $this->t('Prefix the title with the content type label.'),
- '#description' => $this->t('This will not count against the maximum number of title words specified above.'),
- '#default_value' => $this->getSetting('add_type_label'),
- ];
- $form['add_alias'] = [
- '#type' => 'checkbox',
- '#disabled' => !$this->moduleHandler->moduleExists('path'),
- '#description' => $this->t('Requires path.module'),
- '#title' => $this->t('Add an url alias for each node.'),
- '#default_value' => FALSE,
- ];
- $form['add_statistics'] = [
- '#type' => 'checkbox',
- '#title' => $this->t('Add statistics for each node (node_counter table).'),
- '#default_value' => TRUE,
- '#access' => $this->moduleHandler->moduleExists('statistics'),
- ];
- // Add the language and translation options.
- $form += $this->getLanguageForm('nodes');
- // Add the user selection checkboxes.
- $author_header = [
- 'id' => $this->t('User ID'),
- 'user' => $this->t('Name'),
- 'role' => $this->t('Role(s)'),
- ];
- $author_rows = [];
- /** @var \Drupal\user\UserInterface $user */
- foreach ($this->userStorage->loadMultiple() as $user) {
- $author_rows[$user->id()] = [
- 'id' => ['#markup' => $user->id()],
- 'user' => ['#markup' => $user->getAccountName()],
- 'role' => ['#markup' => implode(", ", $user->getRoles())],
- ];
- }
- $form['authors-wrap'] = [
- '#type' => 'details',
- '#title' => $this->t('Users'),
- '#open' => FALSE,
- '#description' => $this->t('Select users for randomly assigning as authors of the generated content. Leave all unchecked to use a random selection of up to 50 users.'),
- ];
- $form['authors-wrap']['authors'] = [
- '#type' => 'tableselect',
- '#header' => $author_header,
- '#options' => $author_rows,
- ];
- $form['#redirect'] = FALSE;
- return $form;
- }
- /**
- * {@inheritdoc}
- */
- public function settingsFormValidate(array $form, FormStateInterface $form_state) {
- if (!array_filter($form_state->getValue('node_types'))) {
- $form_state->setErrorByName('node_types', $this->t('Please select at least one content type'));
- }
- }
- /**
- * {@inheritdoc}
- */
- protected function generateElements(array $values) {
- if ($this->isBatch($values['num'], $values['max_comments'])) {
- $this->generateBatchContent($values);
- }
- else {
- $this->generateContent($values);
- }
- }
- /**
- * Generate content when not in batch mode.
- *
- * This method is used when the number of elements is under 50.
- */
- private function generateContent($values) {
- $values['node_types'] = array_filter($values['node_types']);
- if (!empty($values['kill']) && $values['node_types']) {
- $this->contentKill($values);
- }
- if (!empty($values['node_types'])) {
- // Generate nodes.
- $this->develGenerateContentPreNode($values);
- $start = time();
- $values['num_translations'] = 0;
- for ($i = 1; $i <= $values['num']; $i++) {
- $this->develGenerateContentAddNode($values);
- if (isset($values['feedback']) && $i % $values['feedback'] == 0) {
- $now = time();
- $options = [
- '@feedback' => $values['feedback'],
- '@rate' => ($values['feedback'] * 60) / ($now - $start),
- ];
- $this->messenger()->addStatus(dt('Completed @feedback nodes (@rate nodes/min)', $options));
- $start = $now;
- }
- }
- }
- $this->setMessage($this->formatPlural($values['num'], 'Created 1 node', 'Created @count nodes'));
- if ($values['num_translations'] > 0) {
- $this->setMessage($this->formatPlural($values['num_translations'], 'Created 1 node translation', 'Created @count node translations'));
- }
- }
- /**
- * Generate content in batch mode.
- *
- * This method is used when the number of elements is 50 or more.
- */
- private function generateBatchContent($values) {
- // Remove unselected node types.
- $values['node_types'] = array_filter($values['node_types']);
- // If it is drushBatch then this operation is already run in the
- // self::validateDrushParams().
- if (!$this->drushBatch) {
- // Setup the batch operations and save the variables.
- $operations[] = ['devel_generate_operation',
- [$this, 'batchContentPreNode', $values],
- ];
- }
- // Add the kill operation.
- if ($values['kill']) {
- $operations[] = ['devel_generate_operation',
- [$this, 'batchContentKill', $values],
- ];
- }
- // Add the operations to create the nodes.
- for ($num = 0; $num < $values['num']; $num++) {
- $operations[] = ['devel_generate_operation',
- [$this, 'batchContentAddNode', $values],
- ];
- }
- // Set the batch.
- $batch = [
- 'title' => $this->t('Generating Content'),
- 'operations' => $operations,
- 'finished' => 'devel_generate_batch_finished',
- 'file' => drupal_get_path('module', 'devel_generate') . '/devel_generate.batch.inc',
- ];
- batch_set($batch);
- if ($this->drushBatch) {
- drush_backend_batch_process();
- }
- }
- /**
- * Batch wrapper for calling ContentPreNode.
- */
- public function batchContentPreNode($vars, &$context) {
- $context['results'] = $vars;
- $context['results']['num'] = 0;
- $context['results']['num_translations'] = 0;
- $this->develGenerateContentPreNode($context['results']);
- }
- /**
- * Batch wrapper for calling ContentAddNode.
- */
- public function batchContentAddNode($vars, &$context) {
- if ($this->drushBatch) {
- $this->develGenerateContentAddNode($vars);
- }
- else {
- $this->develGenerateContentAddNode($context['results']);
- }
- $context['results']['num']++;
- if (!empty($vars['num_translations'])) {
- $context['results']['num_translations'] += $vars['num_translations'];
- }
- }
- /**
- * Batch wrapper for calling ContentKill.
- */
- public function batchContentKill($vars, &$context) {
- if ($this->drushBatch) {
- $this->contentKill($vars);
- }
- else {
- $this->contentKill($context['results']);
- }
- }
- /**
- * {@inheritdoc}
- */
- public function validateDrushParams(array $args, array $options = []) {
- $add_language = StringUtils::csvToArray($options['languages']);
- // Intersect with the enabled languages to make sure the language args
- // passed are actually enabled.
- $valid_languages = array_keys($this->languageManager->getLanguages(LanguageInterface::STATE_ALL));
- $values['add_language'] = array_intersect($add_language, $valid_languages);
- $translate_language = StringUtils::csvToArray($options['translations']);
- $values['translate_language'] = array_intersect($translate_language, $valid_languages);
- $values['add_type_label'] = $options['add-type-label'];
- $values['kill'] = $options['kill'];
- $values['feedback'] = $options['feedback'];
- $values['title_length'] = 6;
- $values['num'] = array_shift($args);
- $values['max_comments'] = array_shift($args);
- $all_types = array_keys(node_type_get_names());
- $default_types = array_intersect(['page', 'article'], $all_types);
- $selected_types = StringUtils::csvToArray($options['bundles'] ?: $default_types);
- if (empty($selected_types)) {
- throw new \Exception(dt('No content types available'));
- }
- $values['node_types'] = array_combine($selected_types, $selected_types);
- $node_types = array_filter($values['node_types']);
- if (!empty($values['kill']) && empty($node_types)) {
- throw new \Exception(dt('To delete content, please provide the content types (--bundles)'));
- }
- // Checks for any missing content types before generating nodes.
- if (array_diff($node_types, $all_types)) {
- throw new \Exception(dt('One or more content types have been entered that don\'t exist on this site'));
- }
- $values['authors'] = is_null($options['authors']) ? [] : explode(',',
- $options['authors']);
- if ($this->isBatch($values['num'], $values['max_comments'])) {
- $this->drushBatch = TRUE;
- $this->develGenerateContentPreNode($values);
- }
- return $values;
- }
- /**
- * Determines if the content should be generated in batch mode.
- */
- protected function isBatch($content_count, $comment_count) {
- return $content_count >= 50 || $comment_count >= 10;
- }
- /**
- * Deletes all nodes of given node types.
- *
- * @param array $values
- * The input values from the settings form.
- */
- protected function contentKill(array $values) {
- $nids = $this->nodeStorage->getQuery()
- ->condition('type', $values['node_types'], 'IN')
- ->execute();
- if (!empty($nids)) {
- $nodes = $this->nodeStorage->loadMultiple($nids);
- $this->nodeStorage->delete($nodes);
- $this->setMessage($this->t('Deleted %count nodes.', ['%count' => count($nids)]));
- }
- }
- /**
- * Preprocesses $results before adding content.
- *
- * @param array $results
- * Results information.
- */
- protected function develGenerateContentPreNode(array &$results) {
- $authors = $results['authors'];
- // Remove non-selected users. !== 0 will leave the Anonymous user in if it
- // was selected on the form or entered in the drush parameters.
- $authors = array_filter($authors, function ($k) {
- return $k !== 0;
- });
- // If no users are specified then get a random set up to a maximum of 50.
- // There is no direct way randomise the selection using entity queries, so
- // we use a database query instead.
- if (empty($authors)) {
- $query = $this->database->select('users', 'u')
- ->fields('u', ['uid'])
- ->range(0, 50)
- ->orderRandom();
- $authors = $query->execute()->fetchCol();
- }
- $results['users'] = $authors;
- }
- /**
- * Create one node. Used by both batch and non-batch code branches.
- *
- * @param array $results
- * Results information.
- */
- protected function develGenerateContentAddNode(array &$results) {
- if (!isset($results['time_range'])) {
- $results['time_range'] = 0;
- }
- $users = $results['users'];
- $node_type = array_rand($results['node_types']);
- $uid = $users[array_rand($users)];
- // Add the content type label if required.
- $title_prefix = $results['add_type_label'] ? $this->nodeTypeStorage->load($node_type)->label() . ' - ' : '';
- $values = [
- 'nid' => NULL,
- 'type' => $node_type,
- 'title' => $title_prefix . $this->getRandom()->sentences(mt_rand(1, $results['title_length']), TRUE),
- 'uid' => $uid,
- 'revision' => mt_rand(0, 1),
- 'moderation_state' => 'published',
- 'status' => TRUE,
- 'promote' => mt_rand(0, 1),
- 'created' => $this->time->getRequestTime() - mt_rand(0, $results['time_range']),
- ];
- if (isset($results['add_language'])) {
- $values['langcode'] = $this->getLangcode($results['add_language']);
- }
- $node = $this->nodeStorage->create($values);
- // A flag to let hook_node_insert() implementations know that this is a
- // generated node.
- $node->devel_generate = $results;
- // Populate all fields with sample values.
- $this->populateFields($node);
- // See devel_generate_entity_insert() for actions that happen before and
- // after this save.
- $node->save();
- // Add url alias if required.
- if (!empty($results['add_alias'])) {
- $path_alias = $this->aliasStorage->create([
- 'path' => '/node/' . $node->id(),
- 'alias' => '/node-' . $node->id() . '-' . $node->bundle(),
- 'langcode' => $values['langcode'] ?? LanguageInterface::LANGCODE_NOT_SPECIFIED,
- ]);
- $path_alias->save();
- }
- // Add translations.
- if (isset($results['translate_language']) && !empty($results['translate_language'])) {
- $this->develGenerateContentAddNodeTranslation($results, $node);
- }
- }
- /**
- * Create translation for the given node.
- *
- * @param array $results
- * Results array.
- * @param \Drupal\node\NodeInterface $node
- * Node to add translations to.
- *
- * @throws \Drupal\Core\Entity\EntityStorageException
- */
- protected function develGenerateContentAddNodeTranslation(array &$results, NodeInterface $node) {
- if (is_null($this->contentTranslationManager)) {
- return;
- }
- if (!$this->contentTranslationManager->isEnabled('node', $node->getType())) {
- return;
- }
- if ($node->langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED || $node->langcode == LanguageInterface::LANGCODE_NOT_APPLICABLE) {
- return;
- }
- if (!isset($results['num_translations'])) {
- $results['num_translations'] = 0;
- }
- // Translate node to each target language.
- $skip_languages = [
- LanguageInterface::LANGCODE_NOT_SPECIFIED,
- LanguageInterface::LANGCODE_NOT_APPLICABLE,
- $node->langcode->value,
- ];
- foreach ($results['translate_language'] as $langcode) {
- if (in_array($langcode, $skip_languages)) {
- continue;
- }
- $translation_node = $node->addTranslation($langcode);
- $translation_node->devel_generate = $results;
- $translation_node->setTitle($node->getTitle() . ' (' . $langcode . ')');
- $this->populateFields($translation_node);
- $translation_node->save();
- if ($translation_node->id() > 0 && !empty($results['add_alias'])) {
- $path_alias = $this->aliasStorage->create([
- 'path' => '/node/' . $translation_node->id(),
- 'alias' => '/node-' . $translation_node->id() . '-' . $translation_node->bundle() . '-' . $langcode,
- 'langcode' => $langcode,
- ]);
- $path_alias->save();
- }
- $results['num_translations']++;
- }
- }
- }