devel_wizard-2.x-dev/src/Plugin/DevelWizard/Spell/ConfigEntityAdminViewSpellBase.php

src/Plugin/DevelWizard/Spell/ConfigEntityAdminViewSpellBase.php
<?php

declare(strict_types=1);

namespace Drupal\devel_wizard\Plugin\DevelWizard\Spell;

use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Template\TwigEnvironment;
use Drupal\devel_wizard\ShellProcessFactoryInterface;
use Drupal\devel_wizard\Spell\SpellManagerInterface;
use Drupal\devel_wizard\Spell\SpellTraitPackageManager;
use Drupal\devel_wizard\Utils;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Path;
use Symfony\Component\String\UnicodeString;

/**
 * Creates a page with views for administrators to list content instances for a specific bundle.
 */
abstract class ConfigEntityAdminViewSpellBase extends ConfigEntitySpellBase {

  use SpellTraitPackageManager;

  protected SpellManagerInterface $spellManager;

  protected UuidInterface $uuid;

  protected TwigEnvironment $twig;

  protected Filesystem $fs;

  /**
   * @abstract
   */
  protected string $primaryTabPath = '';

  /**
   * @abstract
   */
  protected string $bundleIdInConfigTemplate = '';

  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('messenger'),
      $container->get('logger.channel.devel_wizard_spell'),
      $container->get('string_translation'),
      $container->get('devel_wizard.utils'),
      $container->get('config.factory'),
      $container->get('entity_type.manager'),
      $container->get('plugin.manager.devel_wizard.spell'),
      $container->get('devel_wizard.shell_process_factory'),
      $container->get('module_installer'),
      $container->get('extension.list.module'),
      $container->get('uuid'),
      $container->get('twig'),
      new Filesystem(),
    );
  }

  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    MessengerInterface $messenger,
    LoggerInterface $logger,
    TranslationInterface $stringTranslation,
    Utils $utils,
    ConfigFactoryInterface $configFactory,
    EntityTypeManagerInterface $entityTypeManager,
    SpellManagerInterface $spellManager,
    ShellProcessFactoryInterface $shellProcessFactory,
    ModuleInstallerInterface $moduleInstaller,
    ModuleExtensionList $moduleList,
    UuidInterface $uuid,
    TwigEnvironment $twig,
    ?Filesystem $fs = NULL,
  ) {
    $this->entityTypeManager = $entityTypeManager;
    $this->spellManager = $spellManager;
    $this->shellProcessFactory = $shellProcessFactory;
    $this->moduleInstaller = $moduleInstaller;
    $this->moduleList = $moduleList;
    $this->uuid = $uuid;
    $this->twig = $twig;
    $this->fs = $fs ?: new Filesystem();

    parent::__construct(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $messenger,
      $logger,
      $stringTranslation,
      $utils,
      $configFactory,
    );
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'machine_name' => '',
      'module' => [
        'machine_name' => 'app_core',
      ],
    ];
  }

  protected function populateCalculatedConfigurationValues(): static {
    parent::populateCalculatedConfigurationValues();

    $conf =& $this->configuration;
    if (empty($conf['machine_name'])) {
      throw new \InvalidArgumentException('bundle.machine_name is required');
    }

    if (empty($conf['module']['machine_name'])) {
      throw new \InvalidArgumentException('module.machine_name is required');
    }

    $conf['idPrefix'] = (new UnicodeString($conf['module']['machine_name']))
      ->split('_')[0]
      ->toString();

    $conf['module']['upperCamel'] = (new UnicodeString("a_{$conf['module']['machine_name']}"))
      ->camel()
      ->trimPrefix('a')
      ->toString();

    $conf['configEntityTypeIdUpperCamel'] = (new UnicodeString("a_{$this->configEntityTypeId}"))
      ->camel()
      ->trimPrefix('a')
      ->toString();

    $conf['contentEntityTypeIdUpperCamel'] = (new UnicodeString("a_{$this->contentEntityTypeId}"))
      ->camel()
      ->trimPrefix('a')
      ->toString();

    $conf['linksTaskBase']['class'] = "LinksTaskEntityOverview";
    $conf['linksTaskBase']['classNamespace'] = "Drupal\\{$conf['module']['machine_name']}\Plugin\Derivative";
    $conf['linksTaskBase']['classFqn'] = "{$conf['linksTaskBase']['classNamespace']}\\{$conf['linksTaskBase']['class']}";

    $conf['linksTaskDeriver']['class'] = "{$conf['linksTaskBase']['class']}{$conf['contentEntityTypeIdUpperCamel']}";
    $conf['linksTaskDeriver']['classNamespace'] = $conf['linksTaskBase']['classNamespace'];
    $conf['linksTaskDeriver']['classFqn'] = "{$conf['linksTaskDeriver']['classNamespace']}\\{$conf['linksTaskDeriver']['class']}";

    $conf['linksActionBase']['class'] = "LinksActionEntityAdd";
    $conf['linksActionBase']['classNamespace'] = "Drupal\\{$conf['module']['machine_name']}\Plugin\Derivative";
    $conf['linksActionBase']['classFqn'] = "{$conf['linksActionBase']['classNamespace']}\\{$conf['linksActionBase']['class']}";

    $conf['linksActionDeriver']['class'] = "{$conf['linksActionBase']['class']}{$conf['contentEntityTypeIdUpperCamel']}";
    $conf['linksActionDeriver']['classNamespace'] = $conf['linksActionBase']['classNamespace'];
    $conf['linksActionDeriver']['classFqn'] = "{$conf['linksActionDeriver']['classNamespace']}\\{$conf['linksActionDeriver']['class']}";

    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $configuration = $this->getConfiguration();
    $configEntityType = $this->getConfigEntityType();
    // @todo Move $machineNamePattern into a common place.
    $machineNamePattern = '[a-z][a-z0-9_]*';

    $form['machine_name'] = [
      '#type' => 'textfield',
      '#required' => TRUE,
      '#title' => $configEntityType?->getLabel() ?: $this->t('Bundle'),
      '#description' => $this->t(
        'The @entity_type.label you want to create the admin view for.',
        [
          '@entity_type.label' => $configEntityType?->getLabel() ?: $this->configEntityTypeId,
        ],
      ),
      '#default_value' => $configuration['machine_name'],
      '#autocomplete_route_name' => 'devel_wizard.autocomplete.config_entity_instance',
      '#autocomplete_route_parameters' => [
        'entityTypeId' => $this->configEntityTypeId,
      ],
    ];

    $form['module'] = [
      '#tree' => TRUE,
      '#type' => 'container',
      'machine_name' => [
        '#type' => 'textfield',
        '#required' => TRUE,
        '#title' => $this->t('Module machine-name'),
        '#description' => $this->t('Machine-name of the module to put the new code files into. If not exists then it will be created in the modules/custom directory'),
        '#default_value' => $configuration['module']['machine_name'],
        '#pattern' => $machineNamePattern,
        '#autocomplete_route_name' => 'devel_wizard.autocomplete.module',
      ],
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    // @todo Implement validateConfigurationForm() method.
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state->getValue($form['#parents'], []);
    $this->setConfiguration($values);
  }

  /**
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   * @throws \Drupal\Core\Entity\EntityMalformedException
   * @throws \Drupal\Core\Entity\EntityStorageException
   * @throws \Twig\Error\Error
   */
  protected function doIt(): static {
    if (!isset($this->batchContext['sandbox']['current_step'])) {
      $this->batchContext['sandbox']['current_step'] = 'composer_packages_require';
    }

    $configuration = $this->getConfiguration();
    switch ($this->batchContext['sandbox']['current_step']) {
      case 'composer_packages_require':
        $this->doItComposerRequire();
        drupal_flush_all_caches();

        $this->batchContext['message'] = $this->t('Create primary and secondary tab related modules');
        $this->batchContext['sandbox']['current_step'] = 'module_create';
        $this->batchContext['sandbox']['finished'] = 0.2;
        break;

      case 'module_create':
        if (!$this->moduleList->exists($configuration['module']['machine_name'])) {
          $moduleSpellConfiguration = $configuration['module'];
          $moduleSpellConfiguration['type'] = 'custom';
          $moduleSpellContext = [];
          /* @noinspection PhpUnhandledExceptionInspection */
          $moduleSpell = $this->spellManager->createInstance(
            'devel_wizard_module',
            $moduleSpellConfiguration,
          );
          $moduleSpell->prepare();
          $moduleSpell->abracadabra($moduleSpellContext);
          drupal_flush_all_caches();
        }

        $this->batchContext['message'] = $this->t('Install required modules');
        $this->batchContext['sandbox']['current_step'] = 'module_install';
        $this->batchContext['sandbox']['finished'] = 0.4;
        break;

      case 'module_install':
        $this->doItModuleInstall();
        drupal_flush_all_caches();

        $this->batchContext['message'] = $this->t('Create views and menu items');
        $this->batchContext['sandbox']['current_step'] = 'tabs_create';
        $this->batchContext['sandbox']['finished'] = 0.6;
        break;

      case 'tabs_create':
        $this->doItTabsPermissions();
        $this->doItTabsCreate();
        drupal_flush_all_caches();

        $this->batchContext['message'] = $this->t('Create primary and secondary tabs');
        $this->batchContext['sandbox']['current_step'] = 'tasks_and_actions';
        $this->batchContext['sandbox']['finished'] = 0.8;
        break;

      case 'tasks_and_actions':
        $this->doItLinksTasks();
        $this->doItLinksActions();
        drupal_flush_all_caches();

        $this->batchContext['message'] = $this->t('Finished');
        $this->batchContext['sandbox']['current_step'] = '_finished';
        $this->batchContext['sandbox']['finished'] = 1.0;
        break;

      case '_finished':
        $this->batchContext['sandbox']['finished'] = 1.0;
        break;
    }

    return $this;
  }

  protected function doItComposerRequire(): static {
    $this->installComposerPackages('prod', $this->getRequiredPackagesProd());
    $this->installComposerPackages('dev', $this->getRequiredPackagesDev());

    return $this;
  }

  protected function doItModuleInstall(): static {
    foreach ($this->getModuleNamesToInstall() as $moduleName) {
      $this->ensureModuleInstalled($moduleName);
    }

    return $this;
  }

  /**
   * Creates custom permission entries.
   */
  protected function doItTabsPermissions(): static {
    $configuration = $this->getConfiguration();
    $moduleMachineName = $configuration['module']['machine_name'];
    $configEntityType = $this->getConfigEntityType();
    $configEntityTypeLabel = $configEntityType ? $configEntityType->getLabel() : $this->configEntityTypeId;
    $modulePath = $this->getModulePath($moduleMachineName);
    $ymlFileName = Path::join(
      $modulePath,
      "{$moduleMachineName}.permissions.yml",
    );
    $entries = [
      "{$moduleMachineName}.{$this->configEntityTypeId}.overview" => [
        'title' => "Access to {$configEntityTypeLabel} overview page",
        'description' => "Allows users to access to the {$configEntityTypeLabel} overview page.",
      ],
    ];

    $this->utils->ymlFileReplace($ymlFileName, $entries);

    return $this;
  }

  /**
   * @throws \Drupal\Core\Entity\EntityMalformedException
   * @throws \Drupal\Core\Entity\EntityStorageException
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function doItTabsCreate(): static {
    /* @noinspection PhpUnhandledExceptionInspection */
    $viewStorage = $this->entityTypeManager->getStorage('view');

    // @todo Config views.view.content" is required.
    // @todo Check that if the links.task entry for the main route is exists or not.
    $viewRaw = $this->getMainTabView();
    if ($viewRaw) {
      /** @var \Drupal\views\ViewEntityInterface $view */
      $view = $viewStorage->load($viewRaw['id']);
      if ($view) {
        $this->messageConfigEntityExists($view);
      }
      else {
        /** @var \Drupal\views\ViewEntityInterface $view */
        $view = $viewStorage->create($viewRaw);
        $view->save();
        $this->messageConfigEntityCreate($view);
      }
    }

    $viewRaw = $this->getSecondaryTaskView();
    if ($viewRaw) {
      /** @var \Drupal\views\ViewEntityInterface $view */
      $view = $viewStorage->load($viewRaw['id']);
      if ($view) {
        $this->messageConfigEntityExists($view);
      }
      else {
        /** @var \Drupal\views\ViewEntityInterface $view */
        $view = $viewStorage->create($viewRaw);
        $view->save();
        $this->messageConfigEntityCreate($view);
      }
    }

    return $this;
  }

  /**
   * @throws \Twig\Error\Error
   */
  protected function doItLinksTasks(): static {
    $conf =& $this->configuration;
    $modulePath = $this->getModulePath($conf['module']['machine_name']);

    $ymlFilePath = Path::join(
      $modulePath,
      "{$conf['module']['machine_name']}.links.task.yml",
    );
    $ymlEntries = [
      "{$conf['module']['machine_name']}_{$this->contentEntityTypeId}_overview" => [
        'class' => 'Drupal\Core\Menu\LocalTaskDefault',
        'deriver' => "{$conf['linksTaskDeriver']['classFqn']}",
      ],
    ];
    $this->utils->ymlFileReplace($ymlFilePath, $ymlEntries);

    $baseTemplate = '@devel_wizard/derivative/links-task/devel_wizard.entity-overview.class.php.twig';
    $baseFilePath = Path::join(
      $modulePath,
      'src',
      'Plugin',
      'Derivative',
      "{$conf['linksTaskBase']['class']}.php",
    );
    $baseFileContent = $this->twig->render($baseTemplate, $conf);
    $this->fs->mkdir(Path::getDirectory($baseFilePath));
    $this->fs->dumpFile($baseFilePath, $baseFileContent);
    $this->messageFilesystemEntryCreate($baseFilePath);

    $derivativeTemplate = "@devel_wizard/derivative/links-task/devel_wizard.entity-overview-{$this->contentEntityTypeId}.class.php.twig";
    $derivativeFilePath = Path::join(
      $modulePath,
      'src',
      'Plugin',
      'Derivative',
      "{$conf['linksTaskDeriver']['class']}.php",
    );
    $derivativeFileContent = $this->twig->render($derivativeTemplate, $conf);
    $this->fs->mkdir(Path::getDirectory($derivativeFilePath));
    $this->fs->dumpFile($derivativeFilePath, $derivativeFileContent);
    $this->messageFilesystemEntryCreate($derivativeFilePath);

    return $this;
  }

  /**
   * @throws \Twig\Error\Error
   */
  protected function doItLinksActions(): static {
    $conf =& $this->configuration;
    $modulePath = $this->getModulePath($conf['module']['machine_name']);

    $ymlFilePath = Path::join(
      $modulePath,
      "{$conf['module']['machine_name']}.links.action.yml",
    );
    $ymlEntries = [
      "{$conf['module']['machine_name']}_{$this->contentEntityTypeId}_add" => [
        'class' => 'Drupal\Core\Menu\LocalActionDefault',
        'deriver' => "{$conf['linksActionDeriver']['classFqn']}",
      ],
    ];
    $this->utils->ymlFileReplace($ymlFilePath, $ymlEntries);

    $baseTemplate = '@devel_wizard/derivative/links-action/devel_wizard.entity-add.class.php.twig';
    $baseFilePath = Path::join(
      $modulePath,
      'src',
      'Plugin',
      'Derivative',
      "{$conf['linksActionBase']['class']}.php",
    );
    $baseFileContent = $this->twig->render($baseTemplate, $conf);
    $this->fs->mkdir(Path::getDirectory($baseFilePath));
    $this->fs->dumpFile($baseFilePath, $baseFileContent);
    $this->messageFilesystemEntryCreate($baseFilePath);

    $derivativeTemplate = "@devel_wizard/derivative/links-action/devel_wizard.entity-add-{$this->contentEntityTypeId}.class.php.twig";
    $derivativeFilePath = Path::join(
      $modulePath,
      'src',
      'Plugin',
      'Derivative',
      "{$conf['linksActionDeriver']['class']}.php",
    );
    $derivativeFileContent = $this->twig->render($derivativeTemplate, $conf);
    $this->fs->mkdir(Path::getDirectory($derivativeFilePath));
    $this->fs->dumpFile($derivativeFilePath, $derivativeFileContent);
    $this->messageFilesystemEntryCreate($derivativeFilePath);

    return $this;
  }

  public function getRequiredPackagesProd(): array {
    return [];
  }

  public function getRequiredPackagesDev(): array {
    return [];
  }

  protected function getModuleNamesToInstall(): array {
    $configuration = $this->getConfiguration();

    return array_filter([
      'views',
      $this->provider,
      $configuration['module']['machine_name'],
    ]);
  }

  protected function getMainTabView(): ?array {
    return NULL;
  }

  protected function getSecondaryTaskViewId(): string {
    $conf =& $this->configuration;

    return "{$conf['idPrefix']}_{$this->contentEntityTypeId}_{$conf['machine_name']}_admin";
  }

  protected function getSecondaryTaskView(): ?array {
    $conf =& $this->configuration;
    $contentEntityType = $this->getContentEntityType();
    $configEntityType = $this->getConfigEntityType();
    $configPrefix = $configEntityType->getConfigPrefix();
    $bundleKey = $contentEntityType->getKey('bundle');

    $filePath = $this->getConfigTemplateFilePath("views.view.{$this->contentEntityTypeId}.bundle_admin.yml");
    $data = Yaml::decode(file_get_contents($filePath));

    $data['uuid'] = $this->uuid->generate();
    $data['id'] = $this->getSecondaryTaskViewId();
    $data['label'] = "{$this->contentEntityTypeId} - {$conf['machine_name']} - admin";

    $key = array_search("{$this->provider}.{$configPrefix}.{$this->bundleIdInConfigTemplate}", $data['dependencies']);
    if ($key !== FALSE) {
      unset($data['dependencies'][$key]);
    }

    $data['dependencies'][] = "{$this->provider}.$configPrefix.{$conf['machine_name']}";

    // @todo Get the bundle label.
    $data['display']['default']['display_options']['title'] = $conf['machine_name'];
    $data['display']['overview']['display_options']['menu']['title'] = $conf['machine_name'];
    $data['display']['default']['display_options']['filters'][$bundleKey]['value'] = [
      $conf['machine_name'] => $conf['machine_name'],
    ];

    return $data;
  }

  protected function getConfigTemplateFilePath(string $fileName): string {
    return Path::join(
      $this->moduleList->getPath('devel_wizard'),
      'config',
      'template',
      $fileName,
    );
  }

  /**
   * Creates custom routing entries.
   */
  protected function createDerivativeRouting(): static {
    $configuration = $this->getConfiguration();
    $moduleMachineName = $configuration['module']['machine_name'];
    $configEntityType = $this->getConfigEntityType();
    $dashedContentEntityTypeId = str_replace('_', '-', $this->contentEntityTypeId);
    $modulePath = $this->getModulePath($moduleMachineName);
    $ymlFileName = Path::join(
      $modulePath,
      "{$moduleMachineName}.routing.yml",
    );
    $entries = [
      "{$moduleMachineName}.{$this->contentEntityTypeId}.collection" => [
        'path' => "/admin/content/{$dashedContentEntityTypeId}",
        'defaults' => [
          '_title' => $configEntityType ? $configEntityType->getLabel()->render() : $this->configEntityTypeId,
          '_entity_list' => $this->contentEntityTypeId,
        ],
        'requirements' => [
          // @todo Permission.
          '_permission' => 'access content',
        ],
      ],
    ];
    $this->utils->ymlFileReplace($ymlFileName, $entries);

    return $this;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc