visualn-8.x-1.x-dev/src/Plugin/VisualN/Builder/DefaultBuilder.php

src/Plugin/VisualN/Builder/DefaultBuilder.php
<?php

namespace Drupal\visualn\Plugin\VisualN\Builder;

use Drupal\visualn\Core\BuilderBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\visualn\Manager\DrawerManager;
use Drupal\visualn\Manager\AdapterManager;
use Drupal\visualn\Manager\MapperManager;
use Drupal\visualn\Entity\VisualNStyle;
use Drupal\visualn\Core\DrawerInterface;
use Drupal\visualn\ResourceInterface;

/**
 * Provides a 'Default Builder' VisualN builder.
 *
 * @todo: add description here
 *
 * Builder makes chain from Chain plugins (see @link chain_plugins Chain plugins @endlink topic)
 *
 * Default Builder supposes that Resource has at least one of keys filled-up: base_type or type.
 * The 'type' key is a required one, base_type may be empty.
 *
 *
 * - base_type
 * Can be used by developers to group multiple resource types into groups.
 * This allows to create adapters that handle multiple resouce types by checking 'base_type' key.
 *
 * Base types may serve different purposes with no strict limits or considerations on this subject.
 *
 * - type
 * The type uniquely identifies a resource type with its properties and structure.
 * For each resource type a Resource plugin should be implemented.
 *
 * @ingroup builder_plugins
 *
 * @VisualNBuilder(
 *  id = "visualn_default",
 *  label = @Translation("Default Builder"),
 * )
 */
class DefaultBuilder extends BuilderBase implements ContainerFactoryPluginInterface {

  /**
   * The image style entity storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $visualNStyleStorage;

  /**
   * The visualn drawer manager service.
   *
   * @var \Drupal\visualn\Manager\DrawerManager
   */
  protected $visualNDrawerManager;

  /**
   * The visualn adapter manager service.
   *
   * @var \Drupal\visualn\Manager\AdapterManager
   */
  protected $VisualNAdapterManager;

  /**
   * The visualn mapper manager service.
   *
   * @var \Drupal\visualn\Manager\MapperManager
   */
  protected $VisualNMapperManager;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('entity_type.manager')->getStorage('visualn_style'),
      $container->get('plugin.manager.visualn.drawer'),
      $container->get('plugin.manager.visualn.adapter'),
      $container->get('plugin.manager.visualn.mapper')
    );
  }

  /**
   * Constructs a Plugin object.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $visualn_style_storage, DrawerManager $visualn_drawer_manager, AdapterManager $visualn_adapter_manager, MapperManager $visualn_mapper_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    //$this->definition = $plugin_definition + $configuration;
    $this->visualNStyleStorage = $visualn_style_storage;
    $this->visualNDrawerManager = $visualn_drawer_manager;
    $this->visualNAdapterManager = $visualn_adapter_manager;
    $this->visualNMapperManager = $visualn_mapper_manager;
  }


  /**
   * @inheritdoc
   */
  public function prepareBuild(array &$build, $vuid, ResourceInterface $resource) {
    //$drawing_options = $this->getConfiguration();

    // @todo: add this to the VisualNPluginBase method
    $options = $this->getConfiguration();
    $output_type = $resource->getResourceType();

    // required options: style_id, html_selector
    // add optional options
    $options += [
      'drawer_config' => [],  // optional (drawer default config is considered)
    ];



/*
    // @todo: do we really need style_id here? maybe just pass drawer_plugin_id or both
    //  builder needs to know nothing about the visualn style
    $visualn_style_id = $options['style_id'];
    $visualn_style = $this->visualNStyleStorage->load($visualn_style_id);
    if (empty($visualn_style)) {
      return;
    }

    // @todo: do we have chain_plugins_configs here? i.e. in case chain is built for the first time
    //    is chain stored anywhere (in config settings)?
    $drawer = $visualn_style->getDrawerPlugin()->setConfiguration($options['drawer_config']);
*/


    if (!empty($options['visualn_style_id'])) {
      $visualn_style_id = $options['visualn_style_id'];
      $visualn_style = $this->visualNStyleStorage->load($visualn_style_id);
      if (empty($visualn_style)) {
        return;
      }
      $drawer = $visualn_style->getDrawerPlugin()->setConfiguration($options['drawer_config']);
    }
    elseif (!empty($options['base_drawer_id'])) {
      $drawer_plugin_id = $options['base_drawer_id'];
      $drawer = $this->visualNDrawerManager->createInstance($drawer_plugin_id, $options['drawer_config']);
      if (empty($drawer)) {
        return;
      }
    }
    else {
      return;
    }

    $drawer->setWindowParameters($this->getWindowParameters());

    //$chain = $this->composePluginsChain($drawer, $input_type, $input_data);
    // @todo: $additional_options
    $chain = $this->composePluginsChain($drawer, $output_type, ['drawer_fields' => $options['drawer_fields']]); // $drawer, $input_type, $input_options
    //$chain = $this->composePluginsChain($drawer, $resource, $drawing_options);
    // @todo: review this interface, additional data may be used e.g. to alter chain building (vuid, drawing_options etc.)
    //$chain = $this->composePluginsChain($drawer, $resource);

    // there could be now drawer after composing chain
    if (empty($chain['drawer'])) {
      return;
    }

    // generally this should be the same drawer as passed into composerPluginsChain()
    //$drawer = $chain['drawer'][0];

    // The $build['#visualn'] array collects data from each plugin (e.g. for data_keys_structure) since
    // it can be required by mappers (e.g. basic_tree_mapper) or adapters.
    // The info is attached to the $build array (instead of using a certain variable)
    // in case it could be required in some non-standard workflow or even anywhere outside VisualN process.
/*
    $build['#visualn'] = [];
*/

    $js_use_found = FALSE;

    // Serverside drawers need data to be already there when called so drawers are called last ones
    $plugin_types = ['adapter', 'mapper', 'drawer'];

    foreach ($plugin_types as $plugin_type) {
      // generally there is one plugin of each kind
      foreach ($chain[$plugin_type] as $k => $chain_plugin) {
        // @todo: or just check if js interface is implemented
        if (!$js_use_found && method_exists($chain_plugin, 'jsId') && is_callable([$chain_plugin, 'jsId'])) {
          // initialize drawings settings storage (to make it explicit here)
          // typically plugins don't need any settings if do not use js handlers (or not?)
          $build['#attached']['drupalSettings']['visualn']['drawings'][$vuid] = [];
          $js_use_found = TRUE;
          // @todo: maybe also attach visualn core js here
        }
        if ($plugin_type == 'adapter' && $k == 0) {
          // @todo: maybe also set data_keys since adapter may need only data keys info
          // @todo: actually drawer_fields, data_keys_structure (and possibly data_keys) should be
          //    set for every type of plugin (adapter, mapper, drawer) since any of them should be
          //    able to do remapping (if it needs to) so these properties should be set
          //    in VisualNPluginBase::defaultConfiguration() and inherited in type-specific classes
          $chain_plugin_config = [
            'drawer_fields' => $options['drawer_fields'] ?: [],
          ] + $chain_plugin->getConfiguration();
          $chain_plugin->setConfiguration($chain_plugin_config);
        }
        elseif ($plugin_type == 'mapper' && $k == 0) {
          // @todo: check if plugin configuration (or default configuration) has data_keys_structure
          //    and maybe drawer_fields keys and only set if parameters are really needed.
          //    Actually these options should be set by linker plugin based on resource and input/output types
          //    or even based on plugin type itself (so that linker could be chosen not only based on
          //    input/output types but also on specific plugins in the chain.
          $chain_plugin_config = [
            'data_keys_structure' => $drawer->dataKeysStructure(),
            'drawer_fields' => $options['drawer_fields'] ?: [],
          ] + $chain_plugin->getConfiguration();
          $chain_plugin->setConfiguration($chain_plugin_config);

          // @todo: set 'data_keys_structure' as a property for mappers and adapters default config
          //    so that it would be an assumption of the workflow
        }
        // @todo: pass Resource into arguments, what to do with the resource, should be uniquely defined by the config
        $resource = $chain_plugin->prepareBuild($build, $vuid, $resource);
      }
    }

/*
    $chain = array_merge($chain['drawer'], $chain['adapter'], $chain['mapper']);
    foreach ($chain as $chain_plugin) {

      // @todo: Implement Linker plugin. Currently the login is implemented in the cycle above.
      //    Linkers would provide data required by specific plugin and would allow users to override default workflow
      //    for specific plugins. Also they make data used by each plugin transparent.
      //    Linkers would need a way to get plugin type, so a corresponding method should be implemented in the
      //    plugins base class.
      //    An example could be "$input_options = Linker($chain_plugin, $build['#visualn'], $options);"
      //

      $resource = $chain_plugin->prepareBuild($build, $vuid, $resource);
    }
*/

    // @todo: or check if implements js interface instead
    // attach js scripts only if there is at least one drawer (or other chain plugin) implementing jsId() method
    $uses_js = $js_use_found;
    if ($uses_js) {
      // this should be generally set though technically plugins may attach libraries to sub keys
      if (!isset($build['#attached']['library'])) {
        $build['#attached']['library'] = [];
      }


      // @todo: attach these libraries and settigns to the beginning of the 'library' array


      // @todo: visualn-core.js must be attached before other visualn js scripts (drawers, mappers, adapters, builders)
      //   to init visualnData variable used by plugins js handlers to register themselves
      //   though some better way should be implemented to avoid it
      // @todo: move into base class or even into dependencies for builder js script and attach it there instead of end of method function
      // @todo: maybe pass VisualN variable to drawer scpripts as it is done for Drupal global variable

      //$build['#attached']['library'][] = 'visualn/visualn-core';
      //array_unshift($build['#attached']['library'], 'visualn/visualn-core');
      $build['#attached']['library'] = array_merge(['visualn/visualn-core'], $build['#attached']['library']);



      //$build['#attached']['drupalSettings']['visualn']['drawings'][$vuid] = [];
      // @todo: check the way it is used, add a comment
      $builder_id = 'visualnDefaultBuilder';
      $build['#attached']['drupalSettings']['visualn']['handlerItems']['builders'][$builder_id][$vuid] = $vuid;








      $build['#attached']['drupalSettings']['visualn']['drawings'][$vuid]['html_selector'] = $options['html_selector'];
      // Attach visualn builder js script.
      $build['#attached']['library'][] = 'visualn/builder-default';

      // @todo: set visualn/core library as a dependency (?)
    }

    return $resource;
  }

  /**
   * @inheritdoc
   *
   * @todo: move to interface and maybe rename
   */
  protected function composePluginsChain(DrawerInterface $drawer, $input_type, array $input_options, $input_base_type = '') {
    // The arrays are used to allow multiple plugins of each type in the chain
    // though generally this isn't used and wasn't tested. In most cases
    // this seems to have no sense (at least for drawer plugins).
    $chain = ['drawer' => [$drawer], 'mapper' => [], 'adapter' => []];

    $drawer_input = $drawer->getPluginDefinition()['input'];

    $mapper_required = count(array_filter($input_options['drawer_fields']));
    // return chain if drawer doesn't need adapters or mappers
    if ($drawer_input == $input_type && !$mapper_required) {
      return $chain;
    }



    // get all adapter candidates
    $matched_adapter_groups = [];
    $adapterDefinitions = $this->visualNAdapterManager->getDefinitions();
    foreach ($adapterDefinitions as $adapter_id => $definition) {
      if ($definition['input'] == $input_type) {
        $matched_adapter_groups[] = [
          'adapters' => [$adapter_id],
          'output' => $definition['output'],
          // actually input type is not used anywhere below but keep it for consistency
          // with suggestested subchains structure
          'input' => $definition['input'],
        ];
      }
    }


    // get suggested adapter subchains
    $subchain_suggestions = [];
    // Call modules that implement the hook, and let them add items.
    // @todo: add hook description into api documentation
    \Drupal::moduleHandler()->alter('visualn_adapter_subchains', $subchain_suggestions);
    // @todo: maybe pass additional data to the hook or cached the info (maybe statically)
    //    if moduleHandler doesn't do it itself

    $adapter_subchain_candidates = [];
    foreach ($subchain_suggestions as $subchain_suggestion) {
      if ($subchain_suggestion['input'] == $input_type) {
        $adapter_subchain_candidates[] = $subchain_suggestion;
      }
    }
    // @todo: then compare as it was done for generic adapters
    //    if a subchain fits into conditions, use it instead of single adapter
    //    or even attach subchains to the adapters list and check the altogether

    $matched_adapter_groups = array_merge($matched_adapter_groups, $adapter_subchain_candidates);


    // no need to get mappers if drawer keys mapping configuration is empty
    //    and chain can be built using only adapters

    if (!$mapper_required) {
      // try to build a no-mapper chain
      foreach ($matched_adapter_groups as $adapters_group) {
        if ($adapters_group['output'] == $drawer_input) {
          foreach ($adapters_group['adapters'] as $adapter_id) {
            $chain['adapter'][] = $this->visualNAdapterManager->createInstance($adapter_id, []);
          }

          // return chain if matching adapter found
          return $chain;
        }
      }
      // Sometimes, when keys mapping itself is not needed, a mapper may still be used
      // e.g.  when it provides some features which is usually done by adapters
      // i.e. changes resource type
    }


    // get the list of adapter groups output types, intup type is already all the same

    // @todo: There may be multiple adapters (groups) with the same input and output types
    //   so priorities should be set. Only the first one is used (see array_search below).
    $adapters_subchain_lst = array_column($matched_adapter_groups, 'output');





    // get all mapper candidates
    $matched_mappers = [];
    $mapperDefinitions = $this->visualNMapperManager->getDefinitions();
    foreach ($mapperDefinitions as $mapper_id => $definition) {
      if ($definition['output'] == $drawer_input) {
        $matched_mappers[$mapper_id] = $definition['input'];
      }
    }

    // choose matching adapters and mappers for the chain
    $join_types = array_intersect($adapters_subchain_lst, $matched_mappers);


    if (!empty($join_types)) {
      // @todo: Multiple different join types are possible here that
      //   define in result multiple chains. There should be some criteria to choose
      //   an optimal chain but not just the first matched.
      $join_type = reset($join_types);
      $adapters_group_key = array_search($join_type, array_column($matched_adapter_groups, 'output'));
      // @todo: move into method ::loadGroup($group, $chain_plugin_type)
      $adapters_group = $matched_adapter_groups[$adapters_group_key];
      foreach ($adapters_group['adapters'] as $adapter_id) {
        $chain['adapter'][] = $this->visualNAdapterManager->createInstance($adapter_id, []);
      }

      $mapper_id = array_search($join_type, $matched_mappers);
      $chain['mapper'][] = $this->visualNMapperManager->createInstance($mapper_id, []);
    }
    else {
      if (!empty($matched_adapter_groups) || !empty($matched_mappers)) {
        // @todo: there is a question which one to choose
        //  here we may have two possibilities: an adapter or a mapper serves as both, adapter and mapper
        //  @todo: first check mappers since no much sense in checking adapters (see comment below)


        $result_mappers = array_keys($matched_mappers, $drawer_input);
        $result_adapters = array_keys(array_column($matched_adapter_groups, 'output'), $drawer_input);
        if (!empty($result_mappers)) {
          $mapper_id = $result_mappers[0];
          $chain['mapper'][] = $this->visualNMapperManager->createInstance($mapper_id, []);
        }
        elseif (!empty($result_adapters)) {
          // @todo: Do not rely on adapters when mapping is required since adapters don't have to
          //   do it and there is no flag that would show whether adapters do remapping or not
          //   and the result of using the chain for building drawing would become unpredictable
          //   which is wrong for the point of view of chain builder concept where
          //   it MUST return a valid chain or an empty chain.
          //
          //   Though it is ok to use mappers without adapters (see the code below)
          //   because it is seen from mapper plugins annotation.
          /*
            $adapters_group_key = reset($result_adapters);
            $adapters_group = $matched_adapter_groups[$adapters_group_key];
            foreach ($adapters_group['adapters'] as $adapter_id) {
              $chain['adapter'][] = $this->visualNAdapterManager->createInstance($adapter_id, []);
            }
          */
        }

      }
    }



    // if source output is equal to drawer input (e.g. no need in mapper or adapter)
    // else empty the chain (no drawing will be drawn)
    if (empty($chain['adapter']) && empty($chain['mapper'])) {
      // @todo: Exclude case when mapping is requried but only drawer is used
      //   since the result of the drawing building would become unpredictable,
      //   see the same notice for adapters above.
      /*
        if (empty($chain['adapter']) && empty($chain['mapper']) && $drawer_input != $input_type) {
      */
      $chain = ['drawer' => [], 'mapper' => [], 'adapter' => []];

      // @todo: check base type and try to compose chain based on it, currently it is
      //   accomplished using suggested subchains mechanism
      if (!$input_base_type) {
        $input_base_type = $this->getBaseTypeByType($input_type);
      }

      if ($input_base_type) {
      }
    }

    // @todo: cache chains

    return $chain;
  }

  // @todo:
  protected function composeSuggestedPluginsChain(DrawerInterface $drawer, $input_type, array $input_options, $input_base_type = '') {
  }

  /**
   * @inheritdoc
   *
   * @todo: move to interface and maybe rename
   */
  protected function getBaseTypeByType($input_type) {

    $input_base_type = '';


    // @todo: try to define base type by input (generally, resouce) type
    //    maybe this should use some kind of external service since refers
    //    to resources by not chains themselves.
    //    Also such a service could scan certain yml files or allow other modules
    //    to alter info about which resource types belong to which resource base types.


    return $input_base_type;
  }

}

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

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