mix-1.1.0-rc1/src/Form/SettingsForm.php

src/Form/SettingsForm.php
<?php

namespace Drupal\mix\Form;

use Drupal\Component\Serialization\Json;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DrupalKernelInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\State\StateInterface;
use Drupal\mix\Controller\Mix;
use Drupal\mix\Controller\MixContentSyncController;
use Drupal\mix\EventSubscriber\MixContentSyncSubscriber;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Configure Mix settings for this site.
 */
class SettingsForm extends ConfigFormBase {

  /**
   * The URL generator.
   *
   * @var \Drupal\Core\Routing\UrlGeneratorInterface
   */
  protected $urlGenerator;

  /**
   * Stores the state storage service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * The drupal kernel.
   *
   * @var \Drupal\Core\DrupalKernelInterface
   */
  protected $kernel;

  /**
   * Constructs a Drupal\mix\Form\SettingsForm object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The factory for configuration objects.
   * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
   *   The url generator.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state key value store.
   * @param \Drupal\Core\DrupalKernelInterface $kernel
   *   The drupal kernel.
   */
  public function __construct(ConfigFactoryInterface $config_factory, UrlGeneratorInterface $url_generator, StateInterface $state, DrupalKernelInterface $kernel) {
    $this->setConfigFactory($config_factory);
    $this->urlGenerator = $url_generator;
    $this->state = $state;
    $this->kernel = $kernel;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('config.factory'),
      $container->get('url_generator'),
      $container->get('state'),
      $container->get('kernel'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'mix_settings';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return ['mix.settings'];
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {

    $config = $this->config('mix.settings');

    $form['dev'] = [
      '#type' => 'details',
      '#title' => $this->t('Development'),
      '#open' => TRUE,
    ];

    // Check dev mode and give tips.
    $devMode = $config->get('dev_mode');
    $form['dev']['dev_mode'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable development mode'),
      '#description' => $this->t('Quick switch between Dev/Prod modes to make module and theme develpment way more easier.'),
      '#default_value' => $devMode,
    ];

    // Help content for dev_mode configuration.
    $form['dev']['dev_mode_help'] = [
      '#type' => 'inline_template',
      '#template' => '<details>
        <summary>{% trans %}Dev mode vs. Prod mode{% endtrans %}</summary>
        <table>
          <tr>
            <th>{% trans %}Configuration items{% endtrans %}</th>
            <th>{% trans %}Dev mode{% endtrans %}</th>
            <th>{% trans %}Prod mode{% endtrans %}</th>
          </tr>
          <tr>
            <td>{% trans %}Twig templates debugging{% endtrans %}</td>
            <td>{% trans %}Enable twig debug{% endtrans %}<br>
                {% trans %}Enable auto reload{% endtrans %}<br>
                {% trans %}Disable cache{% endtrans %}</td>
            <td>{% trans %}Disable twig debug{% endtrans %}<br>
                {% trans %}Disable auto reload{% endtrans %}<br>
                {% trans %}Enable cache{% endtrans %}</td>
          </tr>
          <tr>
            <td>{% trans %}Backend caches (render cache, page cache, dynamic page cache, etc.){% endtrans %}</td>
            <td>{% trans %}Disable{% endtrans %}</td>
            <td>{% trans %}Enable{% endtrans %}</td>
          </tr>
          <tr>
            <td>{% trans %}CSS/JS aggregate and gzip{% endtrans %}</td>
            <td>{% trans %}Disable{% endtrans %}</td>
            <td>{% trans %}Enable{% endtrans %}</a></td>
          </tr>
          <tr>
            <td>{% trans %}Browser and proxy caches{% endtrans %}</td>
            <td>{% trans %}Disable{% endtrans %}</td>
            <td><a href="{{ performanceSettingsUrl }}" target="_blank">{% trans %}Settings{% endtrans %}</a></td>
          </tr>
          <tr>
            <td>{% trans %}Error message to display{% endtrans %}</td>
            <td>{% trans %}All messages, with backtrace information{% endtrans %}</td>
            <td><a href="{{ errorMessageSettingsUrl }}" target="_blank">{% trans %}Settings{% endtrans %}</a></td>
          </tr>
          </table>
        </details>',
      '#context' => [
        'performanceSettingsUrl' => $this->urlGenerator->generateFromRoute('system.performance_settings', [], ['fragment' => 'edit-caching']),
        'errorMessageSettingsUrl' => $this->urlGenerator->generateFromRoute('system.logging_settings'),
      ],
    ];

    $form['remove_x_generator'] = [
      '#title' => $this->t('Remove X-Generator'),
      '#type' => 'checkbox',
      '#description' => $this->t('Remove HTTP header "X-Generator" and meta @meta to obfuscate that your website is running on Drupal.', ['@meta' => '<meta name="Generator" content="Drupal 10 (https://www.drupal.org)">']),
      '#default_value' => $config->get('remove_x_generator'),
    ];

    $form['hide_revision_field'] = [
      '#title' => $this->t('Hide revision field'),
      '#type' => 'checkbox',
      '#description' => $this->t('Hide revision field to all users except UID 1 to provide a clear UI'),
      '#default_value' => $config->get('hide_revision_field'),
    ];

    $form['hide_submit'] = [
      '#title' => $this->t('Enable "Hide submit button"'),
      '#description' => $this->t('To avoid duplicate form submissions, disable the submit button after it has been clicked.'),
      '#type' => 'checkbox',
      '#default_value' => $config->get('hide_submit'),
    ];

    $form['unsaved_form_confirm'] = [
      '#title' => $this->t('Enable "Unsaved form confirmation"'),
      '#description' => $this->t('Show a confirm dialog when user is about to leave an unsaved form.'),
      '#type' => 'checkbox',
      '#default_value' => $config->get('unsaved_form_confirm'),
    ];

    $form['standalone_password_page'] = [
      '#title' => $this->t('Enable "Standalone change password page"'),
      '#description' => $this->t('Move password fields in user form to a standalone password change page for better UX'),
      '#type' => 'checkbox',
      '#default_value' => $config->get('standalone_password_page'),
    ];

    // Show form ID.
    $form['dev']['show_form_id'] = [
      '#title' => $this->t('Show form ID'),
      '#type' => 'checkbox',
      '#description' => $this->t('Show the form ID and form alter function (<a href="https://api.drupal.org/hook_form_FORM_ID_alter" target="_blank"><code>hook_form_FORM_ID_alter()</code></a>) template before a form to make form altering easier.'),
      '#default_value' => $this->state->get('mix.show_form_id'),
    ];

    // Environment indicator.
    $form['dev']['environment_indicator'] = [
      '#title' => $this->t('Environment Indicator'),
      '#type' => 'textfield',
      '#description' => $this->t('Add a simple text (e.g. Development/Dev/Stage/Test or any other text) on the top of this site to help you identify current environment.
        <br>Leave it blank in the Live environment or hide the indicator.'),
      '#default_value' => $this->state->get('mix.environment_indicator'),
    ];

    $form['content_sync'] = [
      '#title' => $this->t('Content synchronization') . '<sup>' . $this->t('(Beta)') . '</sup>',
      '#type' => 'details',
    ];

    $form['content_sync']['show_content_sync_id'] = [
      '#title' => $this->t('Enable content synchronization'),
      '#type' => 'checkbox',
      '#default_value' => $config->get('show_content_sync_id'),
      '#description' => $this->t('Enable content synchronization to allow entity content to be sync like configurations and show links in content management pages.
        <ul>
          <li>Block - admin/structure/block/block-content</li>
          <li>Menu links - admin/structure/menu/manage/[menu-name]</li>
          <li>Taxonomy terms - admin/structure/taxonomy/manage/[taxonomy]/overview</li>
        </ul>'),
    ];

    $form['content_sync']['description_container'] = [
      '#type' => 'details',
      '#title' => $this->t('User guide'),
    ];

    $form['content_sync']['description_container']['description'] = [
      '#markup' => $this->t('By default, Drupal only synchronizes configurations between environments, not content.<br>
When we synchronize a block, only block config will be synchronized, not the block content, so you will get an error message about "This block is broken or missing."<br>
With this "Content Synchronize", we can synchronize selected content (blocks, menu links, taxonomy terms, etc.) between environments.<br>
<strong>Usage</strong>
<ul>
  <li>Enable the "Show content sync ID" below.</li>
  <li>Go to content list page, find "Export as config" and click the link to choose an item.</li>
  <li>Export configurations by Config export page or <code>drush cex</code> from Dev site.</li>
  <li>Import configurations by Config import page or <code>drush cim</code> to Prod site.</li>
  <li>Click the "Generate missing content" button below to create non-existent contents.</li>
</ul>
Note: To avoid unexpected content updates, only non-existent content will be created by now.'),
    ];

    if (!Mix::isContentSyncReady()) {
      $moduleListUrl = $this->urlGenerator->generate('system.modules_list');
      $form['content_sync']['show_content_sync_id']['#prefix'] = '<div class="mix-box mix-warning">' . $this->t('Please enable core modules <a href="@config" target="_blank">Configuration Manager</a> and <a href="@serialization" target="_blank">Serialization</a> before using this feature.', [
        '@config' => $moduleListUrl . '#module-config',
        '@serialization' => $moduleListUrl . '#module-serialization',
      ]) . '</div>';
      $form['content_sync']['show_content_sync_id']['#attached']['library'][] = 'mix/preset';
      $form['content_sync']['show_content_sync_id']['#default_value'] = FALSE;
      $form['content_sync']['show_content_sync_id']['#disabled'] = TRUE;
    }

    $form['content_sync']['advanced'] = [
      '#title' => $this->t('Advanced'),
      '#type' => 'details',
    ];

    $content_sync_ids = $config->get('content_sync_ids') ?: [];
    $form['content_sync']['advanced']['content_sync_ids'] = [
      '#title' => $this->t('Content sync IDs'),
      '#type' => 'textarea',
      '#description' => $this->t('One content sync ID per line.'),
      '#default_value' => implode(PHP_EOL, $content_sync_ids),
      '#prefix' => '<div class="form-item__description">' . $this->t('Content Sync ID will be added/removed from the following textarea automatically when you selected/unselected an item to sync in the content list pages (e.g. block, menu link and term list pages). <br>
You can also edit it manually.') . '</div>',
    ];

    $form['content_sync']['content_sync_generate_content'] = [
      '#type' => 'submit',
      '#value' => $this->t('Generate missing contents'),
      '#submit' => [[$this, 'generateContentSubmit']],
    ];
    if (!Mix::isContentSyncEnabled()) {
      $form['content_sync']['content_sync_generate_content']['#disabled'] = TRUE;
    }
    // Configuration management section.
    $form['cm'] = [
      '#type' => 'details',
      '#title' => $this->t('Configuration Management'),
      '#open' => TRUE,
    ];

    $form['cm']['config_import_ignore_mode'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable config import ignore'),
      '#description' => $this->t('By enabling this on the Prod site, you can ignore Dev modules and configurations to be imported, and use it for configurations override.<br>
For more details please see the <a href="https://www.drupal.org/docs/contributed-modules/mix/config-import-ignore" target="_blank">online documentation</a>.'),
      '#default_value' => $config->get('config_import_ignore.mode'),
    ];

    $form['cm']['config_import_ignore_list'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Ignored configuration items'),
      '#description' => $this->t('One item per line.<br>
# To ignore a dev-related module and configurations to be enabled on the Prod site.<br>
<code>core.extenstion:module.devel</code><br>
<code>devel.settings</code><br>
# To ignore or override a configuration item, use the format: <em>config_name:key</em><br>
<code>mix.settings:dev_mode</code><br>
<code>system.site:page.front</code><br>
# To ignore or override an entire configuration, use the format: <em>config_name</em><br>
<code>system.site</code>
'),
      '#states' => [
        'visible' => [
          ':input[name="config_import_ignore_mode"]' => ['checked' => TRUE],
        ],
      ],
      '#default_value' => implode(PHP_EOL, $config->get('config_import_ignore.list') ?? []),
    ];

    $form['error_pages'] = [
      '#type' => 'details',
      '#title' => $this->t('Error pages'),
      '#open' => TRUE,
    ];

    $errorPageDesc = $this->t('Use custom content replace the default 500 (internal server error) page.') . '<br>';
    $errorPageDesc .= '<a href="' . $this->urlGenerator->generateFromRoute('mix.site_500') . '" target="_blank">' . $this->t('View current error page.') . '</a>';
    $form['error_pages']['error_page'] = [
      '#title' => $this->t('Enable custom error page'),
      '#type' => 'checkbox',
      '#default_value' => $config->get('error_page.mode'),
      '#description' => $errorPageDesc,
    ];

    $form['error_pages']['error_page_content'] = [
      '#title' => $this->t('Error page content'),
      '#type' => 'textarea',
      '#default_value' => $config->get('error_page.content'),
      '#description' => $this->t('Custom content or HTML code of the error page.'),
      '#rows' => 26,
      '#states' => [
        'visible' => [
          ':input[name="error_page"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $form['seo'] = [
      '#title' => 'SEO',
      '#type' => 'details',
      '#open' => TRUE,
    ];

    $meta_settings_link = Link::createFromRoute($this->t('Meta tags'), 'mix.meta_settings', [], [
      'attributes' => [
        'class' => ['use-ajax'],
        'data-dialog-type' => 'modal',
        'data-dialog-options' => Json::encode([
          'width' => '80%',
        ]),
        'target' => '_blank',
      ],
    ]);
    $form['seo']['links'] = [
      '#markup' => $meta_settings_link->toString(),
    ];

    return parent::buildForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {

    $rebulidRouteCache = FALSE;
    $rebuildCache = FALSE;
    $rebuildContainer = FALSE;

    // Get configurations.
    $config = $this->config('mix.settings');

    // Get original dev_mode value, use to compare if changes later.
    $originDevMode = $config->get('dev_mode');

    // Normalize content_sync_ids.
    $content_sync_ids = array_map('trim', explode(PHP_EOL, $form_state->getValue('content_sync_ids')));
    MixContentSyncController::presave($content_sync_ids);

    // Invalidate cache when content_sync_ids changed.
    $oldContentSyncIds = $config->get('content_sync_ids');
    $contentSyncIdsChanged = $oldContentSyncIds !== $content_sync_ids;
    if ($contentSyncIdsChanged) {
      Cache::invalidateTags(['config:views.view.block_content']);
    }

    // @todo Refactor to a reusable function or method.
    $config_import_ignore_list = explode(PHP_EOL, $form_state->getValue('config_import_ignore_list'));
    $config_import_ignore_list = array_filter(array_map('trim', $config_import_ignore_list));
    sort($config_import_ignore_list);

    // Clear caches if 'show_content_sync_id' value changes.
    $oldContentSyncMode = $config->get('show_content_sync_id');
    $newContentSyncMode = $form_state->getValue('show_content_sync_id');
    if ($oldContentSyncMode != $newContentSyncMode) {
      $rebuildCache = TRUE;
    }

    // Rebuild route if 'standalone_password_page' value changes.
    $oldStandalonePasswordPage = $config->get('standalone_password_page');
    $newStandalonePasswordPage = $form_state->getValue('standalone_password_page');
    if ($oldStandalonePasswordPage != $newStandalonePasswordPage) {
      $rebulidRouteCache = TRUE;
    }

    // Save config.
    $this->config('mix.settings')
      ->set('dev_mode', $form_state->getValue('dev_mode'))
      ->set('hide_revision_field', $form_state->getValue('hide_revision_field'))
      ->set('hide_submit', $form_state->getValue('hide_submit'))
      ->set('unsaved_form_confirm', $form_state->getValue('unsaved_form_confirm'))
      ->set('standalone_password_page', $form_state->getValue('standalone_password_page'))
      ->set('remove_x_generator', $form_state->getValue('remove_x_generator'))
      ->set('error_page.mode', $form_state->getValue('error_page'))
      ->set('error_page.content', $form_state->getValue('error_page_content'))
      ->set('show_content_sync_id', $form_state->getValue('show_content_sync_id'))
      ->set('content_sync_ids', $content_sync_ids)
      ->set('config_import_ignore.mode', $form_state->getValue('config_import_ignore_mode'))
      ->set('config_import_ignore.list', $config_import_ignore_list)
      ->save();

    // Clear caches if 'show_form_id' value changes.
    $oldShowFormId = $this->state->get('mix.show_form_id');
    $newShowFormId = $form_state->getValue('show_form_id');
    if ($oldShowFormId != $newShowFormId) {
      $this->state->set('mix.show_form_id', $form_state->getValue('show_form_id'));
      $rebuildCache = TRUE;
    }

    // Save state value and invalidate caches when this config changes.
    $oldEnvironmentIndicator = $this->state->get('mix.environment_indicator');
    $newEnvironmentIndicator = $form_state->getValue('environment_indicator');
    if ($oldEnvironmentIndicator != $newEnvironmentIndicator) {
      $this->state->set('mix.environment_indicator', $newEnvironmentIndicator);
      // Rebuild all caches if the new value or the old value is empty.
      if (empty($oldEnvironmentIndicator) || empty($newEnvironmentIndicator)) {
        $rebuildCache = TRUE;
      }
      // Only invalidate cache tags when change between non-empty values
      // for better performance.
      else {
        Cache::invalidateTags(['mix:environment-indicator']);
      }
    }

    // Only run this when dev_mode has changed.
    $devModeChanged = $form_state->getValue('dev_mode') != $originDevMode;
    if ($devModeChanged) {
      // Clear cache to rebulid service providers and configurations
      // based on dev_mode.
      $rebuildCache = TRUE;
      $rebuildContainer = TRUE;
    }

    if ($rebulidRouteCache) {
      \Drupal::service("router.builder")->rebuild();
    }

    if ($rebuildCache) {
      drupal_flush_all_caches();
    }

    if ($rebuildContainer) {
      $this->kernel->rebuildContainer();
    }

    parent::submitForm($form, $form_state);
  }

  /**
   * Generate content based on imported content-config.
   *
   * @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 generateContentSubmit(array &$form, FormStateInterface $form_state) {
    $config = $this->config('mix.settings');
    $content_sync_ids = $config->get('content_sync_ids');
    $activeStorage = $config->getStorage();

    // Generate non-existent content.
    $reququeCounter = [];
    while ($content_sync_ids) {
      $configName = array_shift($content_sync_ids);

      // Parse entityType.
      $entityType = MixContentSyncSubscriber::parseEntityType($configName);
      // Ignore wrong sync ID or unsupported entity types.
      if (!$entityType || !in_array($entityType, array_keys(MixContentSyncSubscriber::$supportedEntityTypeMap))) {
        continue;
      }

      // Initialize counter.
      $reququeCounter[$configName] = $reququeCounter[$configName] ?? 1;
      // Ignore generate content which already tried multiple times.
      // Show an warning message.
      if ($reququeCounter[$configName] > 5) {
        $this->messenger()->addWarning($this->t('Failed to generate @entity_type: @config_name', [
          '@entity_type' => $entityType,
          '@config_name' => $configName,
        ]));
        continue;
      }

      // Load entity.
      $uuid = substr($configName, strrpos($configName, '.') + 1);
      $existedEntity = \Drupal::service('entity.repository')->loadEntityByUuid($entityType, $uuid);
      // Load config.
      $config = $activeStorage->read($configName);
      // Get normalized content.
      // @see MixContentSyncSubscriber::onExportTransform()
      $entityArray = $config['entity'];
      // Only generate non-existent content.
      if (!$existedEntity && $entityArray) {
        try {
          $serializer = \Drupal::service('serializer');
          $entity = $serializer->denormalize($entityArray, MixContentSyncSubscriber::$supportedEntityTypeMap[$entityType], 'yaml');
          $created = $entity->save();
          if ($created === SAVED_NEW) {

            $this->messenger()->addStatus($this->t('@entity_type "@name" was generated successfully.', [
              '@entity_type' => $entityType,
              '@name' => $entity->label(),
            ]));
          }
        }
        catch (\InvalidArgumentException $e) {
          // Handle exception situation that dependency content not exist.
          if (strpos($e->getMessage(), 'entity found with UUID')) {
            // Re-queue.
            array_push($content_sync_ids, $configName);
            // Requeue counter.
            $reququeCounter[$configName]++;
          }
        }
      }
    }

    drupal_flush_all_caches();
  }

}

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

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