You are here

FlowControllerSimple.php in CMS Content Sync 2.1.x

File

src/Controller/FlowControllerSimple.php
View source
<?php

namespace Drupal\cms_content_sync\Controller;

use Drupal\cms_content_sync\IFlowController;
use Drupal\cms_content_sync\Entity\Flow;
use Drupal\cms_content_sync\Entity\Pool;
use Drupal\cms_content_sync\Plugin\Type\EntityHandlerPluginManager;
use Drupal\cms_content_sync\PushIntent;
use Drupal\cms_content_sync\PullIntent;
use Drupal\cms_content_sync\Helper\SimpleFlowSetupHelper;
class FlowControllerSimple extends FlowControllerBase implements IFlowController {
  const ASSIGN_ALL_POOLS = 'force';
  const ASSIGN_POOLS_MANUALLY = 'allow';
  const MODE_AUTOMATICALLY = 'automatically';
  const MODE_MANUALLY = 'manually';
  const MODE_DEFAULT = 'default';
  const MODE_DEPENDENT = 'dependency';
  const MODE_IGNORE = 'disabled';

  /**
   * A virtual Flow is not saved to the database and only used temporarily, e.g.
   * to serialize an entity at the SyncCoreEntityItemResource that is used by
   * the embed service, e.g. to get taxonomy terms for the flow form to use the
   * "subscribe only to" option.
   *
   * @var boolean
   */
  protected $virtual = false;
  public function isVirtual($set = null) {
    if ($set !== null) {
      $this->virtual = $set;
    }
    return $this->virtual;
  }

  /**
   * Create a flow configuration programmatically.
   *
   * @param $flow_name
   * @param string $flow_id
   * @param bool   $status
   * @param array  $dependencies
   * @param $configurations
   * @param bool $force_update
   *
   * @return SimpleFlowSetupHelper
   */
  public static function createFlow(string $type, string $flow_name, ?string $flow_id = null, $status = true, ?array $pools = null, array $dependencies = [], bool $force_update = false) {
    $flows = Flow::getAll(true);

    // If no flow_id is given, create one.
    if (empty($flow_id)) {
      $flow_id = strtolower($flow_name);
      $flow_id = preg_replace('@[^a-z0-9_]+@', '_', $flow_id);
    }
    if (!$force_update && array_key_exists($flow_id, $flows)) {
      \Drupal::messenger()
        ->addMessage('A flow with the machine name ' . $flow_id . ' already exists. Creation has been skipped.', 'warning');
      return $flow_id;
    }
    $uuid_service = \Drupal::service('uuid');
    $language_manager = \Drupal::service('language_manager');
    $default_language = $language_manager
      ->getDefaultLanguage();
    $config = [
      'dependencies' => $dependencies,
    ];
    $flow_config = \Drupal::service('config.factory')
      ->getEditable('cms_content_sync.flow.' . $flow_id);

    // Setup base configurations.
    $flow_config
      ->set('uuid', $uuid_service
      ->generate())
      ->set('langcode', $default_language
      ->getId())
      ->set('status', $status)
      ->set('id', $flow_id)
      ->set('name', $flow_name)
      ->set('type', $type)
      ->set('variant', Flow::VARIANT_SIMPLE)
      ->set('config', $config)
      ->set('simple_settings', [
      'poolAssignment' => self::ASSIGN_ALL_POOLS,
      'mode' => self::MODE_AUTOMATICALLY,
      'deletions' => true,
      'updateBehavior' => PullIntent::PULL_UPDATE_FORCE_AND_FORBID_EDITING,
      'ignoreUnpublishedChanges' => true,
      'allowExplicitUnpublishing' => true,
      'pushMenuItems' => true,
      'pushPreviews' => true,
      'mergeLocalChanges' => true,
      'resolveUserReferences' => 'name',
      'poolSelectionWidget' => 'checkboxes',
      'entityTypeSettings' => [],
      'pools' => $pools,
    ]);
    $flow_config
      ->save();
    $flows = Flow::getAll(true, true);
    return new SimpleFlowSetupHelper($flows[$flow_id]);
  }

  /**
   * @inheritDoc
   */
  public function getEntityTypeConfig($find_entity_type = null, $find_entity_bundle = null, $used_only = false, $include_new_versions = false) {
    $result = [];

    // TODO: Cache result per Flow if no $find_* parameter is given and return the cached result.
    $settings = $this->flow->simple_settings;
    $is_push = $this->flow->type === Flow::TYPE_PUSH;
    $merge_local_changes = $settings['mergeLocalChanges'];
    $selected_pools = [];
    $selected_pools_manual_assignment = [];
    $auto_assign_pools = $settings['poolAssignment'] === self::ASSIGN_ALL_POOLS;
    $used_pools = $this
      ->getUsedPools();
    $pools = Pool::getAll();
    foreach ($pools as $id => $pool) {
      if (isset($used_pools[$id])) {
        $selected_pools_manual_assignment[$id] = Pool::POOL_USAGE_ALLOW;
        if ($auto_assign_pools) {
          $selected_pools[$id] = Pool::POOL_USAGE_FORCE;
        }
        else {
          $selected_pools[$id] = Pool::POOL_USAGE_ALLOW;
        }
      }
      else {
        $selected_pools[$id] = Pool::POOL_USAGE_FORBID;
      }
    }
    $entity_plugin_manager = \Drupal::service('plugin.manager.cms_content_sync_entity_handler');
    $field_plugin_manager = \Drupal::service('plugin.manager.cms_content_sync_field_handler');
    $entity_field_manager = \Drupal::service('entity_field.manager');
    $field_map = $entity_field_manager
      ->getFieldMap();
    $entity_types = \Drupal::service('entity_type.bundle.info')
      ->getAllBundleInfo();
    $entity_type_versions = $this
      ->getExportedEntityTypeVersions();
    $assign_pools_manually_to_dependencies = empty($settings['assignPoolsManuallyToDependencies']) ? [] : $settings['assignPoolsManuallyToDependencies'];
    foreach ($entity_types as $entity_type_name => $bundles) {
      if (empty($settings['entityTypeSettings'][$entity_type_name])) {
        continue;
      }
      if ($find_entity_type && $entity_type_name !== $find_entity_type) {
        continue;
      }
      foreach ($bundles as $bundle_name => $entity_bundle) {
        if ($find_entity_bundle && $bundle_name !== $find_entity_bundle) {
          continue;
        }
        $info = EntityHandlerPluginManager::getEntityTypeInfo($entity_type_name, $bundle_name);
        if (!empty($info['no_entity_type_handler']) || !empty($info['required_field_not_supported'])) {
          continue;
        }
        if (!empty($settings['entityTypeSettings'][$entity_type_name]['allBundles'])) {
          $bundle_settings = $settings['entityTypeSettings'][$entity_type_name]['allBundles'];
        }
        elseif (!empty($settings['entityTypeSettings'][$entity_type_name]['perBundle'][$bundle_name])) {
          $bundle_settings = $settings['entityTypeSettings'][$entity_type_name]['perBundle'][$bundle_name];
        }
        else {
          continue;
        }
        if ($bundle_settings['mode'] === self::MODE_IGNORE) {
          continue;
        }
        elseif ($bundle_settings['mode'] === self::MODE_DEFAULT) {
          $mode = $settings['mode'];
        }
        else {
          $mode = $bundle_settings['mode'];
        }
        $entity_handlers = $entity_plugin_manager
          ->getHandlerOptions($entity_type_name, $bundle_name, true);
        if (!count($entity_handlers)) {
          continue;
        }
        $entity_handler_names = array_keys($entity_handlers);
        $handler_id = reset($entity_handler_names);
        $pool_assignment = in_array($entity_type_name, $assign_pools_manually_to_dependencies) ? $selected_pools_manual_assignment : $selected_pools;
        $result[$entity_type_name][$bundle_name] = [
          'version' => $include_new_versions || $this->virtual ? Flow::getEntityTypeVersion($entity_type_name, $bundle_name) : (empty($entity_type_versions[$entity_type_name][$bundle_name]) ? null : $entity_type_versions[$entity_type_name][$bundle_name]),
          'handler' => $handler_id,
          'handler_settings' => [
            'ignore_unpublished' => $settings['ignoreUnpublishedChanges'] ? 1 : 0,
            'allow_explicit_unpublishing' => $settings['allowExplicitUnpublishing'] ? 1 : 0,
            'export_menu_items' => $settings['pushMenuItems'] ? 1 : 0,
            'export_crop' => 1,
          ],
          'export' => $is_push ? $mode : self::MODE_IGNORE,
          'export_pools' => $is_push ? $pool_assignment : [],
          'export_deletion_settings' => [
            'export_deletion' => $settings['deletions'] ? 1 : 0,
          ],
          'pool_export_widget_type' => $settings['poolSelectionWidget'],
          'preview' => $settings['pushPreviews'] && !empty($settings['entityTypeSettings'][$entity_type_name]['perBundle'][$bundle_name]['previewViewMode']) ? $settings['entityTypeSettings'][$entity_type_name]['perBundle'][$bundle_name]['previewViewMode'] : Flow::PREVIEW_DISABLED,
          'import' => $is_push ? self::MODE_IGNORE : $mode,
          'import_pools' => $is_push ? [] : $selected_pools,
          'import_deletion_settings' => [
            'import_deletion' => $settings['deletions'] ? 1 : 0,
            'allow_local_deletion_of_import' => $settings['allowLocalDeletion'] ? 1 : 0,
          ],
          'import_updates' => $settings['updateBehavior'],
          'properties' => [],
        ];
        $handler = $entity_plugin_manager
          ->createInstance($handler_id, [
          'entity_type_name' => $entity_type_name,
          'bundle_name' => $bundle_name,
          'settings' => $result[$entity_type_name][$bundle_name]['handler_settings'],
          'sync' => null,
        ]);
        if (isset($field_map[$entity_type_name])) {
          $forbidden_fields = $handler
            ->getForbiddenFields();
          $pools = Pool::getAll();
          if (count($pools)) {
            $reserved = reset($pools)
              ->getClient()
              ->getReservedPropertyNames();
            $forbidden_fields = array_merge($forbidden_fields, $reserved);
          }
          $fields = $entity_field_manager
            ->getFieldDefinitions($entity_type_name, $bundle_name);
          foreach ($fields as $key => $field) {
            $field_handlers = $field_plugin_manager
              ->getHandlerOptions($entity_type_name, $bundle_name, $key, $field, true);
            $ignore = in_array($key, $forbidden_fields) || empty($field_handlers);
            $handler_id = $ignore ? 'ignore' : key($field_handlers);
            $referenced_type = null;
            if (in_array($field
              ->getType(), [
              'entity_reference',
              'entity_reference_revisions',
              'webform',
            ])) {
              $referenced_type = $field
                ->getSetting('target_type');
            }
            elseif (in_array($field
              ->getType(), [
              'image',
              'file',
              'file_uri',
            ])) {
              $referenced_type = 'file';
            }
            elseif (in_array($field
              ->getType(), [
              'bricks',
            ])) {
              $referenced_type = 'brick';
            }
            elseif (in_array($field
              ->getType(), [
              'field_collection',
            ])) {
              $referenced_type = 'field_collection_item';
            }
            $push_referenced_entities = $this->virtual ? false : !$referenced_type || !in_array($referenced_type, $assign_pools_manually_to_dependencies);
            $subscribe_only_to = null;
            if (!empty($settings['entityTypeSettings'][$entity_type_name]['perBundle'][$bundle_name]['filterByReference'])) {
              foreach ($settings['entityTypeSettings'][$entity_type_name]['perBundle'][$bundle_name]['filterByReference'] as $filter) {
                if ($filter['fieldMachineName'] === $key) {
                  $subscribe_only_to = [];
                  foreach ($filter['values'] as $value) {
                    $subscribe_only_to[] = [
                      'type' => $value['namespaceMachineName'],
                      'bundle' => $value['machineName'],
                      'uuid' => $value['remoteUuid'],
                    ];
                  }
                }
              }
            }
            $field_settings = [
              'handler' => $handler_id,
              'export' => null,
              'import' => null,
              'preview' => null,
              'entity_type' => $entity_type_name,
              'entity_bundle' => $bundle_name,
              'handler_settings' => [
                'identification' => $settings['resolveUserReferences'],
                'export_referenced_custom_blocks' => 1,
                'export_referenced_entities' => $push_referenced_entities ? 1 : 0,
                'merge_local_changes' => $merge_local_changes ? 1 : 0,
                'subscribe_only_to' => $subscribe_only_to,
              ],
            ];
            if (!$ignore) {

              /**
               * @var \Drupal\cms_content_sync\Plugin\FieldHandlerInterface $handler
               */
              $handler = $field_plugin_manager
                ->createInstance($handler_id, [
                'entity_type_name' => $entity_type_name,
                'bundle_name' => $bundle_name,
                'field_name' => $key,
                'field_definition' => $field,
                'settings' => $field_settings,
                'sync' => $this->flow,
              ]);
              $allowed_push_options = $handler
                ->getAllowedPushOptions();
              if ($is_push && in_array(PushIntent::PUSH_AUTOMATICALLY, $allowed_push_options)) {
                $field_settings['export'] = PushIntent::PUSH_AUTOMATICALLY;
              }
              $allowed_pull_options = $handler
                ->getAllowedPullOptions();
              if (!$is_push && in_array(PullIntent::PULL_AUTOMATICALLY, $allowed_pull_options)) {
                $field_settings['import'] = PullIntent::PULL_AUTOMATICALLY;
              }
            }
            $result[$entity_type_name][$bundle_name]['properties'][$key] = $field_settings;
          }
        }
        if ($find_entity_type && $find_entity_bundle) {
          return $result[$entity_type_name][$bundle_name];
        }
      }
    }
    return $result;
  }
  protected function getExportedEntityTypeVersions() {
    $status = \Drupal::state()
      ->get('cms_content_sync.flow_status_' . $this->flow->id);
    if (empty($status)) {
      return [];
    }
    return $status['entity_type_versions'];
  }

  /**
   * @inheritDoc
   */
  public function needsEntityTypeUpdate() {
    $old_versions = $this
      ->getEntityTypeConfig(null, null, false, false);
    $new_versions = $this
      ->getEntityTypeConfig(null, null, false, true);
    foreach ($old_versions as $entity_type_name => $bundles) {
      foreach ($bundles as $bundle_name => $config) {
        if ($config['version'] !== $new_versions[$entity_type_name][$bundle_name]['version']) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * @inheritDoc
   */
  public function getPropertyConfig(string $entity_type, string $entity_bundle, string $property) {
    $bundle_settings = $this
      ->getEntityTypeConfig($entity_type, $entity_bundle);
    if (empty($bundle_settings['properties'][$property])) {
      return NULL;
    }
    return $bundle_settings['properties'][$property];
  }

  /**
   * Get the values for the embed Flow form.
   *
   * @return array
   */
  public function getFormValues() {
    return [
      'values' => $this->flow->simple_settings + [
        'machineName' => $this->flow->id,
        'name' => $this->flow->name,
        'type' => $this->flow->type,
      ],
    ] + FlowControllerSimple::getFormConfig($this->flow);
  }

  /**
   * Store the values from the embed Flow form.
   *
   * @return void
   */
  public function setFormValues(array $values) {
    $this->flow->name = $values['name'];
    unset($values['type']);
    unset($values['machineName']);
    unset($values['name']);
    $this->flow->simple_settings = $values;
  }
  public const FLOW_FORM_VERSION = 1;
  public static function getFormValuesForNewFlow(?Flow $flow) {
    $values = $flow ? [
      'machineName' => $flow->id,
      'name' => $flow->name,
    ] : [];
    return [
      'values' => $values,
    ] + FlowControllerSimple::getFormConfig($flow);
  }
  protected static function getFormConfig(?Flow $flow) {
    $all_pools = Pool::getAll();
    $pools = [];
    foreach ($all_pools as $pool) {
      $pools[] = [
        'name' => $pool
          ->label(),
        'machineName' => $pool
          ->id(),
      ];
    }
    $all_flows = Flow::getAll(TRUE);
    $reserved_flows = [];
    $pushed_bundles = [];
    $pulled_bundles = [];
    $pushed_pools = [];
    $pulled_pools = [];
    foreach ($all_flows as $flow_item) {
      if ($flow && $flow->id === $flow_item->id) {
        continue;
      }
      $reserved_flows[] = $flow_item->id;
      foreach ($flow_item
        ->getController()
        ->getEntityTypeConfig() as $entity_type_name => $bundles) {
        foreach ($bundles as $bundle_name => $settings) {
          $bundle = [
            'namespaceMachineName' => $entity_type_name,
            'machineName' => $bundle_name,
          ];
          if ($settings['export'] === PushIntent::PUSH_AUTOMATICALLY || $settings['export'] === PushIntent::PUSH_MANUALLY) {
            if (!in_array($bundle, $pushed_bundles)) {
              $pushed_bundles[] = $bundle;
            }
            foreach ($settings['export_pools'] as $pool_id => $pool_mode) {
              if ($pool_mode !== Pool::POOL_USAGE_FORBID && !in_array($pool_id, $pushed_pools)) {
                $pushed_pools[] = $pool_id;
              }
            }
          }
          if ($settings['import'] === PullIntent::PULL_AUTOMATICALLY || $settings['import'] === PullIntent::PULL_MANUALLY) {
            if (!in_array($bundle, $pulled_bundles)) {
              $pulled_bundles[] = $bundle;
            }
            foreach ($settings['import_pools'] as $pool_id => $pool_mode) {
              if ($pool_mode !== Pool::POOL_USAGE_FORBID && !in_array($pool_id, $pulled_pools)) {
                $pulled_pools[] = $pool_id;
              }
            }
          }
        }
      }
    }
    $bundles = [];
    $entity_types = \Drupal::service('entity_type.bundle.info')
      ->getAllBundleInfo();
    $entity_field_manager = \Drupal::service('entity_field.manager');
    $display_modes = \Drupal::service('entity_type.manager')
      ->getStorage('entity_view_display')
      ->loadMultiple();
    foreach ($entity_types as $entity_type_machine_name => $type_bundles) {
      foreach ($type_bundles as $bundle_machine_name => $bundle) {

        // Always ignore webform submissions as they will not even provide a
        // correct machine name (using numbers).
        if ($entity_type_machine_name === 'webform_submission') {
          continue;
        }
        $info = EntityHandlerPluginManager::getEntityTypeInfo($entity_type_machine_name, $bundle_machine_name);
        $display_modes_ids = array_keys($display_modes);
        $available_preview_modes = [];
        foreach ($display_modes_ids as $id) {
          $length = strlen($entity_type_machine_name) + strlen($bundle_machine_name) + 2;
          if (substr($id, 0, $length) != $entity_type_machine_name . '.' . $bundle_machine_name . '.') {
            continue;
          }
          $id = substr($id, $length);
          $label = $id;
          $available_preview_modes[$id] = $label;
        }
        $missing_fields = array_merge(isset($info['unsupported_required_fields']) ? $info['unsupported_required_fields'] : [], isset($info['unsupported_optional_fields']) ? $info['unsupported_optional_fields'] : []);
        $reference_fields = [];
        if (EntityHandlerPluginManager::isEntityTypeFieldable($entity_type_machine_name)) {

          /**
           * @var \Drupal\Core\Field\FieldDefinitionInterface[] $fields
           */
          $fields = $entity_field_manager
            ->getFieldDefinitions($entity_type_machine_name, $bundle_machine_name);
          foreach ($fields as $key => $field) {
            $referenced_type = null;
            $referenced_bundles = null;
            if (in_array($field
              ->getType(), [
              'entity_reference',
              'entity_reference_revisions',
              'webform',
            ])) {
              $referenced_type = $field
                ->getSetting('target_type');
            }
            elseif (in_array($field
              ->getType(), [
              'image',
              'file',
              'file_uri',
            ])) {
              $referenced_type = 'file';
            }
            elseif (in_array($field
              ->getType(), [
              'bricks',
            ])) {
              $referenced_type = 'brick';
            }
            elseif (in_array($field
              ->getType(), [
              'field_collection',
            ])) {
              $referenced_type = 'field_collection_item';
            }
            if (!$referenced_type) {
              continue;
            }
            $item = [
              'machineName' => $key,
              'name' => $field
                ->getLabel(),
              'targetNamespaceMachineName' => $referenced_type,
            ];
            $field_settings = $field
              ->getSettings();
            if ((!empty($field_settings['handler_settings']) || 'brick' === $referenced_type) && !empty($field_settings['handler_settings']['target_bundles'])) {
              $referenced_bundles = [];
              foreach ($field_settings['handler_settings']['target_bundles'] as $target_bundle) {
                $referenced_bundles[] = $target_bundle;
              }
              $item['targetMachineNames'] = $referenced_bundles;
            }
            $reference_fields[] = $item;
          }
        }
        $bundles[] = [
          'namespaceMachineName' => $entity_type_machine_name,
          'machineName' => $bundle_machine_name,
          'name' => $bundle['label'],
          'supported' => $info['is_supported'],
          'isConfiguration' => EntityHandlerPluginManager::isEntityTypeConfiguration($entity_type_machine_name),
          'viewModes' => $available_preview_modes,
          'unsupportedFields' => $missing_fields,
          'referenceFields' => $reference_fields,
        ];
      }
    }
    return [
      'bundles' => $bundles,
      'pools' => $pools,
      'reservedFlowMachineNames' => $reserved_flows,
      'pushedEntityTypes' => $pushed_bundles,
      'pulledEntityTypes' => $pulled_bundles,
      'pushedPools' => $pushed_pools,
      'pulledPools' => $pulled_pools,
    ];
  }

  /**
   * @return null|string
   */
  public function getType() {
    return Flow::TYPE_PUSH;
    return Flow::TYPE_PULL;
  }
  public function updateEntityTypeVersions() {
    $status = \Drupal::state()
      ->get('cms_content_sync.flow_status_' . $this->flow->id);
    if (empty($status)) {
      $status = [];
    }
    $types = $this
      ->getEntityTypeConfig(null, null, false, true);
    $versions = [];
    foreach ($types as $entity_type_name => $bundles) {
      foreach ($bundles as $bundle_name => $config) {
        $versions[$entity_type_name][$bundle_name] = $config['version'];
      }
    }
    $status['entity_type_versions'] = $versions;
    \Drupal::state()
      ->set('cms_content_sync.flow_status_' . $this->flow->id, $status);
  }
  public function usesPool($pool) {
    $settings = $this->flow->simple_settings;
    if (!isset($settings['pools']) || !is_array($settings['pools'])) {
      return true;
    }
    return in_array($pool->id, $settings['pools']);
  }

}

Classes