SU-SWS/stanford_profile_helper

View on GitHub
modules/stanford_profile_drush/src/Commands/StanfordProfileCommands.php

Summary

Maintainability
A
2 hrs
Test Coverage
A
95%
<?php

namespace Drupal\stanford_profile_drush\Commands;

use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\field\FieldConfigInterface;
use Drush\Commands\DrushCommands;

/**
 * Class StanfordProfileCommands.
 *
 * @package Drupal\stanford_profile\Commands
 */
class StanfordProfileCommands extends DrushCommands {

  /**
   * Entity type manager service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Entity bundle service.
   *
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
   */
  protected $bundleInfo;

  /**
   * Entity field manager service.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $fieldManager;

  /**
   * Field type plugin manager service.
   *
   * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
   */
  protected $fieldTypeManager;

  /**
   * Array of original paragraphs with the key as the paragraph bundle.
   *
   * @var \Drupal\paragraphs\ParagraphInterface[]
   */
  protected $paragraphs;

  /**
   * Name of the content to create.
   *
   * @var string
   */
  protected $name;

  /**
   * Array of paragraph bundles to skip.
   *
   * @var array
   */
  protected $exclude;

  /**
   * Drush command constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   Entity type manager service.
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundleInfo
   *   Entity bundle service.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $fieldManager
   *   Entity field manager service.
   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $fieldTypeManager
   *   Field type plugin manager service.
   */
  public function __construct(EntityTypeManagerInterface $entityTypeManager, EntityTypeBundleInfoInterface $bundleInfo, EntityFieldManagerInterface $fieldManager, FieldTypePluginManagerInterface $fieldTypeManager) {
    parent::__construct();
    $this->entityTypeManager = $entityTypeManager;
    $this->bundleInfo = $bundleInfo;
    $this->fieldManager = $fieldManager;
    $this->fieldTypeManager = $fieldTypeManager;
  }

  /**
   * Generate a page with the possible combinations of components in rows.
   *
   * @option name
   *   Name the node something specific.
   * @option exclude
   *   Comma separated list of paragraphs to skip.
   *
   * @command stanford-profile:stress-test-components
   * @aliases spstc
   *
   * @params array $options
   *   Keyed array of command options.
   */
  public function generateStressTestNode(array $options = [
    'name' => NULL,
    'exclude' => '',
  ]) {
    $this->name = $options['name'] ?? 'Stress Test ' . date('F j Y');
    $this->exclude = explode(',', $options['exclude']);

    $fields_map = $this->fieldManager->getFieldMapByFieldType('entity_reference_revisions');
    foreach ($fields_map as $entity_type_id => $fields) {
      foreach ($fields as $field_name => $bundles) {
        foreach ($bundles['bundles'] as $bundle) {
          $this->generateStressTestContent($entity_type_id, $bundle, $field_name);
        }
      }
    }
  }

  /**
   * Generate the entity that holds all the react paragraphs.
   *
   * @param string $entity_type_id
   *   Entity type ID.
   * @param string $bundle
   *   Entity bundle name.
   * @param string $field
   *   React paragraphs field name.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function generateStressTestContent($entity_type_id, $bundle, $field) {
    $label_key = $this->entityTypeManager->getDefinition($entity_type_id)
      ->getKey('label');
    $bundle_key = $this->entityTypeManager->getDefinition($entity_type_id)
      ->getKey('bundle');

    /** @var \Drupal\field\FieldConfigInterface $field_config */
    $field_config = $this->entityTypeManager->getStorage('field_config')
      ->load("$entity_type_id.$bundle.$field");

    if ($field_config->getSetting('handler') != 'default:paragraph_row') {
      return;
    }

    $this->entityTypeManager->getStorage($entity_type_id)->create([
      $label_key => $this->name,
      $bundle_key => $bundle,
      $field => $this->getRowFieldValues($field_config),
    ])->save();
  }

  /**
   * Build the rows with paragraphs on them.
   *
   * @param \Drupal\field\FieldConfigInterface $field
   *   Row entity reference field entity.
   *
   * @return array
   *   Array of field values for the given field.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function getRowFieldValues(FieldConfigInterface $field): array {
    $row_bundle = $this->getRowBundle($field);
    $row_fields = $this->fieldManager->getFieldDefinitions($field->getSetting('target_type'), $row_bundle);

    $bundle_key = $this->entityTypeManager->getDefinition($field->getSetting('target_type'))
      ->getKey('bundle');

    $row_values = [];

    foreach ($row_fields as $row_field) {
      if ($row_field instanceof FieldConfigInterface && $row_field->getType() == 'entity_reference_revisions') {
        foreach ($this->getParagraphBundles($row_field) as $paragraph_bundle) {
          foreach ([12, 6, 4, 3] as $width) {

            $row = $this->entityTypeManager->getStorage($field->getSetting('target_type'))
              ->create([
                $bundle_key => $row_bundle,
                $row_field->getName() => $this->getParagraphFieldValues($paragraph_bundle, $width),
              ]);
            $row->save();

            $row_values[] = [
              'target_id' => $row->id(),
              'target_revision_id' => $row->getRevisionId(),
            ];
          }
        }
      }
    }
    return $row_values;
  }

  /**
   * Get the paragraph row bundle name.
   *
   * @param \Drupal\field\FieldConfigInterface $field
   *   Entity reference field.
   *
   * @return string
   *   Row bundle name.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function getRowBundle(FieldConfigInterface $field) {
    $handler_settings = $field->getSetting('handler_settings');
    $row_bundle = key($handler_settings['target_bundles']);

    if ($row_bundle) {
      return $row_bundle;
    }
    $row_types = $this->entityTypeManager->getStorage('paragraphs_row_type')
      ->loadMultiple();
    return key($row_types);
  }

  /**
   * Build paragraphs and load them into an array for the row field values.
   *
   * @param string $bundle
   *   Paragraph bundle name.
   * @param int $width
   *   Width of the paragraph to build.
   *
   * @return array
   *   Field value list array.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function getParagraphFieldValues($bundle, $width): array {
    $paragraphs = [];

    for ($i = 1; $i <= 12 / $width; $i++) {
      /** @var \Drupal\paragraphs\ParagraphInterface $new_paragraph */
      $paragraph = $this->createParagraph($bundle);
      $paragraph->setBehaviorSettings('react', [
        'width' => $width,
        'label' => "$width columns",
      ]);
      $paragraph->save();
      $paragraphs[] = [
        'target_id' => $paragraph->id(),
        'target_revision_id' => $paragraph->getRevisionId(),
      ];
    }

    return $paragraphs;
  }

  /**
   * Create a paragraph using field sample content.
   *
   * @param string $bundle
   *   Paragraph bundle name.
   *
   * @return \Drupal\Core\Entity\EntityInterface
   *   Generated paragraph with sample content.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function createParagraph($bundle): EntityInterface {
    // We've already created this paragraph type, just duplicated i.
    if (isset($this->paragraphs[$bundle])) {
      return $this->paragraphs[$bundle]->createDuplicate();
    }

    $bundle_fields = $this->fieldManager->getFieldDefinitions('paragraph', $bundle);
    $values = [
      'type' => $bundle,
    ];

    // Populate the paragraph with sample content in the fields.
    foreach ($bundle_fields as $field_name => $field_definition) {
      if (!$field_definition instanceof FieldConfigInterface) {
        continue;
      }
      $field_type = $field_definition->getType();
      $field_type_definition = $this->fieldTypeManager->getDefinition($field_type);
      $sample_value = NULL;

      try {
        $sample_value = $field_type_definition['class']::generateSampleValue($field_definition);
      }
      catch (\Exception $e) {
        // Move on to the next field.
        continue;
      }

      if ($sample_value) {
        // Alter the field samples because super long text without spaces does
        // no good. Nobody should put 255 characters into a field without any
        // spaces. I mean come on.
        $method = 'alterSample' . str_replace(' ', '', ucwords(str_replace('_', ' ', $field_type)));
        if (method_exists($this, $method)) {
          self::$method($sample_value);
        }
        $values[$field_name] = $sample_value;
      }
    }
    $paragraph = $this->entityTypeManager->getStorage('paragraph')
      ->create($values);
    $paragraph->setBehaviorSettings('react', [
      'width' => 12,
      'label' => '12 columns',
    ]);
    $paragraph->save();
    $this->paragraphs[$bundle] = $paragraph;
    return $paragraph;
  }

  /**
   * Based on the field settings, get the available paragraph bundles.
   *
   * @param \Drupal\field\FieldConfigInterface $paragraph_field
   *   Field entity targeting paragraphs.
   *
   * @return array
   *   Array of paragraph bundle machine names.
   */
  protected function getParagraphBundles(FieldConfigInterface $paragraph_field): array {
    $paragraph_bundles = $this->bundleInfo->getBundleInfo('paragraph');
    $handler_settings = $paragraph_field->getSetting('handler_settings');
    $allowed_bundles = [];
    foreach (array_keys($paragraph_bundles) as $bundle) {
      // Find out if the field is configured to allow the current bundle. The
      // field allows for "Exclude selected" which is the `negate` value. If a
      // paragraph has been added but the field settings haven't been resaved,
      // it may or may not be allowed in the field, so we check that too.
      if (isset($handler_settings['target_bundles_drag_drop'][$bundle])) {
        if ((bool) $handler_settings['negate'] === (bool) $handler_settings['target_bundles_drag_drop'][$bundle]['enabled']) {
          continue;
        }
      }
      elseif (isset($handler_settings['negate']) && !$handler_settings['negate']) {
        continue;
      }
      $allowed_bundles[] = $bundle;
    }
    $allowed_bundles = array_diff($allowed_bundles, $this->exclude);
    return array_filter($allowed_bundles);
  }

  /**
   * Alter the sample field value for the string field type.
   *
   * @param mixed $sample_value
   *   Sample value from the field.
   */
  protected static function alterSampleString(&$sample_value) {
    $new_value = $sample_value['value'];
    // Place random spaces into the string.
    for ($i = 0; $i < strlen($sample_value['value']); $i += 12) {
      $space_location = rand($i - 10, $i);
      $new_value = substr($new_value, 0, $space_location) . ' ' . substr($new_value, $space_location);
    }
    // Chop the string down. We don't need super long strings.
    $sample_value['value'] = substr($new_value, 0, 50);
  }

  /**
   * Alter the sample field value for the long text field type.
   *
   * @param mixed $sample_value
   *   Sample value from the field.
   */
  protected static function alterSampleTextLong(&$sample_value) {
    $sample_value['value'] = substr($sample_value['value'], 0, 250);
  }

  /**
   * Alter the sample field value for the link field type.
   *
   * @param mixed $sample_value
   *   Sample value from the field.
   */
  protected static function alterSampleLink(&$sample_value) {
    if (isset($sample_value['title'])) {
      $sample_value['title'] = substr($sample_value['title'], 0, 15);
    }
  }

}