spalp-8.x-1.2/src/Service/Core.php

src/Service/Core.php
<?php

namespace Drupal\spalp\Service;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\spalp\Event\SpalpConfigAlterEvent;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\spalp\Event\SpalpConfigLocationAlterEvent;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

/**
 * Spalp Core Service.
 *
 * @package Drupal\spalp\Service
 */
class Core {

  use StringTranslationTrait;

  /**
   * Logger Factory.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $loggerFactory;

  /**
   * Module Handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The Entity Type Manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Event Dispatcher.
   *
   * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher;

  /**
   * Language Manager.
   *
   * @var \Drupal\Core\Language\LanguageManager
   */
  protected $languageManager;

  /**
   * Spalp Core constructor.
   *
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory
   *   LoggerChannelFactory.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
   *   Module Handler Interface.
   * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   Event Dispatcher interface.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   EntityTypeManagerInterface.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   LanguageManagerInterface.
   */
  public function __construct(
    LoggerChannelFactoryInterface $loggerFactory,
    ModuleHandlerInterface $moduleHandler,
    EventDispatcherInterface $event_dispatcher,
    EntityTypeManagerInterface $entity_type_manager,
    LanguageManagerInterface $language_manager
  ) {

    $this->loggerFactory = $loggerFactory;
    $this->moduleHandler = $moduleHandler;
    $this->eventDispatcher = $event_dispatcher;
    $this->entityTypeManager = $entity_type_manager;
    $this->languageManager = $language_manager;

  }

  /**
   * Create applanding nodes in each language when a module is enabled.
   *
   * @param string $module
   *   The machine name of the module being installed.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function createNodes($module) {

    // Does a node already exist?
    if ($this->getAppNode($module)) {
      // If so, do nothing.
      return;
    }

    $title = $this->moduleHandler->getName($module);

    // Import the configuration and text.
    $json = $this->getConfigFromJson($module);
    if (!empty($json)) {
      $node = $this->entityTypeManager->getStorage('node')->create(['type' => 'applanding']);

      $node->set('title', $title);
      $node->set('field_spalp_app_id', $module);

      // The node should initially be unpublished.
      $node->setUnpublished();

      $config_json = Json::encode($json);
      $node->set('field_spalp_config_json', $config_json);

      $this->loggerFactory->get('spalp')->notice(
        $this->t('Node @nid has been created for @title (@module)',
          [
            '@title' => $title,
            '@module' => $module,
            '@nid' => $node->id(),
          ]
        )
      );

      // Translate the node.
      $languages = $this->languageManager->getLanguages();
      foreach ($languages as $langcode => $language) {
        if (!$language->isDefault()) {
          // Get the JSON config for this language.
          $jsonTranslation = $this->getConfigFromJson($module, 'config', $langcode);

          // If the config is different from the default, add a translation.
          if ($jsonTranslation != $json) {
            $fields = [
              'title' => $title,
              'field_spalp_config_json' => Json::encode($jsonTranslation),
              // Each translation is unpublished by default.
              'status' => 0,
            ];
            $node->addTranslation($langcode, $fields);

            $this->loggerFactory->get('spalp')->notice(
              $this->t('Node @nid has been translated to @language for @title (@module)',
                [
                  '@title' => $title,
                  '@module' => $module,
                  '@nid' => $node->id(),
                  '@language' => $language->getName(),
                ]
              )
            );
          }
        }
      }
      $node->enforceIsNew();
      $node->save();
    }
  }

  /**
   * Get initial config from the module's JSON file.
   *
   * @param string $module
   *   The machine name of the module.
   * @param string $type
   *   Type to be used for schema json calls.
   * @param string $language
   *   The language code.
   *
   * @return array
   *   Array representation of the configuration settings.
   */
  public function getConfigFromJson($module, $type = 'config', $language = '') {
    $json = [];

    // Set up default paths to config files.
    $module_path = DRUPAL_ROOT . '/' . \Drupal::service('extension.list.module')->getPath($module);

    $base_filename = $module;
    if (!empty($language)) {
      $base_filename .= ".$language";
    }

    $config_locations = [
      'config' => $module_path . "/config/spalp/{$base_filename}.config.json",
      'schema' => $module_path . "/config/spalp/{$module}.config.schema.json",
    ];

    // Allow modules to change the config path.
    $event = new SpalpConfigLocationAlterEvent($module, $config_locations);
    $this->eventDispatcher->dispatch($event, SpalpConfigLocationAlterEvent::CONFIG_LOCATION_ALTER);
    $config_locations = $event->getConfigLocations();

    // Get the JSON from the file.
    $filename = $config_locations[$type];
    if (file_exists($filename)) {
      $string = file_get_contents($filename);
      $json = Json::decode($string);
    }

    return $json;
  }

  /**
   * Get the current text and configuration settings for an app.
   *
   * @param string $module
   *   The machine name of the module being installed.
   * @param string $language
   *   The language code.
   * @param int $revision
   *   The ID of a specific revision to load.
   *
   * @return array
   *   The text and configuration settings for the app json endpoint, as array.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function getAppConfig($module, $language = NULL, $revision = NULL) {
    $config = [];

    if ($language == NULL) {
      $language = $this->languageManager->getCurrentLanguage()->getId();
    }

    // Get the relevant node for the app.
    $node = $this->getAppNode($module, $language, $revision);
    if (!empty($node)) {

      // TODO: check permission to view the node and revision.
      $config_json = $node->field_spalp_config_json->value;
      $config = Json::decode($config_json);

      // Dispatch event to allow modules to change config.
      if (!empty($config)) {
        $event = new SpalpConfigAlterEvent($module, $config);
        $this->eventDispatcher->dispatch($event, SpalpConfigAlterEvent::APP_CONFIG_ALTER);

        $config = $event->getConfig();
      }
    }

    return $config;
  }

  /**
   * Get the relevant node for a single page app.
   *
   * @param string $module
   *   The machine name of the extending module.
   * @param string $language
   *   The language code.
   * @param int $revision
   *   The ID of a specific revision to load.
   *
   * @return \Drupal\Core\Entity\EntityInterface|null
   *   The applanding node for this module.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function getAppNode($module, $language = NULL, $revision = NULL) {
    $node_storage = $this->entityTypeManager->getStorage('node');

    $node = NULL;
    $query = $node_storage->getQuery()
      ->accessCheck(FALSE)
      ->condition('type', 'applanding')
      ->condition('field_spalp_app_id', $module);
    $nids = $query->execute();

    if (!empty($nids)) {
      // TODO: prevent more than 1 node per language being created for each app.
      $nid = end($nids);
      $node = $node_storage->load($nid);

      if (!empty($revision)) {
        $node = $node_storage->loadRevision($revision);
      }

      try {
        // Use the translation, if there is one.
        if (!empty($language)) {
          $node = $node->getTranslation($language);
        }
      }
      catch (\InvalidArgumentException $exception) {
        // If there's no relevant translation, log it.
        $this->loggerFactory->get('spalp')->notice(
          $this->t('Attempt to fetch non-existent translation of node @nid to @language for @module module.',
            [
              '@nid' => $node->id(),
              '@language' => $language,
              '@module' => $module,
            ]
          )
        );
      }
    }

    return $node;
  }

  /**
   * Prepare a link to the page head with the app's JSON endpoint URL.
   *
   * @param string $app_id
   *   The machine name of the extending module.
   * @param int $revision
   *   The node revision ID.
   *
   * @return array
   *   Render array for the link.
   */
  public function getJsonLink($app_id, $revision = NULL) {
    $parameters = ['app_id' => $app_id];
    if (!empty($revision)) {
      $parameters['revision'] = $revision;
    }
    $config_url = Url::fromRoute('entity.node.appjson', $parameters)->toString();
    $config_json = [
      [
        'type' => 'application/json',
        'id' => 'appConfig',
        'rel' => 'alternate',
        'href' => $config_url,
      ],
      TRUE,
    ];

    return $config_json;
  }

  /**
   * Set the configuration settings for an app.
   *
   * @param string $module
   *   The machine name of the module.
   * @param array $config_json
   *   The configuration settings.
   * @param bool $overwrite
   *   Whether to overwrite existing values on the node.
   * @param string $language
   *   The language code.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function setAppConfig($module, array $config_json = NULL, $overwrite = FALSE, $language = NULL) {

    // Get config from JSON file if none is provided.
    if ($config_json === NULL) {
      $config_json = $this->getConfigFromJson($module);

      if (empty($config_json)) {
        throw new \Exception(dt('@module does not provide JSON configuration.', [
          '@module' => $module,
        ]));
      }
    }

    $node = $this->getAppNode($module, $language);
    if (empty($node)) {
      throw new \Exception(dt('There is no app landing node for @module.', [
        '@module' => $module,
      ]));
    }

    // Get existing config from the applanding node.
    $config_node = $this->getAppConfig($module, $language);
    $config = $this->newAppConfig($config_node, $config_json, $overwrite);

    $node->set('field_spalp_config_json', Json::encode($config));
    $node->save();

  }

  /**
   * @param array $config_node
   *   The current configuration on the applanding node.
   * @param array $config_json
   *   The configuration
   * @param bool $overwrite
   *
   * @return array
   *   The merged configuration array.
   */
  public function newAppConfig($config_node, $config_json, $overwrite = FALSE) {
    // Merge the existing and new configuration.
    if ($overwrite) {
      // Overwrite node values with values from JSON.
      $config = NestedArray::mergeDeepArray([$config_node, $config_json], TRUE);
    }
    else {
      // Retain values in the node.
      $config = NestedArray::mergeDeepArray([$config_json, $config_node], TRUE);
    }

    return $config;
  }

  /**
   * Get the difference between config in the JSON file and the applanding node.
   *
   * @param string $module
   *   The machine name of the module.
   *
   * @return array
   *   Associative array of differences.
   *   'node_only': in the node, but not in JSON for the module.
   *   'json_only': in JSON, but not in the node.
   *   'diff': in both arrays, with different values.
   *   - 'node': the value on the node.
   *   - 'json': the value in JSON.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function getAppConfigDiff($module) {
    $config_node = $this->getAppConfig($module);
    $config_json = $this->getConfigFromJson($module);

    $diff = $this->arrayDiffRecursive($config_node, $config_json);

    return array_filter($diff);
  }

  /**
   * Recursively compare two arrays.
   *
   * @param array $config_node
   *   The configuration array from the applanding node.
   * @param array $config_json
   *   The configuration array from the JSON file.
   *
   * @return array
   *   Associative array of differences.
   *   'node_only': in $config_node, but not $config_json
   *   'json_only': in $config_json, but not $config_node
   *   'diff': in both arrays, with different values.
   *   - 'node': the value in $config_node
   *   - 'json': the value in $config_json
   */
  public function arrayDiffRecursive(array $config_node, array $config_json) {
    $result = ['node_only' => [], 'json_only' => [], 'diff' => []];
    foreach ($config_node as $key => $value) {
      if (is_array($value) && isset($config_json[$key]) && is_array($config_json[$key])) {
        $sub_result = $this->arrayDiffRecursive($value, $config_json[$key]);
        foreach (array_keys($sub_result) as $sub_key) {
          if (!empty($sub_result[$sub_key])) {
            $result[$sub_key] = array_merge_recursive($result[$sub_key],
              [$key => $sub_result[$sub_key]]);
          }
        }
      }
      else {
        if (isset($config_json[$key])) {
          if ($value !== $config_json[$key]) {
            $result['diff'][$key] = [
              'node' => $value,
              'json' => $config_json[$key],
            ];
          }
        }
        else {
          $result['node_only'][$key] = $value;
        }
      }
    }
    foreach ($config_json as $key => $value) {
      if (!isset($config_node[$key])) {
        $result['json_only'][$key] = $value;
      }
    }
    return $result;
  }

}

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

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