cms_content_sync-3.0.x-dev/src/Entity/Flow.php

src/Entity/Flow.php
<?php

namespace Drupal\cms_content_sync\Entity;

use Drupal\cms_content_sync\Controller\FlowControllerSimple;
use Drupal\cms_content_sync\Plugin\Type\EntityHandlerPluginManager;
use Drupal\cms_content_sync\PullIntent;
use Drupal\cms_content_sync\PushIntent;
use Drupal\cms_content_sync\SyncIntent;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;

/**
 * Defines the "Content Sync - Flow" entity.
 *
 * @ConfigEntityType(
 *   id = "cms_content_sync_flow",
 *   label = @Translation("Content Sync - Flow"),
 *   handlers = {
 *     "list_builder" = "Drupal\cms_content_sync\Controller\FlowListBuilder",
 *     "form" = {
 *       "add" = "Drupal\cms_content_sync\Form\FlowForm",
 *       "edit" = "Drupal\cms_content_sync\Form\FlowForm",
 *       "delete" = "Drupal\cms_content_sync\Form\FlowDeleteForm",
 *       "copy_remote" = "Drupal\cms_content_sync\Form\CopyRemoteFlow",
 *     }
 *   },
 *   config_prefix = "flow",
 *   admin_permission = "administer cms content sync",
 *   entity_keys = {
 *     "id" = "id",
 *     "label" = "name",
 *   },
 *   config_export = {
 *     "id",
 *     "name",
 *     "variant",
 *     "type",
 *     "simple_settings",
 *     "per_bundle_settings",
 *     "sync_entities",
 *   },
 *   links = {
 *     "edit-form" = "/admin/config/services/cms_content_sync/flow/{cms_content_sync_flow}/edit",
 *     "delete-form" = "/admin/config/services/cms_content_sync/flow/{cms_content_sync_flow}/delete",
 *   }
 * )
 */
class Flow extends ConfigEntityBase implements FlowInterface {
  /**
   * @var string HANDLER_IGNORE
   *             Ignore this entity type / bundle / field completely
   */
  public const HANDLER_IGNORE = 'ignore';

  /**
   * @var string PREVIEW_DISABLED
   *             Hide these entities completely
   */
  public const PREVIEW_DISABLED = 'disabled';

  /**
   * @var string PREVIEW_TABLE
   *             Show these entities in a table view
   */
  public const PREVIEW_TABLE = 'table';

  /**
   * This Flow pushes entities.
   */
  public const TYPE_PUSH = 'push';

  /**
   * This Flow pulls entities.
   */
  public const TYPE_PULL = 'pull';

  /**
   * This Flow pushes and pulls entities.
   *
   * @deprecated will be removed in v3
   */
  public const TYPE_BOTH = 'both';

  public const VARIANT_SIMPLE = 'simple';
  public const VARIANT_PER_BUNDLE = 'per-bundle';


  public const CACHE_TAG_ANY_FLOW = 'cms_content_sync:flow';
  public const CACHE_ITEM_NAME_FLOWS = 'cms_content_sync/flows';

  /**
   * The variant. Simple is the new default, per-bundle is the old default for
   * Flows created before v2.1.
   * Simple offers only a handful of options and applies the same settings
   * to all entity types whereas per-bundle mode allows for entity type based
   * management of all configuration; so more complex but also more precise.
   * Simple uses our new embed frontend, so has better UX.
   *
   * @var string
   */
  public $variant;

  /**
   * Either "push" or "pull".
   *
   * @var string
   */
  public $type;

  /**
   * The Flow ID.
   *
   * @var string
   */
  public $id;

  /**
   * The Flow name.
   *
   * @var string
   */
  public $name;

  /**
   * The settings for variant Simple.
   *
   * @var array
   */
  public $simple_settings;

  /**
   * The settings for variant per-bundle. Hierarchy is:
   * [type machine name e.g. 'node']
   *   [bundle machine name e.g. 'page']
   *     ['settings']
   *       [...]
   *     ['properties']
   *       [property/field name e.g. 'title']
   *         [...].
   *
   * @var array
   */
  public $per_bundle_settings;

  /**
   * @var Flow[]
   *             All content synchronization configs. Use {@see Flow::getAll}
   *             to request them.
   */
  public static $all;

  /**
   * @var \Drupal\cms_content_sync\IFlowController
   */
  protected $handler;

  /**
   *
   */
  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
    Flow::clearFlowCache();

    return parent::postSave($storage, $update);
  }

  /**
   * Clear the flow cache.
   */
  public static function clearFlowCache() {
    Cache::invalidateTags([self::CACHE_TAG_ANY_FLOW]);
  }

  /**
   * Ensure that pools are pulled before the flows.
   */
  public function calculateDependencies() {
    parent::calculateDependencies();

    foreach ($this->getController()->getUsedPools() as $pool) {
      $this->addDependency('config', 'cms_content_sync.pool.' . $pool->id);
    }
  }

  /**
   * Provide the controller to act upon the stored configuration.
   *
   * @return \Drupal\cms_content_sync\IFlowController
   */
  public function getController() {
    if (!$this->handler) {
      if (Flow::VARIANT_SIMPLE === $this->variant) {
        $this->handler = new FlowControllerSimple($this);
      }
      else {
        throw new \Exception("Unknown Flow variant '" . $this->variant . "'. Unable to instantiate Controller.");
      }
    }

    return $this->handler;
  }

  /**
   * Get all flows pushing this entity.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   * @param $action
   * @param bool $include_dependencies
   *
   * @throws \Exception
   *
   * @return array|Flow[]
   */
  public static function getFlowsForPushing($entity, $action, $include_manual = TRUE, $include_dependencies = TRUE) {
    if (SyncIntent::ACTION_DELETE === $action) {
      $last_push = EntityStatus::getLastPushForEntity($entity);
      if (empty($last_push)) {
        return [];
      }
    }

    $flows = PushIntent::getFlowsForEntity(
          $entity,
          PushIntent::PUSH_AUTOMATICALLY,
          $action
      );

    if (!count($flows) && SyncIntent::ACTION_DELETE === $action && $include_manual) {
      $flows = PushIntent::getFlowsForEntity(
            $entity,
            PushIntent::PUSH_MANUALLY,
            $action
        );
    }

    if ($include_dependencies && !count($flows)) {
      $flows = PushIntent::getFlowsForEntity(
            $entity,
            PushIntent::PUSH_AS_DEPENDENCY,
            $action
        );
      if (count($flows)) {
        $infos = EntityStatus::getInfosForEntity(
          $entity->getEntityTypeId(),
          $entity->uuid()
          );

        $pushed = [];
        foreach ($infos as $info) {
          if (!in_array($info->getFlow(), $flows)) {
            continue;
          }
          if (in_array($info->getFlow(), $pushed)) {
            continue;
          }
          if (!$info->getLastPush()) {
            continue;
          }
          $pushed[] = $info->getFlow();
        }
        $flows = $pushed;
      }
    }

    return $flows;
  }

  /**
   * Get a unique version hash for the configuration of the provided entity type
   * and bundle.
   *
   * @param string $type_name
   *   The entity type in question.
   * @param string $bundle_name
   *   The bundle in question.
   *
   * @return string
   *   A 32 character MD5 hash of all important configuration for this entity
   *                type and bundle, representing it's current state and allowing potential
   *                conflicts from entity type updates to be handled smoothly
   */
  public static function getEntityTypeVersion($type_name, $bundle_name) {
    // @todo Include export_config keys in version definition for config entity types like webforms.
    if (EntityHandlerPluginManager::isEntityTypeFieldable($type_name)) {
      $entityFieldManager = \Drupal::service('entity_field.manager');
      $field_definitions = $entityFieldManager->getFieldDefinitions($type_name, $bundle_name);

      $field_strings = [];
      foreach ($field_definitions as $field_name => $field_definition) {
        if ($field_definition->isComputed()) {
          continue;
        }

        $field_strings[] = 'v2::' . join('::', [
          $field_name,
          $field_definition->getType(),
          $field_definition->isRequired() ? 'required' : 'optional',
          $field_definition->isTranslatable() ? 'translatable' : 'non-translatable',
        ]);
      }

      sort($field_strings);

      $version = join("\n", $field_strings);
    }
    else {
      $version = '';
    }

    return md5($version);
  }

  /**
   * Check whether the local deletion of the given entity is allowed.
   *
   * @return bool
   */
  public static function isLocalDeletionAllowed(EntityInterface $entity) {
    if (!$entity->uuid()) {
      return TRUE;
    }
    $entity_status = EntityStatus::getInfosForEntity(
          $entity->getEntityTypeId(),
          $entity->uuid()
      );
    foreach ($entity_status as $info) {
      if (!$info->getLastPull() || $info->isSourceEntity()) {
        continue;
      }
      $flow = $info->getFlow();
      if (!$flow) {
        continue;
      }

      $config = $flow->getController()->getEntityTypeConfig($entity->getEntityTypeId(), $entity->bundle(), TRUE);
      if (PullIntent::PULL_DISABLED === $config['import']) {
        continue;
      }
      if (!boolval($config['import_deletion_settings']['allow_local_deletion_of_import'])) {
        return FALSE;
      }
    }

    return TRUE;
  }

  /**
   * Apply the overrides from the global $config entity for this Flow, if any
   * are given.
   */
  public static function applyOverrides(string $id, Flow &$configuration) {
    global $config;
    $config_name = 'cms_content_sync.flow.' . $id;
    if (!isset($config[$config_name]) || empty($config[$config_name])) {
      return;
    }

    foreach ($config[$config_name] as $key => $new_value) {
      if (in_array($key, ['per_bundle_settings', 'simple_settings'])) {
        $configuration->{$key} = array_merge_recursive($configuration->{$key}, $new_value);

        continue;
      }
      // Ensure backwards compatibility.
      if ('sync_entities' === $key) {
        foreach ($configuration->per_bundle_settings as $entity_type_name => $bundles) {
          foreach ($bundles as $bundle_name => $bundle_config) {
            if (isset($new_value[$entity_type_name . '-' . $bundle_name])) {
              $configuration->per_bundle_settings[$entity_type_name][$bundle_name]['settings'] = array_merge_recursive($bundle_config['settings'], $new_value[$entity_type_name . '-' . $bundle_name]);
            }
            if (!empty($bundle_config['properties'])) {
              foreach ($bundle_config['properties'] as $field_name => $property_config) {
                if (isset($new_value[$entity_type_name . '-' . $bundle_name . '-' . $field_name])) {
                  $configuration->per_bundle_settings[$entity_type_name][$bundle_name]['properties'][$field_name] = array_merge_recursive($property_config, $new_value[$entity_type_name . '-' . $bundle_name . '-' . $field_name]);
                }
              }
            }
          }
        }

        continue;
      }
      $configuration->set($key, $new_value);
    }

    // Per-bundle config: calculate 'version' property if missing.
    $configuration->getController()->getEntityTypeConfig();
  }

  /**
   * Load all entities.
   *
   * Load all cms_content_sync_flow entities and add overrides from global $config.
   *
   * @param bool $skip_inactive
   *   Do not return inactive flows by default.
   * @param mixed $rebuild
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   *
   * @return Flow[]
   */
  public static function getAll($skip_inactive = TRUE, $rebuild = FALSE) {
    if ($skip_inactive && NULL !== self::$all && !$rebuild) {
      return self::$all;
    }

    /**
     * @var Flow[] $configurations
     */
    $configurations = \Drupal::entityTypeManager()
      ->getStorage('cms_content_sync_flow')
      ->loadMultiple();

    ksort($configurations);

    foreach ($configurations as $id => &$configuration) {
      self::applyOverrides($id, $configuration);
    }

    if ($skip_inactive) {
      $result = [];
      foreach ($configurations as $id => $flow) {
        if ($flow->get('status')) {
          $result[$id] = $flow;
        }
      }

      $configurations = $result;

      self::$all = $configurations;
    }

    return $configurations;
  }

  /**
   *
   */
  public static function resetFlowCache() {
    self::$all = NULL;
  }

  /**
   * Get the first synchronization that allows the pull of the provided entity
   * type.
   *
   * @param Pool $pool
   * @param string $entity_type_name
   * @param string $bundle_name
   * @param string $reason
   * @param string $action
   * @param bool $strict
   *
   * @return null|Flow
   */
  public static function getFlowForPoolAndEntityType($pool, $entity_type_name, $bundle_name, $reason, $action = SyncIntent::ACTION_CREATE, $strict = FALSE) {
    $flows = self::getAll();

    // If $reason is DEPENDENCY and there's a Flow pulling AUTOMATICALLY we take that. But only if there's no Flow
    // explicitly handling this entity AS_DEPENDENCY.
    $fallback = NULL;

    foreach ($flows as $flow) {
      if ($pool && !in_array($pool, $flow->getController()->getUsedPoolsForPulling($entity_type_name, $bundle_name))) {
        continue;
      }

      if (!$flow->getController()->canPullEntity($entity_type_name, $bundle_name, $reason, $action, TRUE)) {
        if (!$strict && $flow->getController()->canPullEntity($entity_type_name, $bundle_name, $reason, $action, FALSE)) {
          $fallback = $flow;
        }

        continue;
      }

      return $flow;
    }

    if (!empty($fallback)) {
      return $fallback;
    }

    return NULL;
  }

  /**
   * Unset the flow version warning.
   */
  public function resetVersionWarning() {
    $moduleHandler = \Drupal::service('module_handler');
    if ($moduleHandler->moduleExists('cms_content_sync_developer')) {
      $developer_config = \Drupal::service('config.factory')->getEditable('cms_content_sync.developer');
      $mismatching_versions = $developer_config->get('version_mismatch');
      if (!empty($mismatching_versions)) {
        unset($mismatching_versions[$this->id()]);
        $developer_config->set('version_mismatch', $mismatching_versions)->save();
      }
    }
  }

  /**
   * @param $entity_type_name
   * @param $bundle
   * @param null|IFlowController $existing
   * @param null $field
   *
   * @return array
   */
  public static function getDefaultFieldConfigForEntityType($entity_type_name, $bundle, $existing = NULL, $field = NULL) {
    if ($field) {
      $field_default_values = [
        'export' => NULL,
        'import' => NULL,
      ];

      $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_name);
      // @todo Should be gotten from the Entity Type Handler instead.
      $forbidden_fields = [
            // These are not relevant or misleading when synchronized.
        'revision_default',
        'revision_translation_affected',
        'content_translation_outdated',
            // Field collections.
        'host_type',
            // Files.
        'uri',
        'filemime',
        'filesize',
            // Media.
        'thumbnail',
            // Taxonomy.
        'parent',
            // These are standard fields defined by the Flow
            // Entity type that entities may not override (otherwise
            // these fields will collide with CMS Content Sync functionality)
        $entity_type->getKey('bundle'),
        $entity_type->getKey('id'),
        $entity_type->getKey('uuid'),
        $entity_type->getKey('label'),
        $entity_type->getKey('revision'),
      ];
      $pools = Pool::getAll();
      if (count($pools)) {
        $reserved = reset($pools)
          ->getClient()
          ->getReservedPropertyNames();
        $forbidden_fields = array_merge($forbidden_fields, $reserved);
      }

      if (FALSE !== in_array($field, $forbidden_fields)) {
        $field_default_values['handler'] = 'ignore';
        $field_default_values['export'] = PushIntent::PUSH_DISABLED;
        $field_default_values['import'] = PullIntent::PULL_DISABLED;

        return $field_default_values;
      }

      $field_handler_service = \Drupal::service('plugin.manager.cms_content_sync_field_handler');
      $field_definition = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type_name, $bundle)[$field];

      $field_handlers = $field_handler_service->getHandlerOptions($entity_type_name, $bundle, $field, $field_definition, TRUE);
      if (empty($field_handlers)) {
        throw new \Exception('Unsupported field type ' . $field_definition->getType() . ' for field ' . $entity_type_name . '.' . $bundle . '.' . $field);
      }
      reset($field_handlers);
      $handler_id = empty($field_default_values['handler']) ? key($field_handlers) : $field_default_values['handler'];

      $field_default_values['handler'] = $handler_id;
      $field_default_values['export'] = PushIntent::PUSH_AUTOMATICALLY;
      $field_default_values['import'] = PullIntent::PULL_AUTOMATICALLY;

      $handler = $field_handler_service->createInstance($handler_id, [
        'entity_type_name' => $entity_type_name,
        'bundle_name' => $bundle,
        'field_name' => $field,
        'field_definition' => $field_definition,
        'settings' => $field_default_values,
        'sync' => NULL,
      ]);

      $advanced_settings = $handler->getHandlerSettings($field_default_values);
      if (count($advanced_settings)) {
        foreach ($advanced_settings as $name => $element) {
          $field_default_values['handler_settings'][$name] = $element['#default_value'];
        }
      }

      return $field_default_values;
    }

    $entityTypeManager = \Drupal::service('entity_type.manager');
    $type = $entityTypeManager->getDefinition($entity_type_name, FALSE);

    $field_definition = $type ? \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type_name, $bundle) : FALSE;

    $result = [];

    if ($field_definition) {
      foreach ($field_definition as $key => $field) {
        $field_config = $existing ? $existing->getPropertyConfig($entity_type_name, $bundle, $key) : NULL;
        $result[$key] = $field_config ? $field_config : self::getDefaultFieldConfigForEntityType($entity_type_name, $bundle, NULL, $key);
      }
    }

    return $result;
  }

}

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

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