Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
use Drupal\Core\Annotation\Translation;
|
||||
|
||||
/**
|
||||
* Defines a DevelGenerate annotation object.
|
||||
*
|
||||
* DevelGenerate handle the bulk creation of entites.
|
||||
*
|
||||
* Additional annotation keys for DevelGenerate can be defined in
|
||||
* hook_devel_generate_info_alter().
|
||||
*
|
||||
* @Annotation
|
||||
*
|
||||
* @see \Drupal\devel_generate\DevelGeneratePluginManager
|
||||
* @see \Drupal\devel_generate\DevelGenerateBaseInterface
|
||||
*/
|
||||
class DevelGenerate extends Plugin {
|
||||
/**
|
||||
* The human-readable name of the DevelGenerate type.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public Translation $label;
|
||||
|
||||
/**
|
||||
* A short description of the DevelGenerate type.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public Translation $description;
|
||||
|
||||
/**
|
||||
* A url to access the plugin settings form.
|
||||
*/
|
||||
public string $url;
|
||||
|
||||
/**
|
||||
* The permission required to access the plugin settings form.
|
||||
*/
|
||||
public string $permission;
|
||||
|
||||
/**
|
||||
* The name of the DevelGenerate class.
|
||||
*
|
||||
* This is not provided manually, it will be added by the discovery mechanism.
|
||||
*/
|
||||
public string $class;
|
||||
|
||||
/**
|
||||
* An array of settings passed to the DevelGenerate settingsForm.
|
||||
*
|
||||
* The keys are the names of the settings and the values are the default
|
||||
* values for those settings.
|
||||
*/
|
||||
public array $settings = [];
|
||||
|
||||
/**
|
||||
* Modules that should be enabled in order to make the plugin discoverable.
|
||||
*/
|
||||
public array $dependencies = [];
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\devel_generate\Attributes;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
|
||||
|
||||
/**
|
||||
* Devel generate plugin details.
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_METHOD)]
|
||||
class Generator {
|
||||
|
||||
public function __construct(
|
||||
public string $id,
|
||||
) {}
|
||||
|
||||
public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo): void {
|
||||
$args = $attribute->getArguments();
|
||||
$commandInfo->addAnnotation('pluginId', $args['id']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,343 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate;
|
||||
|
||||
use Drupal\Component\Utility\Random;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use JetBrains\PhpStorm\Deprecated;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a base DevelGenerate plugin implementation.
|
||||
*/
|
||||
abstract class DevelGenerateBase extends PluginBase implements DevelGenerateBaseInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager service.
|
||||
*/
|
||||
protected EntityTypeManagerInterface $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*/
|
||||
protected LanguageManagerInterface $languageManager;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*/
|
||||
protected ModuleHandlerInterface $moduleHandler;
|
||||
|
||||
/**
|
||||
* The entity field manager.
|
||||
*/
|
||||
protected EntityFieldManagerInterface $entityFieldManager;
|
||||
|
||||
/**
|
||||
* The plugin settings.
|
||||
*/
|
||||
protected array $settings = [];
|
||||
|
||||
/**
|
||||
* The random data generator.
|
||||
*/
|
||||
protected ?Random $random = NULL;
|
||||
|
||||
/**
|
||||
* Instantiates a new instance of this class.
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
|
||||
$instance = new static($configuration, $plugin_id, $plugin_definition);
|
||||
$instance->entityTypeManager = $container->get('entity_type.manager');
|
||||
$instance->languageManager = $container->get('language_manager');
|
||||
$instance->moduleHandler = $container->get('module_handler');
|
||||
$instance->stringTranslation = $container->get('string_translation');
|
||||
$instance->entityFieldManager = $container->get('entity_field.manager');
|
||||
$instance->setMessenger($container->get('messenger'));
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSetting(string $key) {
|
||||
// Merge defaults if we have no value for the key.
|
||||
if (!array_key_exists($key, $this->settings)) {
|
||||
$this->settings = $this->getDefaultSettings();
|
||||
}
|
||||
|
||||
return $this->settings[$key] ?? NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultSettings(): array {
|
||||
$definition = $this->getPluginDefinition();
|
||||
return $definition['settings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSettings(): array {
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsFormValidate(array $form, FormStateInterface $form_state): void {
|
||||
// Validation is optional.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate(array $values): void {
|
||||
$this->generateElements($values);
|
||||
$this->messenger()->addMessage('Generate process complete.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Business logic relating with each DevelGenerate plugin.
|
||||
*
|
||||
* @param array $values
|
||||
* The input values from the settings form.
|
||||
*/
|
||||
protected function generateElements(array $values): void {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the fields on a given entity with sample values.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to be enriched with sample field values.
|
||||
* @param array $skip
|
||||
* A list of field names to avoid when populating.
|
||||
* @param array $base
|
||||
* A list of base field names to populate.
|
||||
*/
|
||||
public function populateFields(EntityInterface $entity, array $skip = [], array $base = []): void {
|
||||
if (!$entity->getEntityType()->entityClassImplements(FieldableEntityInterface::class)) {
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
$instances = $this->entityFieldManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle());
|
||||
$instances = array_diff_key($instances, array_flip($skip));
|
||||
foreach ($instances as $instance) {
|
||||
$field_storage = $instance->getFieldStorageDefinition();
|
||||
$field_name = $field_storage->getName();
|
||||
if ($field_storage->isBaseField() && !in_array($field_name, $base)) {
|
||||
// Skip base field unless specifically requested.
|
||||
continue;
|
||||
}
|
||||
|
||||
$max = $field_storage->getCardinality();
|
||||
$cardinality = $max;
|
||||
if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
|
||||
// Just an arbitrary number for 'unlimited'.
|
||||
$max = random_int(1, 3);
|
||||
}
|
||||
|
||||
$entity->$field_name->generateSampleItems($max);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleDrushParams($args) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a message for either drush or the web interface.
|
||||
*
|
||||
* @param string|\Drupal\Component\Render\MarkupInterface $msg
|
||||
* The message to display.
|
||||
* @param string $type
|
||||
* (optional) The message type, as defined in MessengerInterface. Defaults
|
||||
* to MessengerInterface::TYPE_STATUS.
|
||||
*/
|
||||
#[Deprecated(reason: 'Use the messenger trait directly.')]
|
||||
protected function setMessage($msg, string $type = MessengerInterface::TYPE_STATUS): void {
|
||||
$this->messenger()->addMessage($msg, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given param is a number.
|
||||
*
|
||||
* @param mixed $number
|
||||
* The parameter to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the parameter is a number, FALSE otherwise.
|
||||
*/
|
||||
public static function isNumber(mixed $number): bool {
|
||||
if ($number === NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return is_numeric($number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the random data generator.
|
||||
*
|
||||
* @return \Drupal\Component\Utility\Random
|
||||
* The random data generator.
|
||||
*/
|
||||
protected function getRandom(): Random {
|
||||
if (!$this->random instanceof Random) {
|
||||
$this->random = new Random();
|
||||
}
|
||||
|
||||
return $this->random;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random sentence of specific length.
|
||||
*
|
||||
* Words are randomly selected with length from 2 up to the optional parameter
|
||||
* $max_word_length. The first word is capitalised. No ending period is added.
|
||||
*
|
||||
* @param int $sentence_length
|
||||
* The total length of the sentence, including the word-separating spaces.
|
||||
* @param int $max_word_length
|
||||
* (optional) Maximum length of each word. Defaults to 8.
|
||||
*
|
||||
* @return string
|
||||
* A sentence of the required length.
|
||||
*/
|
||||
protected function randomSentenceOfLength(int $sentence_length, int $max_word_length = 8): string {
|
||||
// Maximum word length cannot be longer than the sentence length.
|
||||
$max_word_length = min($sentence_length, $max_word_length);
|
||||
$words = [];
|
||||
$remainder = $sentence_length;
|
||||
do {
|
||||
// If near enough to the end then generate the exact length word to fit.
|
||||
// Otherwise, the remaining space cannot be filled with one word, so
|
||||
// choose a random length, short enough for a following word of at least
|
||||
// minimum length.
|
||||
$next_word = $remainder <= $max_word_length ? $remainder : mt_rand(2, min($max_word_length, $remainder - 3));
|
||||
|
||||
$words[] = $this->getRandom()->word($next_word);
|
||||
$remainder = $remainder - $next_word - 1;
|
||||
} while ($remainder > 0);
|
||||
|
||||
return ucfirst(implode(' ', $words));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the language and translation section of the form.
|
||||
*
|
||||
* This is used by both Content and Term generation.
|
||||
*
|
||||
* @param string $items
|
||||
* The name of the things that are being generated - 'nodes' or 'terms'.
|
||||
*
|
||||
* @return array
|
||||
* The language details section of the form.
|
||||
*/
|
||||
protected function getLanguageForm(string $items): array {
|
||||
// We always need a language, even if the language module is not installed.
|
||||
$options = [];
|
||||
$languages = $this->languageManager->getLanguages(LanguageInterface::STATE_CONFIGURABLE);
|
||||
foreach ($languages as $langcode => $language) {
|
||||
$options[$langcode] = $language->getName();
|
||||
}
|
||||
|
||||
$language_module_exists = $this->moduleHandler->moduleExists('language');
|
||||
$translation_module_exists = $this->moduleHandler->moduleExists('content_translation');
|
||||
|
||||
$form['language'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Language'),
|
||||
'#open' => $language_module_exists,
|
||||
];
|
||||
$form['language']['add_language'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Select the primary language(s) for @items', ['@items' => $items]),
|
||||
'#multiple' => TRUE,
|
||||
'#description' => $language_module_exists ? '' : $this->t('Disabled - requires Language module'),
|
||||
'#options' => $options,
|
||||
'#default_value' => [
|
||||
$this->languageManager->getDefaultLanguage()->getId(),
|
||||
],
|
||||
'#disabled' => !$language_module_exists,
|
||||
];
|
||||
$form['language']['translate_language'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Select the language(s) for translated @items', ['@items' => $items]),
|
||||
'#multiple' => TRUE,
|
||||
'#description' => $translation_module_exists ? $this->t('Translated @items will be created for each language selected.', ['@items' => $items]) : $this->t('Disabled - requires Content Translation module.'),
|
||||
'#options' => $options,
|
||||
'#disabled' => !$translation_module_exists,
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a language code.
|
||||
*
|
||||
* @param array $add_language
|
||||
* Optional array of language codes from which to select one at random.
|
||||
* If empty then return the site's default language.
|
||||
*
|
||||
* @return string
|
||||
* The language code to use.
|
||||
*/
|
||||
protected function getLangcode(array $add_language): string {
|
||||
if ($add_language === []) {
|
||||
return $this->languageManager->getDefaultLanguage()->getId();
|
||||
}
|
||||
|
||||
return $add_language[array_rand($add_language)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a csv string into an array of items.
|
||||
*
|
||||
* Borrowed from Drush.
|
||||
*
|
||||
* @param string|array|null $args
|
||||
* A simple csv string; e.g. 'a,b,c'
|
||||
* or a simple list of items; e.g. array('a','b','c')
|
||||
* or some combination; e.g. array('a,b','c') or array('a,','b,','c,').
|
||||
*/
|
||||
public static function csvToArray($args): array {
|
||||
if ($args === NULL) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 1: implode(',',$args) converts from array('a,','b,','c,') to 'a,,b,,c,'
|
||||
// 2: explode(',', ...) converts to array('a','','b','','c','')
|
||||
// 3: array_filter(...) removes the empty items
|
||||
// 4: array_map(...) trims extra whitespace from each item
|
||||
// (handles csv strings with extra whitespace, e.g. 'a, b, c')
|
||||
//
|
||||
$args = is_array($args) ? implode(',', array_map('strval', $args)) : (string) $args;
|
||||
return array_map('trim', array_filter(explode(',', $args)));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Base interface definition for "DevelGenerate" plugins.
|
||||
*
|
||||
* This interface details base wrapping methods that most DevelGenerate
|
||||
* implementations will want to directly inherit from
|
||||
* Drupal\devel_generate\DevelGenerateBase.
|
||||
*
|
||||
* DevelGenerate implementation plugins should have their own settingsForm() and
|
||||
* generateElements() to achieve their own behaviour.
|
||||
*/
|
||||
interface DevelGenerateBaseInterface extends PluginInspectionInterface {
|
||||
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
/**
|
||||
* Returns the array of settings, including defaults for missing settings.
|
||||
*
|
||||
* @param string $key
|
||||
* The setting name.
|
||||
*
|
||||
* @return array|int|string|bool|null
|
||||
* The setting.
|
||||
*/
|
||||
public function getSetting(string $key);
|
||||
|
||||
/**
|
||||
* Returns the default settings for the plugin.
|
||||
*
|
||||
* @return array
|
||||
* The array of default setting values, keyed by setting names.
|
||||
*/
|
||||
public function getDefaultSettings(): array;
|
||||
|
||||
/**
|
||||
* Returns the current settings for the plugin.
|
||||
*
|
||||
* @return array
|
||||
* The array of current setting values, keyed by setting names.
|
||||
*/
|
||||
public function getSettings(): array;
|
||||
|
||||
/**
|
||||
* Returns the form for the plugin.
|
||||
*
|
||||
* @return array
|
||||
* The array of default setting values, keyed by setting names.
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state): array;
|
||||
|
||||
/**
|
||||
* Form validation handler.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public function settingsFormValidate(array $form, FormStateInterface $form_state): void;
|
||||
|
||||
/**
|
||||
* Execute the instructions in common for all DevelGenerate plugin.
|
||||
*
|
||||
* @param array $values
|
||||
* The input values from the settings form.
|
||||
*/
|
||||
public function generate(array $values): void;
|
||||
|
||||
/**
|
||||
* Responsible for validating Drush params.
|
||||
*
|
||||
* @param array $args
|
||||
* The command arguments.
|
||||
* @param array $options
|
||||
* The commend options.
|
||||
*
|
||||
* @return array
|
||||
* An array of values ready to be used for generateElements().
|
||||
*/
|
||||
public function validateDrushParams(array $args, array $options = []): array;
|
||||
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate;
|
||||
|
||||
/**
|
||||
* DevelGenerateException extending Generic Plugin exception class.
|
||||
*/
|
||||
class DevelGenerateException extends \Exception {
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides dynamic permissions of the filter module.
|
||||
*/
|
||||
class DevelGeneratePermissions implements ContainerInjectionInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The plugin manager.
|
||||
*/
|
||||
protected DevelGeneratePluginManager $develGeneratePluginManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container): self {
|
||||
$instance = new self();
|
||||
$instance->develGeneratePluginManager = $container->get('plugin.manager.develgenerate');
|
||||
$instance->stringTranslation = $container->get('string_translation');
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* A permissions' callback.
|
||||
*
|
||||
* @see devel_generate.permissions.yml
|
||||
*
|
||||
* @return array
|
||||
* An array of permissions for all plugins.
|
||||
*/
|
||||
public function permissions(): array {
|
||||
$permissions = [];
|
||||
$devel_generate_plugins = $this->develGeneratePluginManager->getDefinitions();
|
||||
foreach ($devel_generate_plugins as $plugin) {
|
||||
$permission = $plugin['permission'];
|
||||
$permissions[$permission] = [
|
||||
'title' => $this->t('@permission', ['@permission' => $permission]),
|
||||
];
|
||||
}
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Drupal\devel_generate\Annotation\DevelGenerate;
|
||||
|
||||
/**
|
||||
* Plugin type manager for DevelGenerate plugins.
|
||||
*/
|
||||
class DevelGeneratePluginManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*/
|
||||
protected EntityTypeManagerInterface $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The messenger service.
|
||||
*/
|
||||
protected MessengerInterface $messenger;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*/
|
||||
protected LanguageManagerInterface $languageManager;
|
||||
|
||||
/**
|
||||
* The translation manager.
|
||||
*/
|
||||
protected TranslationInterface $stringTranslation;
|
||||
|
||||
/**
|
||||
* Constructs a DevelGeneratePluginManager object.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger service.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The translation manager.
|
||||
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
|
||||
* The entity field manager.
|
||||
*/
|
||||
public function __construct(
|
||||
\Traversable $namespaces,
|
||||
CacheBackendInterface $cache_backend,
|
||||
ModuleHandlerInterface $module_handler,
|
||||
EntityTypeManagerInterface $entity_type_manager,
|
||||
MessengerInterface $messenger,
|
||||
LanguageManagerInterface $language_manager,
|
||||
TranslationInterface $string_translation,
|
||||
protected EntityFieldManagerInterface $entityFieldManager,
|
||||
) {
|
||||
parent::__construct('Plugin/DevelGenerate', $namespaces, $module_handler, NULL, DevelGenerate::class);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->messenger = $messenger;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->stringTranslation = $string_translation;
|
||||
$this->alterInfo('devel_generate_info');
|
||||
$this->setCacheBackend($cache_backend, 'devel_generate_plugins');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function findDefinitions(): array {
|
||||
$definitions = [];
|
||||
foreach (parent::findDefinitions() as $plugin_id => $plugin_definition) {
|
||||
$plugin_available = TRUE;
|
||||
foreach ($plugin_definition['dependencies'] as $module_name) {
|
||||
// If a plugin defines module dependencies and at least one module is
|
||||
// not installed don't make this plugin available.
|
||||
if (!$this->moduleHandler->moduleExists($module_name)) {
|
||||
$plugin_available = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($plugin_available) {
|
||||
$definitions[$plugin_id] = $plugin_definition;
|
||||
}
|
||||
}
|
||||
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate\Drush\Commands;
|
||||
|
||||
use Consolidation\AnnotatedCommand\CommandData;
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Drupal\devel_generate\Attributes\Generator;
|
||||
use Drupal\devel_generate\DevelGenerateBaseInterface;
|
||||
use Drupal\devel_generate\DevelGeneratePluginManager;
|
||||
use Drush\Attributes as CLI;
|
||||
use Drush\Commands\AutowireTrait;
|
||||
use Drush\Commands\DrushCommands;
|
||||
|
||||
/**
|
||||
* Provide Drush commands for all the core Devel Generate plugins.
|
||||
*
|
||||
* For commands that are parts of modules, Drush expects to find commandfiles in
|
||||
* __MODULE__/src/Drush/Commands, and the namespace is Drupal/__MODULE__/Drush/Commands.
|
||||
*/
|
||||
final class DevelGenerateCommands extends DrushCommands {
|
||||
|
||||
use AutowireTrait;
|
||||
|
||||
const USERS = 'devel-generate:users';
|
||||
|
||||
const TERMS = 'devel-generate:terms';
|
||||
|
||||
const VOCABS = 'devel-generate:vocabs';
|
||||
|
||||
const MENUS = 'devel-generate:menus';
|
||||
|
||||
const CONTENT = 'devel-generate:content';
|
||||
|
||||
const BLOCK_CONTENT = 'devel-generate:block-content';
|
||||
|
||||
const MEDIA = 'devel-generate:media';
|
||||
|
||||
/**
|
||||
* The plugin instance.
|
||||
*/
|
||||
private DevelGenerateBaseInterface $pluginInstance;
|
||||
|
||||
/**
|
||||
* The Generate plugin parameters.
|
||||
*/
|
||||
private array $parameters;
|
||||
|
||||
/**
|
||||
* DevelGenerateCommands constructor.
|
||||
*
|
||||
* @param \Drupal\devel_generate\DevelGeneratePluginManager $manager
|
||||
* The DevelGenerate plugin manager.
|
||||
*/
|
||||
public function __construct(protected DevelGeneratePluginManager $manager) {
|
||||
parent::__construct();
|
||||
$this->setManager($manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DevelGenerate plugin manager.
|
||||
*
|
||||
* @return \Drupal\devel_generate\DevelGeneratePluginManager
|
||||
* The DevelGenerate plugin manager.
|
||||
*/
|
||||
public function getManager(): DevelGeneratePluginManager {
|
||||
return $this->manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DevelGenerate plugin manager.
|
||||
*
|
||||
* @param \Drupal\devel_generate\DevelGeneratePluginManager $manager
|
||||
* The DevelGenerate plugin manager.
|
||||
*/
|
||||
public function setManager(DevelGeneratePluginManager $manager): void {
|
||||
$this->manager = $manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DevelGenerate plugin instance.
|
||||
*
|
||||
* @return \Drupal\devel_generate\DevelGenerateBaseInterface
|
||||
* The DevelGenerate plugin instance.
|
||||
*/
|
||||
public function getPluginInstance(): DevelGenerateBaseInterface {
|
||||
return $this->pluginInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DevelGenerate plugin instance.
|
||||
*
|
||||
* @param mixed $pluginInstance
|
||||
* The DevelGenerate plugin instance.
|
||||
*/
|
||||
public function setPluginInstance(mixed $pluginInstance): void {
|
||||
$this->pluginInstance = $pluginInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DevelGenerate plugin parameters.
|
||||
*
|
||||
* @return array
|
||||
* The plugin parameters.
|
||||
*/
|
||||
public function getParameters(): array {
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DevelGenerate plugin parameters.
|
||||
*
|
||||
* @param array $parameters
|
||||
* The plugin parameters.
|
||||
*/
|
||||
public function setParameters(array $parameters): void {
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create users.
|
||||
*/
|
||||
#[CLI\Command(name: self::USERS, aliases: ['genu', 'devel-generate-users'])]
|
||||
#[CLI\Argument(name: 'num', description: 'Number of users to generate.')]
|
||||
#[CLI\Option(name: 'kill', description: 'Delete all users before generating new ones.')]
|
||||
#[CLI\Option(name: 'roles', description: 'A comma delimited list of role IDs for new users. Don\'t specify <info>authenticated</info>.')]
|
||||
#[CLI\Option(name: 'pass', description: 'Specify a password to be set for all generated users.')]
|
||||
#[Generator(id: 'user')]
|
||||
public function users(string|int $num = 50, array $options = ['kill' => FALSE, 'roles' => self::REQ]): void {
|
||||
// @todo pass $options to the plugins.
|
||||
$this->generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create terms in specified vocabulary.
|
||||
*/
|
||||
#[CLI\Command(name: self::TERMS, aliases: ['gent', 'devel-generate-terms'])]
|
||||
#[CLI\Argument(name: 'num', description: 'Number of terms to generate.')]
|
||||
#[CLI\Option(name: 'kill', description: 'Delete all terms in these vocabularies before generating new ones.')]
|
||||
#[CLI\Option(name: 'bundles', description: 'A comma-delimited list of machine names for the vocabularies where terms will be created.')]
|
||||
#[CLI\Option(name: 'feedback', description: 'An integer representing interval for insertion rate logging.')]
|
||||
#[CLI\Option(name: 'languages', description: 'A comma-separated list of language codes')]
|
||||
#[CLI\Option(name: 'translations', description: 'A comma-separated list of language codes for translations.')]
|
||||
#[CLI\Option(name: 'min-depth', description: 'The minimum depth of hierarchy for the new terms.')]
|
||||
#[CLI\Option(name: 'max-depth', description: 'The maximum depth of hierarchy for the new terms.')]
|
||||
#[Generator(id: 'term')]
|
||||
public function terms(?string $num = '50', array $options = ['kill' => FALSE, 'bundles' => self::REQ, 'feedback' => '1000', 'languages' => self::REQ, 'translations' => self::REQ, 'min-depth' => '1', 'max-depth' => '4']): void {
|
||||
$this->generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create vocabularies.
|
||||
*/
|
||||
#[CLI\Command(name: self::VOCABS, aliases: ['genv', 'devel-generate-vocabs'])]
|
||||
#[CLI\Argument(name: 'num', description: 'Number of vocabularies to generate.')]
|
||||
#[Generator(id: 'vocabulary')]
|
||||
#[CLI\ValidateModulesEnabled(modules: ['taxonomy'])]
|
||||
#[CLI\Option(name: 'kill', description: 'Delete all vocabs before generating new ones.')]
|
||||
public function vocabs(?string $num = '1', array $options = ['kill' => FALSE]): void {
|
||||
$this->generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menus.
|
||||
*/
|
||||
#[CLI\Command(name: self::MENUS, aliases: ['genm', 'devel-generate-menus'])]
|
||||
#[CLI\Argument(name: 'number_menus', description: 'Number of menus to generate.')]
|
||||
#[CLI\Argument(name: 'number_links', description: 'Number of links to generate.')]
|
||||
#[CLI\Argument(name: 'max_depth', description: 'Max link depth.')]
|
||||
#[CLI\Argument(name: 'max_width', description: 'Max width of first level of links.')]
|
||||
#[CLI\Option(name: 'kill', description: 'Delete any menus and menu links previously created by devel_generate before generating new ones.')]
|
||||
#[Generator(id: 'menu')]
|
||||
public function menus(?string $number_menus = '2', ?string $number_links = '50', ?string $max_depth = '3', string $max_width = '8', array $options = ['kill' => FALSE]): void {
|
||||
$this->generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create content.
|
||||
*/
|
||||
#[CLI\Command(name: self::CONTENT, aliases: ['genc', 'devel-generate-content'])]
|
||||
#[CLI\ValidateModulesEnabled(modules: ['node'])]
|
||||
#[CLI\Argument(name: 'num', description: 'Number of nodes to generate.')]
|
||||
#[CLI\Argument(name: 'max_comments', description: 'Maximum number of comments to generate.')]
|
||||
#[CLI\Option(name: 'kill', description: 'Delete all content before generating new content.')]
|
||||
#[CLI\Option(name: 'bundles', description: 'A comma-delimited list of content types to create.')]
|
||||
#[CLI\Option(name: 'authors', description: 'A comma delimited list of authors ids. Defaults to all users.')]
|
||||
#[CLI\Option(name: 'roles', description: 'A comma delimited list of role machine names to filter the random selection of users. Defaults to all roles.')]
|
||||
#[CLI\Option(name: 'feedback', description: 'An integer representing interval for insertion rate logging.')]
|
||||
#[CLI\Option(name: 'skip-fields', description: 'A comma delimited list of fields to omit when generating random values')]
|
||||
#[CLI\Option(name: 'base-fields', description: 'A comma delimited list of base field names to populate')]
|
||||
#[CLI\Option(name: 'languages', description: 'A comma-separated list of language codes')]
|
||||
#[CLI\Option(name: 'translations', description: 'A comma-separated list of language codes for translations.')]
|
||||
#[CLI\Option(name: 'add-type-label', description: 'Add the content type label to the front of the node title')]
|
||||
#[Generator(id: 'content')]
|
||||
public function content(string $num = '50', ?string $max_comments = '0', array $options = ['kill' => FALSE, 'bundles' => 'page,article', 'authors' => self::REQ, 'roles' => self::REQ, 'feedback' => 1000, 'skip-fields' => self::REQ, 'base-fields' => self::REQ, 'languages' => self::REQ, 'translations' => self::REQ, 'add-type-label' => FALSE]): void {
|
||||
$this->generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Block content blocks.
|
||||
*/
|
||||
#[CLI\Command(name: self::BLOCK_CONTENT, aliases: ['genbc', 'devel-generate-block-content'])]
|
||||
#[CLI\ValidateModulesEnabled(modules: ['block_content'])]
|
||||
#[CLI\Argument(name: 'num', description: 'Number of blocks to generate.')]
|
||||
#[CLI\Option(name: 'kill', description: 'Delete all block content before generating new.')]
|
||||
#[CLI\Option(name: 'block_types', description: 'A comma-delimited list of block content types to create.')]
|
||||
#[CLI\Option(name: 'authors', description: 'A comma delimited list of authors ids. Defaults to all users.')]
|
||||
#[CLI\Option(name: 'feedback', description: 'An integer representing interval for insertion rate logging.')]
|
||||
#[CLI\Option(name: 'skip-fields', description: 'A comma delimited list of fields to omit when generating random values')]
|
||||
#[CLI\Option(name: 'base-fields', description: 'A comma delimited list of base field names to populate')]
|
||||
#[CLI\Option(name: 'languages', description: 'A comma-separated list of language codes')]
|
||||
#[CLI\Option(name: 'translations', description: 'A comma-separated list of language codes for translations.')]
|
||||
#[CLI\Option(name: 'add-type-label', description: 'Add the block type label to the front of the node title')]
|
||||
#[CLI\Option(name: 'reusable', description: 'Create re-usable blocks. Disable for inline Layout Builder blocks, for example.')]
|
||||
#[Generator(id: 'block_content')]
|
||||
public function blockContent(?string $num = '50', array $options = ['kill' => FALSE, 'block_types' => 'basic', 'feedback' => 1000, 'skip-fields' => self::REQ, 'base-fields' => self::REQ, 'languages' => self::REQ, 'translations' => self::REQ, 'add-type-label' => FALSE, 'reusable' => TRUE]): void {
|
||||
$this->generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create media items.
|
||||
*/
|
||||
#[CLI\Command(name: self::MEDIA, aliases: ['genmd', 'devel-generate-media'])]
|
||||
#[CLI\Argument(name: 'num', description: 'Number of media to generate.')]
|
||||
#[CLI\Option(name: 'kill', description: 'Delete all media items before generating new.')]
|
||||
#[CLI\Option(name: 'media_types', description: 'A comma-delimited list of media types to create.')]
|
||||
#[CLI\Option(name: 'feedback', description: 'An integer representing interval for insertion rate logging.')]
|
||||
#[CLI\Option(name: 'skip-fields', description: 'A comma delimited list of fields to omit when generating random values')]
|
||||
#[CLI\Option(name: 'base-fields', description: 'A comma delimited list of base field names to populate')]
|
||||
#[CLI\Option(name: 'languages', description: 'A comma-separated list of language codes')]
|
||||
#[CLI\ValidateModulesEnabled(modules: ['media'])]
|
||||
#[Generator(id: 'media')]
|
||||
public function media(?string $num = '50', array $options = ['kill' => FALSE, 'media-types' => self::REQ, 'feedback' => 1000, 'skip-fields' => self::REQ, 'languages' => self::REQ, 'base-fields' => self::REQ]): void {
|
||||
$this->generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* The standard drush validate hook.
|
||||
*
|
||||
* @param \Consolidation\AnnotatedCommand\CommandData $commandData
|
||||
* The data sent from the drush command.
|
||||
*/
|
||||
#[CLI\Hook(HookManager::ARGUMENT_VALIDATOR)]
|
||||
public function validate(CommandData $commandData): void {
|
||||
$manager = $this->manager;
|
||||
$args = $commandData->input()->getArguments();
|
||||
// The command name is the first argument but we do not need this.
|
||||
array_shift($args);
|
||||
/** @var \Drupal\devel_generate\DevelGenerateBaseInterface $instance */
|
||||
$instance = $manager->createInstance($commandData->annotationData()->get('pluginId'), []);
|
||||
$this->setPluginInstance($instance);
|
||||
$parameters = $instance->validateDrushParams($args, $commandData->input()->getOptions());
|
||||
$this->setParameters($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for calling the plugin instance generate function.
|
||||
*/
|
||||
public function generate(): void {
|
||||
$instance = $this->pluginInstance;
|
||||
$instance->generate($this->parameters);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate\Form;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\devel_generate\DevelGenerateBaseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a form that allows privileged users to generate entities.
|
||||
*/
|
||||
class DevelGenerateForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The manager to be used for instantiating plugins.
|
||||
*/
|
||||
protected PluginManagerInterface $develGenerateManager;
|
||||
|
||||
/**
|
||||
* Logger service.
|
||||
*/
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container): static {
|
||||
$instance = parent::create($container);
|
||||
$instance->develGenerateManager = $container->get('plugin.manager.develgenerate');
|
||||
$instance->messenger = $container->get('messenger');
|
||||
$instance->logger = $container->get('logger.channel.devel_generate');
|
||||
$instance->requestStack = $container->get('request_stack');
|
||||
$instance->stringTranslation = $container->get('string_translation');
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId(): string {
|
||||
return 'devel_generate_form_' . $this->getPluginIdFromRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the param _plugin_id for the current request.
|
||||
*
|
||||
* @see \Drupal\devel_generate\Routing\DevelGenerateRouteSubscriber
|
||||
*/
|
||||
protected function getPluginIdFromRequest() {
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
return $request->get('_plugin_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DevelGenerate plugin instance for a given plugin id.
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
*
|
||||
* @return \Drupal\devel_generate\DevelGenerateBaseInterface
|
||||
* A DevelGenerate plugin instance.
|
||||
*/
|
||||
public function getPluginInstance(string $plugin_id): DevelGenerateBaseInterface {
|
||||
return $this->develGenerateManager->createInstance($plugin_id, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state): array {
|
||||
$plugin_id = $this->getPluginIdFromRequest();
|
||||
$instance = $this->getPluginInstance($plugin_id);
|
||||
$form = $instance->settingsForm($form, $form_state);
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Generate'),
|
||||
'#button_type' => 'primary',
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state): void {
|
||||
$plugin_id = $this->getPluginIdFromRequest();
|
||||
$instance = $this->getPluginInstance($plugin_id);
|
||||
$instance->settingsFormValidate($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state): void {
|
||||
try {
|
||||
$plugin_id = $this->getPluginIdFromRequest();
|
||||
$instance = $this->getPluginInstance($plugin_id);
|
||||
$instance->generate($form_state->getValues());
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->logger->error($this->t('Failed to generate elements due to "%error".', ['%error' => $e->getMessage()]));
|
||||
$this->messenger->addMessage($this->t('Failed to generate elements due to "%error".', ['%error' => $e->getMessage()]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,493 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate\Plugin\DevelGenerate;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Extension\ExtensionPathResolver;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\block_content\BlockContentInterface;
|
||||
use Drupal\content_translation\ContentTranslationManagerInterface;
|
||||
use Drupal\devel_generate\DevelGenerateBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a BlockContentDevelGenerate plugin.
|
||||
*
|
||||
* @DevelGenerate(
|
||||
* id = "block_content",
|
||||
* label = @Translation("Block Content"),
|
||||
* description = @Translation("Generate a given number of Block content blocks. Optionally delete current blocks."),
|
||||
* url = "block-content",
|
||||
* permission = "administer devel_generate",
|
||||
* settings = {
|
||||
* "num" = 50,
|
||||
* "kill" = FALSE,
|
||||
* "title_length" = 4,
|
||||
* "add_type_label" = FALSE,
|
||||
* "reusable" = TRUE
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class BlockContentDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The block content storage.
|
||||
*/
|
||||
protected EntityStorageInterface $blockContentStorage;
|
||||
|
||||
/**
|
||||
* The block content type storage.
|
||||
*/
|
||||
protected EntityStorageInterface $blockContentTypeStorage;
|
||||
|
||||
/**
|
||||
* The extension path resolver service.
|
||||
*/
|
||||
protected ExtensionPathResolver $extensionPathResolver;
|
||||
|
||||
/**
|
||||
* The entity type bundle info service.
|
||||
*/
|
||||
protected EntityTypeBundleInfoInterface $entityTypeBundleInfo;
|
||||
|
||||
/**
|
||||
* The content translation manager.
|
||||
*/
|
||||
protected ?ContentTranslationManagerInterface $contentTranslationManager;
|
||||
|
||||
/**
|
||||
* The Drush batch flag.
|
||||
*/
|
||||
protected bool $drushBatch = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
|
||||
$entity_type_manager = $container->get('entity_type.manager');
|
||||
|
||||
// @phpstan-ignore ternary.alwaysTrue (False positive)
|
||||
$content_translation_manager = $container->has('content_translation.manager') ? $container->get('content_translation.manager') : NULL;
|
||||
|
||||
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
|
||||
$instance->blockContentStorage = $entity_type_manager->getStorage('block_content');
|
||||
$instance->blockContentTypeStorage = $entity_type_manager->getStorage('block_content_type');
|
||||
$instance->extensionPathResolver = $container->get('extension.path.resolver');
|
||||
$instance->entityTypeBundleInfo = $container->get('entity_type.bundle.info');
|
||||
$instance->contentTranslationManager = $content_translation_manager;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state): array {
|
||||
/** @var \Drupal\block_content\BlockContentTypeInterface[] $blockTypes */
|
||||
$blockTypes = $this->blockContentTypeStorage->loadMultiple();
|
||||
$options = [];
|
||||
|
||||
foreach ($blockTypes as $type) {
|
||||
$options[$type->id()] = [
|
||||
'type' => [
|
||||
'label' => $type->label(),
|
||||
'description' => $type->getDescription(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$header = [
|
||||
'type' => $this->t('Block Content type'),
|
||||
'description' => $this->t('Description'),
|
||||
];
|
||||
|
||||
$form['block_types'] = [
|
||||
'#type' => 'tableselect',
|
||||
'#header' => $header,
|
||||
'#options' => $options,
|
||||
];
|
||||
|
||||
$form['kill'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('<strong>Delete all content</strong> in these block types before generating new content.'),
|
||||
'#default_value' => $this->getSetting('kill'),
|
||||
];
|
||||
|
||||
$form['num'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('How many blocks would you like to generate?'),
|
||||
'#default_value' => $this->getSetting('num'),
|
||||
'#required' => TRUE,
|
||||
'#min' => 0,
|
||||
];
|
||||
|
||||
$form['title_length'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Maximum number of words in block descriptions'),
|
||||
'#default_value' => $this->getSetting('title_length'),
|
||||
'#required' => TRUE,
|
||||
'#min' => 1,
|
||||
'#max' => 255,
|
||||
];
|
||||
|
||||
$form['skip_fields'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Fields to leave empty'),
|
||||
'#description' => $this->t('Enter the field names as a comma-separated list. These will be skipped and have a default value in the generated content.'),
|
||||
'#default_value' => NULL,
|
||||
];
|
||||
$form['base_fields'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Base fields to populate'),
|
||||
'#description' => $this->t('Enter the field names as a comma-separated list. These will be populated.'),
|
||||
'#default_value' => NULL,
|
||||
];
|
||||
|
||||
$form['reusable'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Reusable blocks'),
|
||||
'#description' => $this->t('This will mark the blocks to be created as reusable.'),
|
||||
'#default_value' => $this->getSetting('reusable'),
|
||||
|
||||
];
|
||||
$form['add_type_label'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Prefix the title with the block 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'),
|
||||
];
|
||||
|
||||
// Add the language and translation options.
|
||||
$form += $this->getLanguageForm('blocks');
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsFormValidate(array $form, FormStateInterface $form_state): void {
|
||||
if (array_filter($form_state->getValue('block_types')) === []) {
|
||||
$form_state->setErrorByName('block_types', $this->t('Please select at least one block type'));
|
||||
}
|
||||
|
||||
$skip_fields = is_null($form_state->getValue('skip_fields')) ? [] : self::csvToArray($form_state->getValue('skip_fields'));
|
||||
$base_fields = is_null($form_state->getValue('base_fields')) ? [] : self::csvToArray($form_state->getValue('base_fields'));
|
||||
$form_state->setValue('skip_fields', $skip_fields);
|
||||
$form_state->setValue('base_fields', $base_fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateDrushParams(array $args, array $options = []): array {
|
||||
$add_language = self::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 = self::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['skip_fields'] = is_null($options['skip-fields']) ? [] : self::csvToArray($options['skip-fields']);
|
||||
$values['base_fields'] = is_null($options['base-fields']) ? [] : self::csvToArray($options['base-fields']);
|
||||
$values['title_length'] = 6;
|
||||
$values['num'] = array_shift($args);
|
||||
$values['max_comments'] = array_shift($args);
|
||||
|
||||
$all_types = array_keys($this->blockContentGetBundles());
|
||||
$selected_types = self::csvToArray($options['block_types']);
|
||||
|
||||
if ($selected_types === []) {
|
||||
throw new \Exception(dt('No Block content types available'));
|
||||
}
|
||||
|
||||
$values['block_types'] = array_combine($selected_types, $selected_types);
|
||||
$block_types = array_filter($values['block_types']);
|
||||
|
||||
if (!empty($values['kill']) && $block_types === []) {
|
||||
throw new \Exception(dt('To delete content, please provide the Block content types (--bundles)'));
|
||||
}
|
||||
|
||||
// Checks for any missing block content types before generating blocks.
|
||||
if (array_diff($block_types, $all_types) !== []) {
|
||||
throw new \Exception(dt('One or more block content types have been entered that don\'t exist on this site'));
|
||||
}
|
||||
|
||||
if ($this->isBatch($values['num'])) {
|
||||
$this->drushBatch = TRUE;
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function generateElements(array $values): void {
|
||||
if ($this->isBatch($values['num'])) {
|
||||
$this->generateBatchContent($values);
|
||||
}
|
||||
else {
|
||||
$this->generateContent($values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate content in batch mode.
|
||||
*
|
||||
* This method is used when the number of elements is 50 or more.
|
||||
*/
|
||||
private function generateBatchContent(array $values): void {
|
||||
$operations = [];
|
||||
|
||||
// Remove unselected block content types.
|
||||
$values['block_types'] = array_filter($values['block_types']);
|
||||
// If it is drushBatch then this operation is already run in the
|
||||
// self::validateDrushParams().
|
||||
// Add the kill operation.
|
||||
if ($values['kill']) {
|
||||
$operations[] = [
|
||||
'devel_generate_operation',
|
||||
[$this, 'batchContentKill', $values],
|
||||
];
|
||||
}
|
||||
|
||||
// Add the operations to create the blocks.
|
||||
for ($num = 0; $num < $values['num']; ++$num) {
|
||||
$operations[] = [
|
||||
'devel_generate_operation',
|
||||
[$this, 'batchContentAddBlock', $values],
|
||||
];
|
||||
}
|
||||
|
||||
// Set the batch.
|
||||
$batch = [
|
||||
'title' => $this->t('Generating Content'),
|
||||
'operations' => $operations,
|
||||
'finished' => 'devel_generate_batch_finished',
|
||||
'file' => $this->extensionPathResolver->getPath('module', 'devel_generate') . '/devel_generate.batch.inc',
|
||||
];
|
||||
|
||||
batch_set($batch);
|
||||
if ($this->drushBatch) {
|
||||
drush_backend_batch_process();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch wrapper for calling ContentAddBlock.
|
||||
*/
|
||||
public function batchContentAddBlock(array $vars, array &$context): void {
|
||||
if (!isset($context['results']['num'])) {
|
||||
$context['results']['num'] = 0;
|
||||
}
|
||||
|
||||
if ($this->drushBatch) {
|
||||
++$context['results']['num'];
|
||||
$this->develGenerateContentAddBlock($vars);
|
||||
}
|
||||
else {
|
||||
$context['results'] = $vars;
|
||||
$this->develGenerateContentAddBlock($context['results']);
|
||||
}
|
||||
|
||||
if (!empty($vars['num_translations'])) {
|
||||
$context['results']['num_translations'] += $vars['num_translations'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch wrapper for calling ContentKill.
|
||||
*/
|
||||
public function batchContentKill(array $vars, array &$context): void {
|
||||
if ($this->drushBatch) {
|
||||
$this->contentKill($vars);
|
||||
}
|
||||
else {
|
||||
$context['results'] = $vars;
|
||||
$this->contentKill($context['results']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate content when not in batch mode.
|
||||
*
|
||||
* This method is used when the number of elements is under 50.
|
||||
*/
|
||||
private function generateContent(array $values): void {
|
||||
$values['block_types'] = array_filter($values['block_types']);
|
||||
if (!empty($values['kill']) && $values['block_types']) {
|
||||
$this->contentKill($values);
|
||||
}
|
||||
|
||||
if (isset($values['block_types']) && $values['block_types'] !== []) {
|
||||
$start = time();
|
||||
$values['num_translations'] = 0;
|
||||
for ($i = 1; $i <= $values['num']; ++$i) {
|
||||
$this->develGenerateContentAddBlock($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 blocks (@rate blocks/min)', $options));
|
||||
$start = $now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->setMessage($this->formatPlural($values['num'], 'Created 1 block', 'Created @count blocks'));
|
||||
if ($values['num_translations'] > 0) {
|
||||
$this->setMessage($this->formatPlural($values['num_translations'], 'Created 1 block translation', 'Created @count block translations'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create one block. Used by both batch and non-batch code branches.
|
||||
*
|
||||
* @param array $results
|
||||
* Results information.
|
||||
*/
|
||||
protected function develGenerateContentAddBlock(array &$results): void {
|
||||
if (!isset($results['time_range'])) {
|
||||
$results['time_range'] = 0;
|
||||
}
|
||||
|
||||
$block_type = array_rand($results['block_types']);
|
||||
|
||||
// Add the block type label if required.
|
||||
$title_prefix = $results['add_type_label'] ? $this->blockContentTypeStorage->load($block_type)->label() . ' - ' : '';
|
||||
|
||||
$values = [
|
||||
'info' => $title_prefix . $this->getRandom()->sentences(mt_rand(1, $results['title_length']), TRUE),
|
||||
'type' => $block_type,
|
||||
// A flag to let hook_block_content_insert() implementations know that this is a generated block.
|
||||
'devel_generate' => $results,
|
||||
];
|
||||
|
||||
if (isset($results['add_language'])) {
|
||||
$values['langcode'] = $this->getLangcode($results['add_language']);
|
||||
}
|
||||
|
||||
if (isset($results['reusable'])) {
|
||||
$values['reusable'] = (int) $results['reusable'];
|
||||
}
|
||||
|
||||
/** @var \Drupal\block_content\BlockContentInterface $block */
|
||||
$block = $this->blockContentStorage->create($values);
|
||||
|
||||
// Populate non-skipped fields with sample values.
|
||||
$this->populateFields($block, $results['skip_fields'], $results['base_fields']);
|
||||
|
||||
// Remove the fields which are intended to have no value.
|
||||
foreach ($results['skip_fields'] as $field) {
|
||||
unset($block->$field);
|
||||
}
|
||||
|
||||
$block->save();
|
||||
|
||||
// Add translations.
|
||||
$this->develGenerateContentAddBlockTranslation($results, $block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create translation for the given block.
|
||||
*
|
||||
* @param array $results
|
||||
* Results array.
|
||||
* @param \Drupal\block_content\BlockContentInterface $block
|
||||
* Block to add translations to.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
*/
|
||||
protected function develGenerateContentAddBlockTranslation(array &$results, BlockContentInterface $block): void {
|
||||
if (empty($results['translate_language'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($this->contentTranslationManager)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->contentTranslationManager->isEnabled('block_content', $block->bundle())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($block->get('langcode')->getLangcode() === LanguageInterface::LANGCODE_NOT_SPECIFIED
|
||||
|| $block->get('langcode')->getLangcode() === LanguageInterface::LANGCODE_NOT_APPLICABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($results['num_translations'])) {
|
||||
$results['num_translations'] = 0;
|
||||
}
|
||||
|
||||
// Translate the block to each target language.
|
||||
$skip_languages = [
|
||||
LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
LanguageInterface::LANGCODE_NOT_APPLICABLE,
|
||||
$block->get('langcode')->getLangcode(),
|
||||
];
|
||||
foreach ($results['translate_language'] as $langcode) {
|
||||
if (in_array($langcode, $skip_languages)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$translation_block = $block->addTranslation($langcode);
|
||||
$translation_block->setInfo($block->label() . ' (' . $langcode . ')');
|
||||
$this->populateFields($translation_block);
|
||||
$translation_block->save();
|
||||
|
||||
++$results['num_translations'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all blocks of given block content types.
|
||||
*
|
||||
* @param array $values
|
||||
* The input values from the settings form.
|
||||
*/
|
||||
protected function contentKill(array $values): void {
|
||||
$bids = $this->blockContentStorage->getQuery()
|
||||
->condition('type', $values['block_types'], 'IN')
|
||||
->accessCheck(FALSE)
|
||||
->execute();
|
||||
|
||||
if (!empty($bids)) {
|
||||
$blocks = $this->blockContentStorage->loadMultiple($bids);
|
||||
$this->blockContentStorage->delete($blocks);
|
||||
$this->setMessage($this->t('Deleted %count blocks.', ['%count' => count($bids)]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the content should be generated in batch mode.
|
||||
*/
|
||||
protected function isBatch($content_count): bool {
|
||||
return $content_count >= 50;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of available block content type names.
|
||||
*
|
||||
* This list can include types that are queued for addition or deletion.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of block content type labels,
|
||||
* keyed by the block content type name.
|
||||
*/
|
||||
public function blockContentGetBundles(): array {
|
||||
return array_map(static fn($bundle_info) => $bundle_info['label'], $this->entityTypeBundleInfo->getBundleInfo('block_content'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,888 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate\Plugin\DevelGenerate;
|
||||
|
||||
use Drupal\comment\CommentManagerInterface;
|
||||
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Component\Utility\Random;
|
||||
use Drupal\content_translation\ContentTranslationManagerInterface;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Extension\ExtensionPathResolver;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
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\node\NodeStorageInterface;
|
||||
use Drupal\path_alias\PathAliasStorage;
|
||||
use Drupal\user\RoleStorageInterface;
|
||||
use Drupal\user\UserStorageInterface;
|
||||
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
|
||||
* },
|
||||
* dependencies = {
|
||||
* "node",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class ContentDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The node storage.
|
||||
*/
|
||||
protected NodeStorageInterface $nodeStorage;
|
||||
|
||||
/**
|
||||
* The node type storage.
|
||||
*/
|
||||
protected EntityStorageInterface $nodeTypeStorage;
|
||||
|
||||
/**
|
||||
* The user storage.
|
||||
*/
|
||||
protected UserStorageInterface $userStorage;
|
||||
|
||||
/**
|
||||
* The url generator service.
|
||||
*/
|
||||
protected UrlGeneratorInterface $urlGenerator;
|
||||
|
||||
/**
|
||||
* The alias storage.
|
||||
*/
|
||||
protected PathAliasStorage $aliasStorage;
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*/
|
||||
protected DateFormatterInterface $dateFormatter;
|
||||
|
||||
/**
|
||||
* Provides system time.
|
||||
*/
|
||||
protected TimeInterface $time;
|
||||
|
||||
/**
|
||||
* Database connection.
|
||||
*/
|
||||
protected Connection $database;
|
||||
|
||||
/**
|
||||
* The extension path resolver service.
|
||||
*/
|
||||
protected ExtensionPathResolver $extensionPathResolver;
|
||||
|
||||
/**
|
||||
* The role storage.
|
||||
*/
|
||||
protected RoleStorageInterface $roleStorage;
|
||||
|
||||
/**
|
||||
* The comment manager service.
|
||||
*/
|
||||
protected ?CommentManagerInterface $commentManager;
|
||||
|
||||
/**
|
||||
* The content translation manager.
|
||||
*/
|
||||
protected ?ContentTranslationManagerInterface $contentTranslationManager;
|
||||
|
||||
/**
|
||||
* The Drush batch flag.
|
||||
*/
|
||||
protected bool $drushBatch = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
|
||||
$entity_type_manager = $container->get('entity_type.manager');
|
||||
|
||||
// @phpstan-ignore ternary.alwaysTrue (False positive)
|
||||
$comment_manager = $container->has('comment.manager') ? $container->get('comment.manager') : NULL;
|
||||
|
||||
// @phpstan-ignore ternary.alwaysTrue (False positive)
|
||||
$content_translation_manager = $container->has('content_translation.manager') ? $container->get('content_translation.manager') : NULL;
|
||||
|
||||
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
|
||||
$instance->nodeTypeStorage = $entity_type_manager->getStorage('node_type');
|
||||
$instance->nodeStorage = $entity_type_manager->getStorage('node');
|
||||
$instance->userStorage = $entity_type_manager->getStorage('user');
|
||||
$instance->urlGenerator = $container->get('url_generator');
|
||||
$instance->aliasStorage = $entity_type_manager->getStorage('path_alias');
|
||||
$instance->dateFormatter = $container->get('date.formatter');
|
||||
$instance->time = $container->get('datetime.time');
|
||||
$instance->database = $container->get('database');
|
||||
$instance->extensionPathResolver = $container->get('extension.path.resolver');
|
||||
$instance->roleStorage = $entity_type_manager->getStorage('user_role');
|
||||
$instance->commentManager = $comment_manager;
|
||||
$instance->contentTranslationManager = $content_translation_manager;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state): array {
|
||||
$types = $this->nodeTypeStorage->loadMultiple();
|
||||
|
||||
if (empty($types)) {
|
||||
$create_url = $this->urlGenerator->generateFromRoute('node.type_add');
|
||||
$this->messenger()->addMessage($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');
|
||||
return [];
|
||||
}
|
||||
|
||||
$options = [];
|
||||
|
||||
foreach ($types as $type) {
|
||||
$options[$type->id()] = [
|
||||
'type' => ['#markup' => $type->label()],
|
||||
];
|
||||
if ($this->commentManager instanceof CommentManagerInterface) {
|
||||
$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 ($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 instanceof CommentManagerInterface) {
|
||||
$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['skip_fields'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Fields to leave empty'),
|
||||
'#description' => $this->t('Enter the field names as a comma-separated list. These will be skipped and have a default value in the generated content.'),
|
||||
'#default_value' => NULL,
|
||||
];
|
||||
$form['base_fields'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Base fields to populate'),
|
||||
'#description' => $this->t('Enter the field names as a comma-separated list. These will be populated.'),
|
||||
'#default_value' => NULL,
|
||||
];
|
||||
$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)'),
|
||||
];
|
||||
|
||||
$num_users = $this->database->select('users')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$author_form_limit = 50;
|
||||
$query = $this->database->select('users', 'u')
|
||||
->fields('u', ['uid'])
|
||||
->range(0, $author_form_limit)
|
||||
->orderBy('uid');
|
||||
$uids = $query->execute()->fetchCol();
|
||||
|
||||
$author_rows = [];
|
||||
foreach ($uids as $uid) {
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user = $this->userStorage->load($uid);
|
||||
$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.')
|
||||
. ($num_users > $author_form_limit ? ' ' . $this->t('The site has @num_users users, only the first @$author_form_limit are shown and selectable here.', ['@num_users' => $num_users, '@$author_form_limit' => $author_form_limit]) : ''),
|
||||
];
|
||||
|
||||
$form['authors-wrap']['authors'] = [
|
||||
'#type' => 'tableselect',
|
||||
'#header' => $author_header,
|
||||
'#options' => $author_rows,
|
||||
];
|
||||
|
||||
$role_rows = [];
|
||||
$roles = array_map(static fn($role): string => $role->label(), $this->roleStorage->loadMultiple());
|
||||
foreach ($roles as $role_id => $role_name) {
|
||||
$role_rows[$role_id] = [
|
||||
'id' => ['#markup' => $role_id],
|
||||
'role' => ['#markup' => $role_name],
|
||||
];
|
||||
}
|
||||
|
||||
$form['authors-wrap']['roles'] = [
|
||||
'#type' => 'tableselect',
|
||||
'#header' => [
|
||||
'id' => $this->t('Role ID'),
|
||||
'role' => $this->t('Role Description'),
|
||||
],
|
||||
'#options' => $role_rows,
|
||||
'#prefix' => $this->t('Specify the roles that randomly selected authors must have.'),
|
||||
'#suffix' => $this->t('You can select users and roles. Authors will be randomly selected that match at least one of the criteria. Leave <em>both</em> selections unchecked to use a random selection of @$author_form_limit users, including Anonymous.', ['@$author_form_limit' => $author_form_limit]),
|
||||
];
|
||||
|
||||
$form['#redirect'] = FALSE;
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsFormValidate(array $form, FormStateInterface $form_state): void {
|
||||
if (array_filter($form_state->getValue('node_types')) === []) {
|
||||
$form_state->setErrorByName('node_types', $this->t('Please select at least one content type'));
|
||||
}
|
||||
|
||||
$skip_fields = is_null($form_state->getValue('skip_fields')) ? [] : self::csvToArray($form_state->getValue('skip_fields'));
|
||||
$base_fields = is_null($form_state->getValue('base_fields')) ? [] : self::csvToArray($form_state->getValue('base_fields'));
|
||||
$form_state->setValue('skip_fields', $skip_fields);
|
||||
$form_state->setValue('base_fields', $base_fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function generateElements(array $values): void {
|
||||
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(array $values): void {
|
||||
$values['node_types'] = array_filter($values['node_types']);
|
||||
if (!empty($values['kill']) && $values['node_types']) {
|
||||
$this->contentKill($values);
|
||||
}
|
||||
|
||||
if ($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->messenger()->addMessage($this->formatPlural($values['num'], 'Created 1 node', 'Created @count nodes'));
|
||||
if ($values['num_translations'] > 0) {
|
||||
$this->messenger()->addMessage($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(array $values): void {
|
||||
$operations = [];
|
||||
|
||||
// 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' => $this->extensionPathResolver->getPath('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, array &$context): void {
|
||||
$context['results'] = $vars;
|
||||
$context['results']['num'] = 0;
|
||||
$context['results']['num_translations'] = 0;
|
||||
$this->develGenerateContentPreNode($context['results']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch wrapper for calling ContentAddNode.
|
||||
*/
|
||||
public function batchContentAddNode(array $vars, array &$context): void {
|
||||
if ($this->drushBatch) {
|
||||
$this->develGenerateContentAddNode($vars);
|
||||
}
|
||||
else {
|
||||
$this->develGenerateContentAddNode($context['results']);
|
||||
}
|
||||
|
||||
if (!isset($context['results']['num'])) {
|
||||
$context['results']['num'] = 0;
|
||||
}
|
||||
|
||||
++$context['results']['num'];
|
||||
if (!empty($vars['num_translations'])) {
|
||||
$context['results']['num_translations'] += $vars['num_translations'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch wrapper for calling ContentKill.
|
||||
*/
|
||||
public function batchContentKill(array $vars, array &$context): void {
|
||||
if ($this->drushBatch) {
|
||||
$this->contentKill($vars);
|
||||
}
|
||||
else {
|
||||
$this->contentKill($context['results']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateDrushParams(array $args, array $options = []): array {
|
||||
$add_language = self::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 = self::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['skip_fields'] = is_null($options['skip-fields']) ? [] : self::csvToArray($options['skip-fields']);
|
||||
$values['base_fields'] = is_null($options['base-fields']) ? [] : self::csvToArray($options['base-fields']);
|
||||
$values['title_length'] = 6;
|
||||
$values['num'] = (int) trim(array_shift($args));
|
||||
$values['max_comments'] = (int) array_shift($args);
|
||||
// Do not use csvToArray for 'authors' because it removes '0' values.
|
||||
$values['authors'] = is_null($options['authors']) ? [] : explode(',', $options['authors']);
|
||||
$values['roles'] = self::csvToArray($options['roles']);
|
||||
|
||||
$all_types = array_keys(node_type_get_names());
|
||||
$default_types = array_intersect(['page', 'article'], $all_types);
|
||||
$selected_types = self::csvToArray($options['bundles'] ?: $default_types);
|
||||
|
||||
if ($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']) && $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'));
|
||||
}
|
||||
|
||||
if ($this->isBatch((int) $values['num'], (int) $values['max_comments'])) {
|
||||
$this->drushBatch = TRUE;
|
||||
$this->develGenerateContentPreNode($values);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the content should be generated in batch mode.
|
||||
*/
|
||||
protected function isBatch(int $content_count, int $comment_count): bool {
|
||||
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): void {
|
||||
$nids = $this->nodeStorage->getQuery()
|
||||
->condition('type', $values['node_types'], 'IN')
|
||||
->accessCheck(FALSE)
|
||||
->execute();
|
||||
|
||||
if (!empty($nids)) {
|
||||
$nodes = $this->nodeStorage->loadMultiple($nids);
|
||||
$this->nodeStorage->delete($nodes);
|
||||
$this->messenger()->addMessage($this->t('Deleted @count nodes.', ['@count' => count($nids)]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocesses $results before adding content.
|
||||
*
|
||||
* @param array $results
|
||||
* Results information.
|
||||
*/
|
||||
protected function develGenerateContentPreNode(array &$results): void {
|
||||
$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, static fn($k): bool => $k !== 0);
|
||||
// Likewise remove non-selected roles.
|
||||
$roles = $results['roles'];
|
||||
$roles = array_filter($roles, static fn($k): bool => $k !== 0);
|
||||
|
||||
// If specific roles have been selected then also add up to 50 users who
|
||||
// have one of these roles. There is no direct way randomise the selection
|
||||
// using entity queries, so we use a database query instead.
|
||||
if ($roles !== [] && !in_array('authenticated', $roles)) {
|
||||
$query = $this->database->select('user__roles', 'ur')
|
||||
->fields('ur', ['entity_id', 'roles_target_id'])
|
||||
->condition('roles_target_id', $roles, 'in')
|
||||
->range(0, 50)
|
||||
->orderRandom();
|
||||
$uids = array_unique($query->execute()->fetchCol());
|
||||
// If the 'anonymous' role is selected, then add '0' to the user ids. Also
|
||||
// do this if no users were specified and none were found with the role(s)
|
||||
// requested. This makes it clear that no users were found. It would be
|
||||
// worse to fall through and select completely random users who do not
|
||||
// have any of the roles requested.
|
||||
if (in_array('anonymous', $roles) || ($authors === [] && $uids === [])) {
|
||||
$uids[] = '0';
|
||||
}
|
||||
|
||||
$authors = array_unique(array_merge($authors, $uids));
|
||||
}
|
||||
|
||||
// If still no authors have been collected, or the 'authenticated' role was
|
||||
// requested then add a random set of users up to a maximum of 50.
|
||||
if ($authors === [] || in_array('authenticated', $roles)) {
|
||||
$query = $this->database->select('users', 'u')
|
||||
->fields('u', ['uid'])
|
||||
->range(0, 50)
|
||||
->orderRandom();
|
||||
$uids = $query->execute()->fetchCol();
|
||||
$authors = array_unique(array_merge($authors, $uids));
|
||||
}
|
||||
|
||||
$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): void {
|
||||
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']),
|
||||
// A flag to let hook_node_insert() implementations know that this is a
|
||||
// generated node.
|
||||
'devel_generate' => $results,
|
||||
];
|
||||
|
||||
if (isset($results['add_language'])) {
|
||||
$values['langcode'] = $this->getLangcode($results['add_language']);
|
||||
}
|
||||
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
$node = $this->nodeStorage->create($values);
|
||||
|
||||
// Populate non-skipped fields with sample values.
|
||||
$this->populateFields($node, $results['skip_fields'], $results['base_fields']);
|
||||
|
||||
// Remove the fields which are intended to have no value.
|
||||
foreach ($results['skip_fields'] as $field) {
|
||||
unset($node->$field);
|
||||
}
|
||||
|
||||
$node->save();
|
||||
$this->insertNodeData($node);
|
||||
|
||||
// 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.
|
||||
$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): void {
|
||||
if (empty($results['translate_language'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($this->contentTranslationManager)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->contentTranslationManager->isEnabled('node', $node->getType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($node->get('langcode')->getLangcode() === LanguageInterface::LANGCODE_NOT_SPECIFIED
|
||||
|| $node->get('langcode')->getLangcode() === 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->get('langcode')->getLangcode(),
|
||||
];
|
||||
foreach ($results['translate_language'] as $langcode) {
|
||||
if (in_array($langcode, $skip_languages)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$translation_node = $node->addTranslation($langcode);
|
||||
$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'];
|
||||
}
|
||||
}
|
||||
|
||||
private function insertNodeData(NodeInterface $node): void {
|
||||
if (!isset($node->devel_generate)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$results = $node->devel_generate;
|
||||
if (!empty($results['max_comments'])) {
|
||||
foreach ($node->getFieldDefinitions() as $field_name => $field_definition) {
|
||||
if ($field_definition->getType() !== 'comment') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($node->get($field_name)->getValue()[0]['status'] !== CommentItemInterface::OPEN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add comments for each comment field on entity.
|
||||
$this->addNodeComments($node, $field_definition, $results['users'], $results['max_comments'], $results['title_length']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($results['add_statistics'])) {
|
||||
$this->addNodeStatistics($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create comments and add them to a node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* Node to add comments to.
|
||||
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
|
||||
* The field storage definition.
|
||||
* @param array $users
|
||||
* Array of users to assign comment authors.
|
||||
* @param int $max_comments
|
||||
* Max number of comments to generate per node.
|
||||
* @param int $title_length
|
||||
* Max length of the title of the comments.
|
||||
*/
|
||||
private function addNodeComments(NodeInterface $node, FieldDefinitionInterface $field_definition, array $users, int $max_comments, int $title_length = 8): void {
|
||||
$parents = [];
|
||||
$commentStorage = $this->entityTypeManager->getStorage('comment');
|
||||
$field_name = $field_definition->getName();
|
||||
$num_comments = mt_rand(0, $max_comments);
|
||||
for ($i = 1; $i <= $num_comments; ++$i) {
|
||||
$query = $commentStorage->getQuery();
|
||||
switch ($i % 3) {
|
||||
case 0:
|
||||
// No parent.
|
||||
case 1:
|
||||
// Top level parent.
|
||||
$parents = $query
|
||||
->condition('pid', 0)
|
||||
->condition('entity_id', $node->id())
|
||||
->condition('entity_type', 'node')
|
||||
->condition('field_name', $field_name)
|
||||
->range(0, 1)
|
||||
->accessCheck(FALSE)
|
||||
->execute();
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Non top level parent.
|
||||
$parents = $query
|
||||
->condition('pid', 0, '>')
|
||||
->condition('entity_id', $node->id())
|
||||
->condition('entity_type', 'node')
|
||||
->condition('field_name', $field_name)
|
||||
->range(0, 1)
|
||||
->accessCheck(FALSE)
|
||||
->execute();
|
||||
break;
|
||||
}
|
||||
|
||||
$random = new Random();
|
||||
$stub = [
|
||||
'entity_type' => $node->getEntityTypeId(),
|
||||
'entity_id' => $node->id(),
|
||||
'field_name' => $field_name,
|
||||
'name' => 'devel generate',
|
||||
'mail' => 'devel_generate@example.com',
|
||||
'timestamp' => mt_rand($node->getCreatedTime(), $this->time->getRequestTime()),
|
||||
'subject' => substr($random->sentences(mt_rand(1, $title_length), TRUE), 0, 63),
|
||||
'uid' => $users[array_rand($users)],
|
||||
'langcode' => $node->language()->getId(),
|
||||
];
|
||||
if ($parents) {
|
||||
$stub['pid'] = current($parents);
|
||||
}
|
||||
|
||||
$comment = $commentStorage->create($stub);
|
||||
|
||||
// Populate all core fields.
|
||||
$this->populateFields($comment);
|
||||
$comment->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate statistics information for a node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* A node object.
|
||||
*/
|
||||
private function addNodeStatistics(NodeInterface $node): void {
|
||||
if (!$this->moduleHandler->moduleExists('statistics')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$statistic = [
|
||||
'nid' => $node->id(),
|
||||
'totalcount' => mt_rand(0, 500),
|
||||
'timestamp' => $this->time->getRequestTime() - mt_rand(0, $node->getCreatedTime()),
|
||||
];
|
||||
$statistic['daycount'] = mt_rand(0, $statistic['totalcount']);
|
||||
$this->database->insert('node_counter')->fields($statistic)->execute();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,534 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate\Plugin\DevelGenerate;
|
||||
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
use Drupal\Core\Extension\ExtensionPathResolver;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Routing\UrlGeneratorInterface;
|
||||
use Drupal\devel_generate\DevelGenerateBase;
|
||||
use Drupal\user\UserStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a plugin that generates media entities.
|
||||
*
|
||||
* @DevelGenerate(
|
||||
* id = "media",
|
||||
* label = @Translation("media"),
|
||||
* description = @Translation("Generate a given number of media entities."),
|
||||
* url = "media",
|
||||
* permission = "administer devel_generate",
|
||||
* settings = {
|
||||
* "num" = 50,
|
||||
* "kill" = FALSE,
|
||||
* "name_length" = 4,
|
||||
* },
|
||||
* dependencies = {
|
||||
* "media",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class MediaDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The media entity storage.
|
||||
*/
|
||||
protected ContentEntityStorageInterface $mediaStorage;
|
||||
|
||||
/**
|
||||
* The media type entity storage.
|
||||
*/
|
||||
protected ConfigEntityStorageInterface $mediaTypeStorage;
|
||||
|
||||
/**
|
||||
* The user entity storage.
|
||||
*/
|
||||
protected UserStorageInterface $userStorage;
|
||||
|
||||
/**
|
||||
* The url generator service.
|
||||
*/
|
||||
protected UrlGeneratorInterface $urlGenerator;
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*/
|
||||
protected DateFormatterInterface $dateFormatter;
|
||||
|
||||
/**
|
||||
* The system time service.
|
||||
*/
|
||||
protected TimeInterface $time;
|
||||
|
||||
/**
|
||||
* The extension path resolver service.
|
||||
*/
|
||||
protected ExtensionPathResolver $extensionPathResolver;
|
||||
|
||||
/**
|
||||
* The Drush batch flag.
|
||||
*/
|
||||
protected bool $drushBatch = FALSE;
|
||||
|
||||
/**
|
||||
* {@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->mediaStorage = $entity_type_manager->getStorage('media');
|
||||
$instance->mediaTypeStorage = $entity_type_manager->getStorage('media_type');
|
||||
$instance->userStorage = $entity_type_manager->getStorage('user');
|
||||
$instance->urlGenerator = $container->get('url_generator');
|
||||
$instance->dateFormatter = $container->get('date.formatter');
|
||||
$instance->time = $container->get('datetime.time');
|
||||
$instance->extensionPathResolver = $container->get('extension.path.resolver');
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state): array {
|
||||
$types = $this->mediaTypeStorage->loadMultiple();
|
||||
|
||||
if (empty($types)) {
|
||||
$create_url = $this->urlGenerator->generateFromRoute('entity.media_type.add_form');
|
||||
$this->setMessage($this->t('You do not have any media types that can be generated. <a href=":url">Go create a new media type</a>', [
|
||||
':url' => $create_url,
|
||||
]), MessengerInterface::TYPE_ERROR);
|
||||
return [];
|
||||
}
|
||||
|
||||
$options = [];
|
||||
foreach ($types as $type) {
|
||||
$options[$type->id()] = ['type' => ['#markup' => $type->label()]];
|
||||
}
|
||||
|
||||
$form['media_types'] = [
|
||||
'#type' => 'tableselect',
|
||||
'#header' => ['type' => $this->t('Media type')],
|
||||
'#options' => $options,
|
||||
];
|
||||
|
||||
$form['kill'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('<strong>Delete all media</strong> in these types before generating new media.'),
|
||||
'#default_value' => $this->getSetting('kill'),
|
||||
];
|
||||
$form['num'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('How many media items 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 media be dated?'),
|
||||
'#description' => $this->t('Media creation dates will be distributed randomly from the current time, back to the selected time.'),
|
||||
'#options' => $options,
|
||||
'#default_value' => 604800,
|
||||
];
|
||||
|
||||
$form['name_length'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Maximum number of words in names'),
|
||||
'#default_value' => $this->getSetting('name_length'),
|
||||
'#required' => TRUE,
|
||||
'#min' => 1,
|
||||
'#max' => 255,
|
||||
];
|
||||
|
||||
$form['skip_fields'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Fields to leave empty'),
|
||||
'#description' => $this->t('Enter the field names as a comma-separated list. These will be skipped and have a default value in the generated content.'),
|
||||
'#default_value' => NULL,
|
||||
];
|
||||
$form['base_fields'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Base fields to populate'),
|
||||
'#description' => $this->t('Enter the field names as a comma-separated list. These will be populated.'),
|
||||
'#default_value' => NULL,
|
||||
];
|
||||
|
||||
$options = [];
|
||||
// We always need a language.
|
||||
$languages = $this->languageManager->getLanguages(LanguageInterface::STATE_ALL);
|
||||
foreach ($languages as $langcode => $language) {
|
||||
$options[$langcode] = $language->getName();
|
||||
}
|
||||
|
||||
$form['add_language'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Set language on media'),
|
||||
'#multiple' => TRUE,
|
||||
'#description' => $this->t('Requires locale.module'),
|
||||
'#options' => $options,
|
||||
'#default_value' => [
|
||||
$this->languageManager->getDefaultLanguage()->getId(),
|
||||
],
|
||||
];
|
||||
|
||||
$form['#redirect'] = FALSE;
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsFormValidate(array $form, FormStateInterface $form_state): void {
|
||||
// Remove the media types not selected.
|
||||
$media_types = array_filter($form_state->getValue('media_types'));
|
||||
if ($media_types === []) {
|
||||
$form_state->setErrorByName('media_types', $this->t('Please select at least one media type'));
|
||||
}
|
||||
|
||||
// Store the normalized value back, in form state.
|
||||
$form_state->setValue('media_types', array_combine($media_types, $media_types));
|
||||
|
||||
$skip_fields = is_null($form_state->getValue('skip_fields')) ? [] : self::csvToArray($form_state->getValue('skip_fields'));
|
||||
$base_fields = is_null($form_state->getValue('base_fields')) ? [] : self::csvToArray($form_state->getValue('base_fields'));
|
||||
$form_state->setValue('skip_fields', $skip_fields);
|
||||
$form_state->setValue('base_fields', $base_fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function generateElements(array $values): void {
|
||||
if ($this->isBatch((int) $values['num'])) {
|
||||
$this->generateBatchMedia($values);
|
||||
}
|
||||
else {
|
||||
$this->generateMedia($values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for creating media when number of elements is less than 50.
|
||||
*
|
||||
* @param array $values
|
||||
* Array of values submitted through a form.
|
||||
*
|
||||
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
|
||||
* Thrown if the storage handler couldn't be loaded.
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
|
||||
* Thrown if the entity type doesn't exist.
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
* Thrown if the bundle does not exist or was needed but not specified.
|
||||
*/
|
||||
protected function generateMedia(array $values): void {
|
||||
if (!empty($values['kill']) && $values['media_types']) {
|
||||
$this->mediaKill($values);
|
||||
}
|
||||
|
||||
if (!empty($values['media_types'])) {
|
||||
// Generate media items.
|
||||
$this->preGenerate($values);
|
||||
$start = time();
|
||||
for ($i = 1; $i <= $values['num']; ++$i) {
|
||||
$this->createMediaItem($values);
|
||||
if (isset($values['feedback']) && $i % $values['feedback'] == 0) {
|
||||
$now = time();
|
||||
$this->messenger->addStatus(dt('Completed !feedback media items (!rate media/min)', [
|
||||
'!feedback' => $values['feedback'],
|
||||
'!rate' => ($values['feedback'] * 60) / ($now - $start),
|
||||
]));
|
||||
$start = $now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->setMessage($this->formatPlural($values['num'], '1 media item created.', 'Finished creating @count media items.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for creating media when number of elements is greater than 50.
|
||||
*
|
||||
* @param array $values
|
||||
* The input values from the settings form.
|
||||
*/
|
||||
protected function generateBatchMedia(array $values): void {
|
||||
$operations = [];
|
||||
|
||||
// Setup the batch operations and save the variables.
|
||||
$operations[] = [
|
||||
'devel_generate_operation',
|
||||
[$this, 'batchPreGenerate', $values],
|
||||
];
|
||||
|
||||
// Add the kill operation.
|
||||
if ($values['kill']) {
|
||||
$operations[] = [
|
||||
'devel_generate_operation',
|
||||
[$this, 'batchMediaKill', $values],
|
||||
];
|
||||
}
|
||||
|
||||
// Add the operations to create the media.
|
||||
for ($num = 0; $num < $values['num']; ++$num) {
|
||||
$operations[] = [
|
||||
'devel_generate_operation',
|
||||
[$this, 'batchCreateMediaItem', $values],
|
||||
];
|
||||
}
|
||||
|
||||
// Start the batch.
|
||||
$batch = [
|
||||
'title' => $this->t('Generating media items'),
|
||||
'operations' => $operations,
|
||||
'finished' => 'devel_generate_batch_finished',
|
||||
'file' => $this->extensionPathResolver->getPath('module', 'devel_generate') . '/devel_generate.batch.inc',
|
||||
];
|
||||
batch_set($batch);
|
||||
|
||||
if ($this->drushBatch) {
|
||||
drush_backend_batch_process();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a batch version of preGenerate().
|
||||
*
|
||||
* @param array $vars
|
||||
* The input values from the settings form.
|
||||
* @param iterable $context
|
||||
* Batch job context.
|
||||
*
|
||||
* @see self::preGenerate()
|
||||
*/
|
||||
public function batchPreGenerate(array $vars, iterable &$context): void {
|
||||
$context['results'] = $vars;
|
||||
$context['results']['num'] = 0;
|
||||
$this->preGenerate($context['results']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a batch version of createMediaItem().
|
||||
*
|
||||
* @param array $vars
|
||||
* The input values from the settings form.
|
||||
* @param iterable $context
|
||||
* Batch job context.
|
||||
*
|
||||
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
|
||||
* Thrown if the storage handler couldn't be loaded.
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
|
||||
* Thrown if the entity type doesn't exist.
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
* Thrown if the bundle does not exist or was needed but not specified.
|
||||
*
|
||||
* @see self::createMediaItem()
|
||||
*/
|
||||
public function batchCreateMediaItem(array $vars, iterable &$context): void {
|
||||
if ($this->drushBatch) {
|
||||
$this->createMediaItem($vars);
|
||||
}
|
||||
else {
|
||||
$this->createMediaItem($context['results']);
|
||||
}
|
||||
|
||||
if (!isset($context['results']['num'])) {
|
||||
$context['results']['num'] = 0;
|
||||
}
|
||||
|
||||
++$context['results']['num'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a batch version of mediaKill().
|
||||
*
|
||||
* @param array $vars
|
||||
* The input values from the settings form.
|
||||
* @param iterable $context
|
||||
* Batch job context.
|
||||
*
|
||||
* @see self::mediaKill()
|
||||
*/
|
||||
public function batchMediaKill(array $vars, iterable &$context): void {
|
||||
if ($this->drushBatch) {
|
||||
$this->mediaKill($vars);
|
||||
}
|
||||
else {
|
||||
$this->mediaKill($context['results']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateDrushParams(array $args, array $options = []): array {
|
||||
$add_language = $options['languages'];
|
||||
if (!empty($add_language)) {
|
||||
$add_language = explode(',', str_replace(' ', '', $add_language));
|
||||
// Intersect with the enabled languages to make sure the language args
|
||||
// passed are actually enabled.
|
||||
$values['values']['add_language'] = array_intersect($add_language, array_keys($this->languageManager->getLanguages(LanguageInterface::STATE_ALL)));
|
||||
}
|
||||
|
||||
$values['kill'] = $options['kill'];
|
||||
$values['feedback'] = $options['feedback'];
|
||||
$values['name_length'] = 6;
|
||||
$values['num'] = (int) array_shift($args);
|
||||
|
||||
$values['skip_fields'] = is_null($options['skip-fields']) ? [] : self::csvToArray($options['skip-fields']);
|
||||
$values['base_fields'] = is_null($options['base-fields']) ? [] : self::csvToArray($options['base-fields']);
|
||||
|
||||
$all_media_types = array_values($this->mediaTypeStorage->getQuery()->accessCheck(FALSE)->execute());
|
||||
$requested_media_types = self::csvToArray($options['media-types'] ?: $all_media_types);
|
||||
|
||||
if ($requested_media_types === []) {
|
||||
throw new \Exception(dt('No media types available'));
|
||||
}
|
||||
|
||||
// Check for any missing media type.
|
||||
if (($invalid_media_types = array_diff($requested_media_types, $all_media_types)) !== []) {
|
||||
throw new \Exception("Requested media types don't exists: " . implode(', ', $invalid_media_types));
|
||||
}
|
||||
|
||||
$values['media_types'] = array_combine($requested_media_types, $requested_media_types);
|
||||
|
||||
if ($this->isBatch($values['num'])) {
|
||||
$this->drushBatch = TRUE;
|
||||
$this->preGenerate($values);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all media of given media media types.
|
||||
*
|
||||
* @param array $values
|
||||
* The input values from the settings form.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
* Thrown if the media type does not exist.
|
||||
*/
|
||||
protected function mediaKill(array $values): void {
|
||||
$mids = $this->mediaStorage->getQuery()
|
||||
->condition('bundle', $values['media_types'], 'IN')
|
||||
->accessCheck(FALSE)
|
||||
->execute();
|
||||
|
||||
if (!empty($mids)) {
|
||||
$media = $this->mediaStorage->loadMultiple($mids);
|
||||
$this->mediaStorage->delete($media);
|
||||
$this->setMessage($this->t('Deleted %count media items.', ['%count' => count($mids)]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Code to be run before generating items.
|
||||
*
|
||||
* Returns the same array passed in as parameter, but with an array of uids
|
||||
* for the key 'users'.
|
||||
*
|
||||
* @param array $results
|
||||
* The input values from the settings form.
|
||||
*/
|
||||
protected function preGenerate(array &$results): void {
|
||||
// Get user id.
|
||||
$users = array_values($this->userStorage->getQuery()
|
||||
->range(0, 50)
|
||||
->accessCheck(FALSE)
|
||||
->execute());
|
||||
$users = array_merge($users, ['0']);
|
||||
$results['users'] = $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create one media item. Used by both batch and non-batch code branches.
|
||||
*
|
||||
* @param array $results
|
||||
* The input values from the settings form.
|
||||
*
|
||||
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
|
||||
* Thrown if the storage handler couldn't be loaded.
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
|
||||
* Thrown if the entity type doesn't exist.
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
* Thrown if the bundle does not exist or was needed but not specified.
|
||||
*/
|
||||
protected function createMediaItem(array &$results): void {
|
||||
if (!isset($results['time_range'])) {
|
||||
$results['time_range'] = 0;
|
||||
}
|
||||
|
||||
$media_type = array_rand($results['media_types']);
|
||||
$uid = $results['users'][array_rand($results['users'])];
|
||||
|
||||
$media = $this->mediaStorage->create([
|
||||
'bundle' => $media_type,
|
||||
'name' => $this->getRandom()->sentences(mt_rand(1, $results['name_length']), TRUE),
|
||||
'uid' => $uid,
|
||||
'revision' => mt_rand(0, 1),
|
||||
'status' => TRUE,
|
||||
'moderation_state' => 'published',
|
||||
'created' => $this->time->getRequestTime() - mt_rand(0, $results['time_range']),
|
||||
'langcode' => $this->getLangcode($results),
|
||||
// A flag to let hook implementations know that this is a generated item.
|
||||
'devel_generate' => $results,
|
||||
]);
|
||||
|
||||
// Populate all non-skipped fields with sample values.
|
||||
$this->populateFields($media, $results['skip_fields'], $results['base_fields']);
|
||||
|
||||
// Remove the fields which are intended to have no value.
|
||||
foreach ($results['skip_fields'] as $field) {
|
||||
unset($media->$field);
|
||||
}
|
||||
|
||||
$media->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine language based on $results.
|
||||
*
|
||||
* @param array $results
|
||||
* The input values from the settings form.
|
||||
*
|
||||
* @return string
|
||||
* The language code.
|
||||
*/
|
||||
protected function getLangcode(array $results): string {
|
||||
if (isset($results['add_language'])) {
|
||||
$langcodes = $results['add_language'];
|
||||
|
||||
return $langcodes[array_rand($langcodes)];
|
||||
}
|
||||
|
||||
return $this->languageManager->getDefaultLanguage()->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds out if the media item generation will run in batch process.
|
||||
*
|
||||
* @param int $media_items_count
|
||||
* Number of media items to be generated.
|
||||
*
|
||||
* @return bool
|
||||
* If the process should be a batch process.
|
||||
*/
|
||||
protected function isBatch(int $media_items_count): bool {
|
||||
return $media_items_count >= 50;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,383 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate\Plugin\DevelGenerate;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Menu\MenuLinkTreeInterface;
|
||||
use Drupal\Core\Menu\MenuTreeParameters;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\devel_generate\DevelGenerateBase;
|
||||
use Drupal\menu_link_content\MenuLinkContentStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a MenuDevelGenerate plugin.
|
||||
*
|
||||
* @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,
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,454 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate\Plugin\DevelGenerate;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
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\content_translation\ContentTranslationManagerInterface;
|
||||
use Drupal\devel_generate\DevelGenerateBase;
|
||||
use Drupal\taxonomy\TermInterface;
|
||||
use Drupal\taxonomy\TermStorageInterface;
|
||||
use Drupal\taxonomy\VocabularyStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a TermDevelGenerate plugin.
|
||||
*
|
||||
* @DevelGenerate(
|
||||
* id = "term",
|
||||
* label = @Translation("terms"),
|
||||
* description = @Translation("Generate a given number of terms. Optionally delete current terms."),
|
||||
* url = "term",
|
||||
* permission = "administer devel_generate",
|
||||
* settings = {
|
||||
* "num" = 10,
|
||||
* "title_length" = 12,
|
||||
* "minimum_depth" = 1,
|
||||
* "maximum_depth" = 4,
|
||||
* "kill" = FALSE,
|
||||
* },
|
||||
* dependencies = {
|
||||
* "taxonomy",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class TermDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The vocabulary storage.
|
||||
*/
|
||||
protected VocabularyStorageInterface $vocabularyStorage;
|
||||
|
||||
/**
|
||||
* The term storage.
|
||||
*/
|
||||
protected TermStorageInterface $termStorage;
|
||||
|
||||
/**
|
||||
* Database connection.
|
||||
*/
|
||||
protected Connection $database;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*/
|
||||
protected ModuleHandlerInterface $moduleHandler;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*/
|
||||
protected LanguageManagerInterface $languageManager;
|
||||
|
||||
/**
|
||||
* The content translation manager.
|
||||
*/
|
||||
protected ?ContentTranslationManagerInterface $contentTranslationManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
|
||||
$entity_type_manager = $container->get('entity_type.manager');
|
||||
|
||||
// @phpstan-ignore ternary.alwaysTrue (False positive)
|
||||
$content_translation_manager = $container->has('content_translation.manager') ? $container->get('content_translation.manager') : NULL;
|
||||
|
||||
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
|
||||
$instance->vocabularyStorage = $entity_type_manager->getStorage('taxonomy_vocabulary');
|
||||
$instance->termStorage = $entity_type_manager->getStorage('taxonomy_term');
|
||||
$instance->database = $container->get('database');
|
||||
$instance->contentTranslationManager = $content_translation_manager;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state): array {
|
||||
$options = [];
|
||||
foreach ($this->vocabularyStorage->loadMultiple() as $vocabulary) {
|
||||
$options[$vocabulary->id()] = $vocabulary->label();
|
||||
}
|
||||
|
||||
// Sort by vocabulary label.
|
||||
asort($options);
|
||||
// Set default to 'tags' only if it exists as a vocabulary.
|
||||
$default_vids = array_key_exists('tags', $options) ? 'tags' : '';
|
||||
$form['vids'] = [
|
||||
'#type' => 'select',
|
||||
'#multiple' => TRUE,
|
||||
'#title' => $this->t('Vocabularies'),
|
||||
'#required' => TRUE,
|
||||
'#default_value' => $default_vids,
|
||||
'#options' => $options,
|
||||
'#description' => $this->t('Restrict terms to these vocabularies.'),
|
||||
];
|
||||
$form['num'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Number of terms'),
|
||||
'#default_value' => $this->getSetting('num'),
|
||||
'#required' => TRUE,
|
||||
'#min' => 0,
|
||||
];
|
||||
$form['title_length'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Maximum number of characters in term names'),
|
||||
'#default_value' => $this->getSetting('title_length'),
|
||||
'#required' => TRUE,
|
||||
'#min' => 2,
|
||||
'#max' => 255,
|
||||
];
|
||||
$form['minimum_depth'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Minimum depth for new terms in the vocabulary hierarchy'),
|
||||
'#description' => $this->t('Enter a value from 1 to 20.'),
|
||||
'#default_value' => $this->getSetting('minimum_depth'),
|
||||
'#min' => 1,
|
||||
'#max' => 20,
|
||||
];
|
||||
$form['maximum_depth'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Maximum depth for new terms in the vocabulary hierarchy'),
|
||||
'#description' => $this->t('Enter a value from 1 to 20.'),
|
||||
'#default_value' => $this->getSetting('maximum_depth'),
|
||||
'#min' => 1,
|
||||
'#max' => 20,
|
||||
];
|
||||
$form['kill'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Delete existing terms in specified vocabularies before generating new terms.'),
|
||||
'#default_value' => $this->getSetting('kill'),
|
||||
];
|
||||
|
||||
// Add the language and translation options.
|
||||
$form += $this->getLanguageForm('terms');
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function generateElements(array $values): void {
|
||||
$new_terms = $this->generateTerms($values);
|
||||
if (!empty($new_terms['terms'])) {
|
||||
$this->setMessage($this->formatPlural($new_terms['terms'], 'Created 1 new term', 'Created @count new terms'));
|
||||
|
||||
// Helper function to format the number of terms and the list of terms.
|
||||
$format_terms_func = function (array $data, $level) {
|
||||
if ($data['total'] > 10) {
|
||||
$data['terms'][] = '...';
|
||||
}
|
||||
|
||||
return $this->formatPlural($data['total'],
|
||||
'1 new term at level @level (@terms)',
|
||||
'@count new terms at level @level (@terms)',
|
||||
['@level' => $level, '@terms' => implode(',', $data['terms'])]);
|
||||
};
|
||||
|
||||
foreach ($new_terms['vocabs'] as $vid => $vlabel) {
|
||||
if (array_key_exists($vid, $new_terms)) {
|
||||
ksort($new_terms[$vid]);
|
||||
$termlist = implode(', ', array_map($format_terms_func, $new_terms[$vid], array_keys($new_terms[$vid])));
|
||||
$this->setMessage($this->t('In vocabulary @vlabel: @termlist', ['@vlabel' => $vlabel, '@termlist' => $termlist]));
|
||||
}
|
||||
else {
|
||||
$this->setMessage($this->t('In vocabulary @vlabel: No terms created', ['@vlabel' => $vlabel]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($new_terms['terms_translations'] > 0) {
|
||||
$this->setMessage($this->formatPlural($new_terms['terms_translations'], 'Created 1 term translation', 'Created @count term translations'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all terms of given vocabularies.
|
||||
*
|
||||
* @param array $vids
|
||||
* Array of vocabulary ids.
|
||||
*
|
||||
* @return int
|
||||
* The number of terms deleted.
|
||||
*/
|
||||
protected function deleteVocabularyTerms(array $vids): int {
|
||||
$tids = $this->vocabularyStorage->getToplevelTids($vids);
|
||||
$terms = $this->termStorage->loadMultiple($tids);
|
||||
$total_deleted = 0;
|
||||
foreach ($vids as $vid) {
|
||||
$total_deleted += count($this->termStorage->loadTree($vid));
|
||||
}
|
||||
|
||||
$this->termStorage->delete($terms);
|
||||
return $total_deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates taxonomy terms for a list of given vocabularies.
|
||||
*
|
||||
* @param array $parameters
|
||||
* The input parameters from the settings form or drush command.
|
||||
*
|
||||
* @return array
|
||||
* Information about the created terms.
|
||||
*/
|
||||
protected function generateTerms(array $parameters): array {
|
||||
$info = [
|
||||
'terms' => 0,
|
||||
'terms_translations' => 0,
|
||||
];
|
||||
$min_depth = $parameters['minimum_depth'];
|
||||
$max_depth = $parameters['maximum_depth'];
|
||||
|
||||
// $parameters['vids'] from the UI has keys of the vocab ids. From drush
|
||||
// the array is keyed 0,1,2. Therefore create $vocabs which has keys of the
|
||||
// vocab ids, so it can be used with array_rand().
|
||||
$vocabs = array_combine($parameters['vids'], $parameters['vids']);
|
||||
|
||||
// Delete terms from the vocabularies we are creating new terms in.
|
||||
if ($parameters['kill']) {
|
||||
$deleted = $this->deleteVocabularyTerms($vocabs);
|
||||
$this->setMessage($this->formatPlural($deleted, 'Deleted 1 existing term', 'Deleted @count existing terms'));
|
||||
if ($min_depth != 1) {
|
||||
$this->setMessage($this->t('Minimum depth changed from @min_depth to 1 because all terms were deleted', ['@min_depth' => $min_depth]));
|
||||
$min_depth = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Build an array of potential parents for the new terms. These will be
|
||||
// terms in the vocabularies we are creating in, which have a depth of one
|
||||
// less than the minimum for new terms up to one less than the maximum.
|
||||
$all_parents = [];
|
||||
foreach ($parameters['vids'] as $vid) {
|
||||
$info['vocabs'][$vid] = $this->vocabularyStorage->load($vid)->label();
|
||||
// Initialise the nested array for this vocabulary.
|
||||
$all_parents[$vid] = ['top_level' => [], 'lower_levels' => []];
|
||||
$ids = [];
|
||||
for ($depth = 1; $depth < $max_depth; ++$depth) {
|
||||
$query = $this->termStorage->getQuery()->accessCheck(FALSE)->condition('vid', $vid);
|
||||
if ($depth == 1) {
|
||||
// For the top level the parent id must be zero.
|
||||
$query->condition('parent', 0);
|
||||
}
|
||||
else {
|
||||
// For lower levels use the $ids array obtained in the previous loop.
|
||||
$query->condition('parent', $ids, 'IN');
|
||||
}
|
||||
|
||||
$ids = $query->execute();
|
||||
|
||||
if (empty($ids)) {
|
||||
// Reached the end, no more parents to be found.
|
||||
break;
|
||||
}
|
||||
|
||||
// Store these terms as parents if they are within the depth range for
|
||||
// new terms.
|
||||
if ($depth == $min_depth - 1) {
|
||||
$all_parents[$vid]['top_level'] = array_fill_keys($ids, $depth);
|
||||
}
|
||||
elseif ($depth >= $min_depth) {
|
||||
$all_parents[$vid]['lower_levels'] += array_fill_keys($ids, $depth);
|
||||
}
|
||||
}
|
||||
|
||||
// No top-level parents will have been found above when the minimum depth
|
||||
// is 1 so add a record for that data here.
|
||||
if ($min_depth == 1) {
|
||||
$all_parents[$vid]['top_level'] = [0 => 0];
|
||||
}
|
||||
elseif (empty($all_parents[$vid]['top_level'])) {
|
||||
// No parents for required minimum level so cannot use this vocabulary.
|
||||
unset($vocabs[$vid]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($vocabs === []) {
|
||||
// There are no available parents at the required depth in any vocabulary,
|
||||
// so we cannot create any new terms.
|
||||
throw new \Exception(sprintf('Invalid minimum depth %s because there are no terms in any vocabulary at depth %s', $min_depth, $min_depth - 1));
|
||||
}
|
||||
|
||||
// Insert new data:
|
||||
for ($i = 1; $i <= $parameters['num']; ++$i) {
|
||||
// Select a vocabulary at random.
|
||||
$vid = array_rand($vocabs);
|
||||
|
||||
// Set the group to use to select a random parent from. Using < 50 means
|
||||
// on average half of the new terms will be top_level. Also if no terms
|
||||
// exist yet in 'lower_levels' then we have to use 'top_level'.
|
||||
$group = (mt_rand(0, 100) < 50 || empty($all_parents[$vid]['lower_levels'])) ? 'top_level' : 'lower_levels';
|
||||
$parent = array_rand($all_parents[$vid][$group]);
|
||||
$depth = $all_parents[$vid][$group][$parent] + 1;
|
||||
$name = $this->getRandom()->word(mt_rand(2, $parameters['title_length']));
|
||||
|
||||
$values = [
|
||||
'name' => $name,
|
||||
'description' => 'Description of ' . $name . ' (depth ' . $depth . ')',
|
||||
'format' => filter_fallback_format(),
|
||||
'weight' => mt_rand(0, 10),
|
||||
'vid' => $vid,
|
||||
'parent' => [$parent],
|
||||
// Give hook implementations access to the parameters used for generation.
|
||||
'devel_generate' => $parameters,
|
||||
];
|
||||
if (isset($parameters['add_language'])) {
|
||||
$values['langcode'] = $this->getLangcode($parameters['add_language']);
|
||||
}
|
||||
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
$term = $this->termStorage->create($values);
|
||||
|
||||
// Populate all fields with sample values.
|
||||
$this->populateFields($term);
|
||||
$term->save();
|
||||
|
||||
// Add translations.
|
||||
if (isset($parameters['translate_language']) && !empty($parameters['translate_language'])) {
|
||||
$info['terms_translations'] += $this->generateTermTranslation($parameters['translate_language'], $term);
|
||||
}
|
||||
|
||||
// If the depth of the new term is less than the maximum depth then it can
|
||||
// also be saved as a potential parent for the subsequent new terms.
|
||||
if ($depth < $max_depth) {
|
||||
$all_parents[$vid]['lower_levels'] += [$term->id() => $depth];
|
||||
}
|
||||
|
||||
// Store data about the newly generated term.
|
||||
++$info['terms'];
|
||||
@$info[$vid][$depth]['total']++;
|
||||
// List only the first 10 new terms at each vocab/level.
|
||||
if (!isset($info[$vid][$depth]['terms']) || count($info[$vid][$depth]['terms']) < 10) {
|
||||
$info[$vid][$depth]['terms'][] = $term->label();
|
||||
}
|
||||
|
||||
unset($term);
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create translation for the given term.
|
||||
*
|
||||
* @param array $translate_language
|
||||
* Potential translate languages array.
|
||||
* @param \Drupal\taxonomy\TermInterface $term
|
||||
* Term to add translations to.
|
||||
*
|
||||
* @return int
|
||||
* Number of translations added.
|
||||
*/
|
||||
protected function generateTermTranslation(array $translate_language, TermInterface $term): int {
|
||||
if (is_null($this->contentTranslationManager)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!$this->contentTranslationManager->isEnabled('taxonomy_term', $term->bundle())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($term->get('langcode')->getLangcode() === LanguageInterface::LANGCODE_NOT_SPECIFIED
|
||||
|| $term->get('langcode')->getLangcode() === LanguageInterface::LANGCODE_NOT_APPLICABLE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$num_translations = 0;
|
||||
// Translate term to each target language.
|
||||
$skip_languages = [
|
||||
LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
LanguageInterface::LANGCODE_NOT_APPLICABLE,
|
||||
$term->get('langcode')->getLangcode(),
|
||||
];
|
||||
foreach ($translate_language as $langcode) {
|
||||
if (in_array($langcode, $skip_languages)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$translation_term = $term->addTranslation($langcode);
|
||||
$translation_term->setName($term->getName() . ' (' . $langcode . ')');
|
||||
$this->populateFields($translation_term);
|
||||
$translation_term->save();
|
||||
++$num_translations;
|
||||
}
|
||||
|
||||
return $num_translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateDrushParams(array $args, array $options = []): array {
|
||||
// Get default settings from the annotated command definition.
|
||||
$defaultSettings = $this->getDefaultSettings();
|
||||
|
||||
$bundles = self::csvToarray($options['bundles']);
|
||||
if (count($bundles) < 1) {
|
||||
throw new \Exception(dt('Please provide a vocabulary machine name (--bundles).'));
|
||||
}
|
||||
|
||||
foreach ($bundles as $bundle) {
|
||||
// Verify that each bundle is a valid vocabulary id.
|
||||
if (!$this->vocabularyStorage->load($bundle)) {
|
||||
throw new \Exception(dt('Invalid vocabulary machine name: @name', ['@name' => $bundle]));
|
||||
}
|
||||
}
|
||||
|
||||
$number = array_shift($args) ?: $defaultSettings['num'];
|
||||
if (!$this->isNumber($number)) {
|
||||
throw new \Exception(dt('Invalid number of terms: @num', ['@num' => $number]));
|
||||
}
|
||||
|
||||
$minimum_depth = $options['min-depth'] ?? $defaultSettings['minimum_depth'];
|
||||
$maximum_depth = $options['max-depth'] ?? $defaultSettings['maximum_depth'];
|
||||
if ($minimum_depth < 1 || $minimum_depth > 20 || $maximum_depth < 1 || $maximum_depth > 20 || $minimum_depth > $maximum_depth) {
|
||||
throw new \Exception(dt('The depth values must be in the range 1 to 20 and min-depth cannot be larger than max-depth (values given: min-depth @min, max-depth @max)', ['@min' => $minimum_depth, '@max' => $maximum_depth]));
|
||||
}
|
||||
|
||||
$values = [
|
||||
'num' => $number,
|
||||
'kill' => $options['kill'],
|
||||
'title_length' => 12,
|
||||
'vids' => $bundles,
|
||||
'minimum_depth' => $minimum_depth,
|
||||
'maximum_depth' => $maximum_depth,
|
||||
];
|
||||
$add_language = self::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 = self::csvToArray($options['translations']);
|
||||
$values['translate_language'] = array_intersect($translate_language, $valid_languages);
|
||||
return $values;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate\Plugin\DevelGenerate;
|
||||
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\devel_generate\DevelGenerateBase;
|
||||
use Drupal\user\RoleStorageInterface;
|
||||
use Drupal\user\UserStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a UserDevelGenerate plugin.
|
||||
*
|
||||
* @DevelGenerate(
|
||||
* id = "user",
|
||||
* label = @Translation("users"),
|
||||
* description = @Translation("Generate a given number of users. Optionally delete current users."),
|
||||
* url = "user",
|
||||
* permission = "administer devel_generate",
|
||||
* settings = {
|
||||
* "num" = 50,
|
||||
* "kill" = FALSE,
|
||||
* "pass" = ""
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class UserDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The user storage.
|
||||
*/
|
||||
protected UserStorageInterface $userStorage;
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*/
|
||||
protected DateFormatterInterface $dateFormatter;
|
||||
|
||||
/**
|
||||
* Provides system time.
|
||||
*/
|
||||
protected TimeInterface $time;
|
||||
|
||||
/**
|
||||
* The role storage.
|
||||
*/
|
||||
protected RoleStorageInterface $roleStorage;
|
||||
|
||||
/**
|
||||
* {@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->userStorage = $entity_type_manager->getStorage('user');
|
||||
$instance->dateFormatter = $container->get('date.formatter');
|
||||
$instance->time = $container->get('datetime.time');
|
||||
$instance->roleStorage = $entity_type_manager->getStorage('user_role');
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state): array {
|
||||
$form['num'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('How many users would you like to generate?'),
|
||||
'#default_value' => $this->getSetting('num'),
|
||||
'#required' => TRUE,
|
||||
'#min' => 0,
|
||||
];
|
||||
|
||||
$form['kill'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Delete all users (except user id 1) before generating new users.'),
|
||||
'#default_value' => $this->getSetting('kill'),
|
||||
];
|
||||
|
||||
$roles = array_map(static fn($role): string => $role->label(), $this->roleStorage->loadMultiple());
|
||||
unset($roles[AccountInterface::AUTHENTICATED_ROLE], $roles[AccountInterface::ANONYMOUS_ROLE]);
|
||||
$form['roles'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Which roles should the users receive?'),
|
||||
'#description' => $this->t('Users always receive the <em>authenticated user</em> role.'),
|
||||
'#options' => $roles,
|
||||
];
|
||||
|
||||
$form['pass'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Password to be set'),
|
||||
'#default_value' => $this->getSetting('pass'),
|
||||
'#size' => 32,
|
||||
'#description' => $this->t('Leave this field empty if you do not need to set a password'),
|
||||
];
|
||||
|
||||
$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 old should user accounts be?'),
|
||||
'#description' => $this->t('User ages will be distributed randomly from the current time, back to the selected time.'),
|
||||
'#options' => $options,
|
||||
'#default_value' => 604800,
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function generateElements(array $values): void {
|
||||
$num = $values['num'];
|
||||
$kill = $values['kill'];
|
||||
$pass = $values['pass'];
|
||||
$age = $values['time_range'];
|
||||
$roles = array_filter($values['roles']);
|
||||
|
||||
if ($kill) {
|
||||
$uids = $this->userStorage->getQuery()
|
||||
->condition('uid', 1, '>')
|
||||
->accessCheck(FALSE)
|
||||
->execute();
|
||||
$users = $this->userStorage->loadMultiple($uids);
|
||||
$this->userStorage->delete($users);
|
||||
|
||||
$this->setMessage($this->formatPlural(count($uids), '1 user deleted', '@count users deleted.'));
|
||||
}
|
||||
|
||||
if ($num > 0) {
|
||||
$names = [];
|
||||
while (count($names) < $num) {
|
||||
$name = $this->getRandom()->word(mt_rand(6, 12));
|
||||
$names[$name] = '';
|
||||
}
|
||||
|
||||
if ($roles === []) {
|
||||
$roles = [AccountInterface::AUTHENTICATED_ROLE];
|
||||
}
|
||||
|
||||
foreach (array_keys($names) as $name) {
|
||||
/** @var \Drupal\user\UserInterface $account */
|
||||
$account = $this->userStorage->create([
|
||||
'uid' => NULL,
|
||||
'name' => $name,
|
||||
'pass' => $pass,
|
||||
'mail' => $name . '@example.com',
|
||||
'status' => 1,
|
||||
'created' => $this->time->getRequestTime() - mt_rand(0, $age),
|
||||
'roles' => array_values($roles),
|
||||
// A flag to let hook_user_* know that this is a generated user.
|
||||
'devel_generate' => TRUE,
|
||||
]);
|
||||
|
||||
// Populate all fields with sample values.
|
||||
$this->populateFields($account);
|
||||
$account->save();
|
||||
}
|
||||
}
|
||||
|
||||
$this->setMessage($this->t('@num_users created.',
|
||||
['@num_users' => $this->formatPlural($num, '1 user', '@count users')]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateDrushParams(array $args, array $options = []): array {
|
||||
return [
|
||||
'num' => array_shift($args),
|
||||
'time_range' => 0,
|
||||
'roles' => self::csvToArray($options['roles']),
|
||||
'kill' => $options['kill'],
|
||||
'pass' => $options['pass'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate\Plugin\DevelGenerate;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\devel_generate\DevelGenerateBase;
|
||||
use Drupal\taxonomy\VocabularyStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a VocabularyDevelGenerate plugin.
|
||||
*
|
||||
* @DevelGenerate(
|
||||
* id = "vocabulary",
|
||||
* label = @Translation("vocabularies"),
|
||||
* description = @Translation("Generate a given number of vocabularies. Optionally delete current vocabularies."),
|
||||
* url = "vocabs",
|
||||
* permission = "administer devel_generate",
|
||||
* settings = {
|
||||
* "num" = 1,
|
||||
* "title_length" = 12,
|
||||
* "kill" = FALSE
|
||||
* },
|
||||
* dependencies = {
|
||||
* "taxonomy",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class VocabularyDevelGenerate extends DevelGenerateBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The vocabulary storage.
|
||||
*/
|
||||
protected VocabularyStorageInterface $vocabularyStorage;
|
||||
|
||||
/**
|
||||
* {@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->vocabularyStorage = $entity_type_manager->getStorage('taxonomy_vocabulary');
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state): array {
|
||||
$form['num'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Number of vocabularies?'),
|
||||
'#default_value' => $this->getSetting('num'),
|
||||
'#required' => TRUE,
|
||||
'#min' => 0,
|
||||
];
|
||||
$form['title_length'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Maximum number of characters in vocabulary names'),
|
||||
'#default_value' => $this->getSetting('title_length'),
|
||||
'#required' => TRUE,
|
||||
'#min' => 2,
|
||||
'#max' => 255,
|
||||
];
|
||||
$form['kill'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Delete existing vocabularies before generating new ones.'),
|
||||
'#default_value' => $this->getSetting('kill'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function generateElements(array $values): void {
|
||||
if ($values['kill']) {
|
||||
$this->deleteVocabularies();
|
||||
$this->setMessage($this->t('Deleted existing vocabularies.'));
|
||||
}
|
||||
|
||||
$new_vocs = $this->generateVocabularies($values['num'], $values['title_length']);
|
||||
if ($new_vocs !== []) {
|
||||
$this->setMessage($this->t('Created the following new vocabularies: @vocs', ['@vocs' => implode(', ', $new_vocs)]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all vocabularies.
|
||||
*/
|
||||
protected function deleteVocabularies(): void {
|
||||
$vocabularies = $this->vocabularyStorage->loadMultiple();
|
||||
$this->vocabularyStorage->delete($vocabularies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates vocabularies.
|
||||
*
|
||||
* @param int $records
|
||||
* Number of vocabularies to create.
|
||||
* @param int $maxlength
|
||||
* (optional) Maximum length for vocabulary name.
|
||||
*
|
||||
* @return array
|
||||
* Array containing the generated vocabularies id.
|
||||
*/
|
||||
protected function generateVocabularies(int $records, int $maxlength = 12): array {
|
||||
$vocabularies = [];
|
||||
|
||||
// Insert new data:
|
||||
for ($i = 1; $i <= $records; ++$i) {
|
||||
$name = $this->getRandom()->word(mt_rand(2, $maxlength));
|
||||
|
||||
$vocabulary = $this->vocabularyStorage->create([
|
||||
'name' => $name,
|
||||
'vid' => mb_strtolower($name),
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
'description' => 'Description of ' . $name,
|
||||
'hierarchy' => 1,
|
||||
'weight' => mt_rand(0, 10),
|
||||
'multiple' => 1,
|
||||
'required' => 0,
|
||||
'relations' => 1,
|
||||
]);
|
||||
|
||||
// Populate all fields with sample values.
|
||||
$this->populateFields($vocabulary);
|
||||
$vocabulary->save();
|
||||
|
||||
$vocabularies[] = $vocabulary->id();
|
||||
unset($vocabulary);
|
||||
}
|
||||
|
||||
return $vocabularies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateDrushParams(array $args, array $options = []): array {
|
||||
$values = [
|
||||
'num' => array_shift($args),
|
||||
'kill' => $options['kill'],
|
||||
'title_length' => 12,
|
||||
];
|
||||
|
||||
if ($this->isNumber($values['num']) == FALSE) {
|
||||
throw new \Exception(dt('Invalid number of vocabularies: @num.', ['@num' => $values['num']]));
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\devel_generate\Routing;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\devel_generate\Form\DevelGenerateForm;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Provides dynamic routes for devel_generate.
|
||||
*/
|
||||
class DevelGenerateRoutes implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The manager to be used for instantiating plugins.
|
||||
*/
|
||||
protected PluginManagerInterface $develGenerateManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container): self {
|
||||
$instance = new self();
|
||||
$instance->develGenerateManager = $container->get('plugin.manager.develgenerate');
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define routes for all devel_generate plugins.
|
||||
*/
|
||||
public function routes(): array {
|
||||
$devel_generate_plugins = $this->develGenerateManager->getDefinitions();
|
||||
|
||||
$routes = [];
|
||||
foreach ($devel_generate_plugins as $id => $plugin) {
|
||||
$label = $plugin['label'];
|
||||
$type_url_str = str_replace('_', '-', $plugin['url']);
|
||||
$routes['devel_generate.' . $id] = new Route(
|
||||
'admin/config/development/generate/' . $type_url_str,
|
||||
[
|
||||
'_form' => DevelGenerateForm::class,
|
||||
'_title' => 'Generate ' . $label,
|
||||
'_plugin_id' => $id,
|
||||
],
|
||||
[
|
||||
'_permission' => $plugin['permission'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Add the route for the 'Generate' admin group on the admin/config page.
|
||||
// This also provides the page for all devel_generate links.
|
||||
$routes['devel_generate.admin_config_generate'] = new Route(
|
||||
'/admin/config/development/generate',
|
||||
[
|
||||
'_controller' => '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage',
|
||||
'_title' => 'Generate',
|
||||
],
|
||||
[
|
||||
'_permission' => 'administer devel_generate',
|
||||
]
|
||||
);
|
||||
|
||||
return $routes;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user