visualn-8.x-1.x-dev/modules/visualn_block/src/Plugin/Block/VisualNBlock.php

modules/visualn_block/src/Plugin/Block/VisualNBlock.php
<?php

namespace Drupal\visualn_block\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformState;
use Symfony\Component\HttpFoundation\Request;
use Drupal\visualn\Manager\DrawingFetcherManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;

use Drupal\Core\Form\SubformStateInterface;
use Drupal\visualn_iframe\Entity\VisualNIFrame;

/**
 * Provides a 'VisualNBlock' block.
 *
 * @ingroup iframes_toolkit
 *
 * @Block(
 *  id = "visualn_block",
 *  admin_label = @Translation("VisualN Block"),
 * )
 */
class VisualNBlock extends BlockBase implements ContainerFactoryPluginInterface {

  const IFRAME_HANDLER_KEY = 'visualn_block_key';

  /**
   * The visualn drawing fetcher manager service.
   *
   * @var \Drupal\visualn\Manager\DrawingFetcherManager
   */
  protected $visualNDrawingFetcherManager;

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

  /**
   * Constructs a VisualNFormatter object.
   *
   * @param array $configuration
   *   The plugin configuration, i.e. an array with configuration values keyed
   *   by configuration option name. The special key 'context' may be used to
   *   initialize the defined contexts by setting it to an array of context
   *   values keyed by context names.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition
   * @param \Drupal\visualn\Manager\DrawingFetcherManager $visualn_drawing_fetcher_manager
   *   The visualn drawing fetcher manager service.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, DrawingFetcherManager $visualn_drawing_fetcher_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    $this->visualNDrawingFetcherManager = $visualn_drawing_fetcher_manager;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
         'fetcher_id' => '',
         'fetcher_config' => [],
         'sharing_settings' => [],
         'iframe_hash' => '',
        ] + parent::defaultConfiguration();
  }

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state) {
    // Get fetcher plugins list for the drawing fetcher select.
    $fetchers_list = [];
    $definitions = $this->visualNDrawingFetcherManager->getDefinitions();
    foreach ($definitions as $definition) {
      // Exclude fetchers with which have at least one required context scince here no context is provided.
      if (!empty($definition['context'])) {
        foreach ($definition['context'] as $name => $context_definition) {
          if ($context_definition->isRequired()) {
            continue 2;
          }
        }
      }
      $fetchers_list[$definition['id']] = $definition['label'];
    }

    // @todo: review this check after the main issue in drupal core is resolved
    // @see https://www.drupal.org/node/2798261
    if ($form_state instanceof SubformStateInterface) {
      $fetcher_id = $form_state->getCompleteFormState()->getValue(['settings', 'fetcher_id']);
    }
    else {
      $fetcher_id = $form_state->getValue(['settings', 'fetcher_id']);
    }


    // If form is new and form_state is null for the fetcher_id, get fetcher_id from the block configuration.
    // Also we destinguish empty string and null because user may change fetcher
    // to '- Select drawing fetcher -' keyed by "", which is not null.
    if (is_null($fetcher_id)) {
      $fetcher_id = $this->configuration['fetcher_id'];
    }


    // select drawing fetcher plugin
    //$ajax_wrapper_id = 'visualn-block-fetcher-config-ajax-wrapper';
    $form_array_parents = isset($form['#array_parents']) ? $form['#array_parents'] : [];
    $ajax_wrapper_id = implode('-', array_merge($form_array_parents, ['fetcher_id'])) . '-visualn-block-ajax-wrapper';
    $form['fetcher_id'] = [
      '#type' => 'select',
      '#title' => t('Drawing fetcher plugin'),
      '#options' => $fetchers_list,
      '#default_value' => $fetcher_id,
      '#ajax' => [
        'callback' => [get_called_class(), 'ajaxCallback'],
        'wrapper' => $ajax_wrapper_id,
      ],
      '#empty_value' => '',
      '#empty_option' => t('- Select drawing fetcher -'),
    ];
    $form['fetcher_container'] = [
      '#prefix' => '<div id="' . $ajax_wrapper_id . '">',
      '#suffix' => '</div>',
      '#type' => 'container',
      '#weight' => '1',
      // add process at fetcher_container level since fetcher_id input should be already mapped (prepopulated)
      // for further processing (see FormBuilder::processForm())
      //'#process' => [[get_called_class(), 'processFetcherConfigurationSubform']],

      //'#process' => [[$this, 'processFetcherConfigurationSubform']],
    ];
    // Use #process callback for building the fetcher configuration form itself because it
    // may need #array_parents key to be already filled up (see PluginFormInterface::buildConfigurationForm()
    // method comments on https://api.drupal.org).
    $form['fetcher_container']['fetcher_config'] = [
      '#type' => 'container',
      '#process' => [[$this, 'processFetcherConfigurationSubform']],
    ];



    // @todo: review key names
    // check if visualn_iframe module is enabled
    $moduleHandler = \Drupal::service('module_handler');
    // @todo: check if blocks sharing enabled (in visualn_iframe settings) and permissions
    if ($moduleHandler->moduleExists('visualn_iframe')){
      $iframes_default_config = \Drupal::config('visualn_iframe.settings');
      $additional_config = \Drupal::config('visualn_block.iframe.settings');
      // check if blocks sharing allowed
      if ($additional_config->get('allow_blocks_sharing')) {

        $settings = $this->configuration['sharing_settings'];
        $form['sharing_settings'] = [
          '#type' => 'details',
          '#title' => $this->t('Sharing settings'),
          '#open' => TRUE,
          '#weight' => '1',
        ];
        // add hash link to the form
        // @todo: add hash link styles (for hash color depending on iframe pulishing status)
        $hash_label = \Drupal::service('visualn_iframe.builder')->getHashLabel($this->configuration['iframe_hash']);
        if ($hash_label) {
          $form['#attached']['library'][] = 'visualn_iframe/visualn-iframe-ui';
          $hash_label = " {$hash_label}";
        }
        $form['sharing_settings']['sharing_enabled'] = [
          '#type' => 'checkbox',
          '#title' => $this->t('Enable sharing') . $hash_label,
          '#default_value' => isset($settings['sharing_enabled']) ? $settings['sharing_enabled'] : FALSE,
        ];
        $form['sharing_settings']['use_defaults'] = [
          '#type' => 'checkbox',
          '#title' => $this->t('Use defaults'),
          // @todo: add to iframe settings
          '#default_value' => isset($settings['use_defaults']) ? $settings['use_defaults'] : FALSE,
          '#states' => [
            'visible' => [
              ':input[name="settings[sharing_settings][sharing_enabled]"]' => ['checked' => TRUE],
            ],
          ],
        ];
        $form['sharing_settings']['show_link'] = [
          '#type' => 'checkbox',
          '#title' => $this->t('Show origin link'),
          '#default_value' => isset($settings['show_link']) ? $settings['show_link'] : $iframes_default_config->get('default.show_link'),
          '#states' => [
            'visible' => [
              ':input[name="settings[sharing_settings][sharing_enabled]"]' => ['checked' => TRUE],
              ':input[name="settings[sharing_settings][use_defaults]"]' => ['checked' => FALSE],
            ],
          ],
        ];

        $form['sharing_settings']['origin_url'] = [
          '#type' => 'textfield',
          '#title' => $this->t('Origin url'),
          '#default_value' => isset($settings['origin_url']) ? $settings['origin_url'] : $iframes_default_config->get('default.origin_url'),
          '#description' => $this->t('Leave blank to use default origin url'),
          '#attributes' => [
            'placeholder' => $iframes_default_config->get('default.origin_url'),
          ],
          '#states' => [
            'visible' => [
              ':input[name="settings[sharing_settings][sharing_enabled]"]' => ['checked' => TRUE],
              ':input[name="settings[sharing_settings][use_defaults]"]' => ['checked' => FALSE],
              ':input[name="settings[sharing_settings][show_link]"]' => ['checked' => TRUE],
            ],
          ],
          // @todo: try to validate user input (allow absolute or relative paths or tokens)
        ];
        $form['sharing_settings']['origin_title'] = [
          '#type' => 'textfield',
          '#title' => $this->t('Origin title'),
          '#default_value' => isset($settings['origin_title']) ? $settings['origin_title'] : $iframes_default_config->get('default.origin_title'),
          '#description' => $this->t('Leave blank to use default origin title'),
          '#attributes' => [
            'placeholder' => $iframes_default_config->get('default.origin_title'),
          ],
          '#states' => [
            'visible' => [
              ':input[name="settings[sharing_settings][sharing_enabled]"]' => ['checked' => TRUE],
              ':input[name="settings[sharing_settings][use_defaults]"]' => ['checked' => FALSE],
              ':input[name="settings[sharing_settings][show_link]"]' => ['checked' => TRUE],
            ],
          ],
          // @todo: check if user input validation is needed
        ];
        $form['sharing_settings']['open_in_new_window'] = [
          '#type' => 'checkbox',
          '#title' => $this->t('Open in new window'),
          '#default_value' => isset($settings['open_in_new_window']) ? $settings['open_in_new_window'] : $iframes_default_config->get('default.open_in_new_window'),
          '#states' => [
            'visible' => [
              ':input[name="settings[sharing_settings][sharing_enabled]"]' => ['checked' => TRUE],
              ':input[name="settings[sharing_settings][use_defaults]"]' => ['checked' => FALSE],
              ':input[name="settings[sharing_settings][show_link]"]' => ['checked' => TRUE],
            ],
          ],
        ];
      }
    }
    // @todo: is this needed, i.e. maybe the value in $this->configuration is enough
    $form['iframe_hash'] = [
      '#type' => 'value',
      '#value' => $this->configuration['iframe_hash'],  // hash is set in blockSubmit()
    ];

    return $form;
  }

  /**
   * Return fetcher configuration form via ajax request at fetcher change
   */
  public static function ajaxCallback(array $form, FormStateInterface $form_state, Request $request) {
    return $form['settings']['fetcher_container'];
  }

  /**
   * Process fetcher configuration subform
   *
   * Here we use process callback, cinse fetcher_plugin::buildConfigurationForm() may need
   * element #array_parents keys (e.g. to define triggering element at ajax calls).
   */
  //public static function processFetcherConfigurationSubform(array $element, FormStateInterface $form_state, $form) {
  public function processFetcherConfigurationSubform(array $element, FormStateInterface $form_state, $form) {
    $fetcher_element_parents = array_slice($element['#parents'], 0, -2);
    $fetcher_id = $form_state->getValue(array_merge($fetcher_element_parents, ['fetcher_id']));
    // Whether fetcher_id is an empty string (which means changed to the Default option) or NULL (which means
    // that the form is fresh) there is nothing to attach for fetcher_config subform.
    //if (empty($fetcher_id)) {
    if (!$fetcher_id) {
      return $element;
    }

    //if (!is_null($fetcher_id) && $fetcher_id == $this->configuration['fetcher_id']) {
    if ($fetcher_id == $this->configuration['fetcher_id']) {
      // @note: plugins are instantiated with default configuration to know about it
      //    but at configuration form rendering always the form_state values are (should be) used
      $fetcher_config = $this->configuration['fetcher_config'];
    }
    else {
      $fetcher_config = [];
    }

    // Basically this check is not needed
    if ($fetcher_id) {
      // fetcher plugin buildConfigurationForm() needs Subform:createForSubform() form_state
      $subform_state = SubformState::createForSubform($element, $form, $form_state);

      // instantiate fetcher plugin
      $fetcher_plugin = $this->visualNDrawingFetcherManager->createInstance($fetcher_id, $fetcher_config);
      // attach fetcher configuration form
      // @todo: also fetcher_config_key may be added here as it is done for ResourceGenericDraweringFethcher
      //    and drawer_container_key.
      $element = $fetcher_plugin->buildConfigurationForm($element, $subform_state);

      // change fetcher configuration form container to fieldset if not empty
      if (Element::children($element)) {
        $element['#type'] = 'fieldset';
        $element['#title'] = t('Fetcher settings');
      }
/*
      $element['fetcher_config'] = [];
      $element['fetcher_config'] += [
        '#parents' => array_merge($element['#parents'], ['fetcher_config']),
        '#array_parents' => array_merge($element['#array_parents'], ['fetcher_config']),
      ];
*/
/*
      $element[$drawer_container_key]['drawer_config'] = [];
      $element[$drawer_container_key]['drawer_config'] += [
        '#parents' => array_merge($element['#parents'], [$drawer_container_key, 'drawer_config']),
        '#array_parents' => array_merge($element['#array_parents'], [$drawer_container_key, 'drawer_config']),
      ];
*/
    }

    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state) {
    $this->configuration['fetcher_id'] = $form_state->getValue('fetcher_id');
    // @todo: also keep in mind that fetcher_container will be removed from form_state values after restructuring
    $this->configuration['fetcher_config'] = $form_state->getValue(['fetcher_container', 'fetcher_config'], []);
    // @todo: maybe just keep as configuration value without using form element?
    $this->configuration['iframe_hash'] = $form_state->getValue('iframe_hash');

    // @todo: extracting and restructuring values, if needed, would better be done on the element level,
    //    as it is done inside ResourceGenericDrawingFetcher for drawer_container with drawer_config and drawer_fields.

    $fetcher_id = $this->configuration['fetcher_id'];
    $fetcher_config = $this->configuration['fetcher_config'];
    $fetcher_plugin = $this->visualNDrawingFetcherManager->createInstance($fetcher_id, $fetcher_config);

    // @todo: maybe move fetcher_plugin::submitConfigurationForm() to #element_submit when introduced into core,
    //    currently can be also in #element_validate which is not correct strictly speaking
    $full_form = $form_state->getCompleteForm();
    $subform = $form['settings']['fetcher_container']['fetcher_config'];
    $subform_state = SubformState::createForSubform($subform, $full_form, $form_state->getCompleteFormState());
    $fetcher_plugin->submitConfigurationForm($subform, $subform_state);

    // @todo: check $this->configuration['label'] for new blocks, is it already set here



    // @todo: rework this and share link build
    // @todo: move this block into a visualn_iframe function or a class
    // @todo: service would be preferable to using \Drupal (assuming it's an option in current context)
    $moduleHandler = \Drupal::service('module_handler');
    // check if visualn_iframe module is enabled
    if ($moduleHandler->moduleExists('visualn_iframe')) {
      // @todo: what if drawings sharing is enabled while properties form and editing before submit?
      $additional_config = \Drupal::config('visualn_block.iframe.settings');
      if ($additional_config->get('allow_blocks_sharing')) {

        $this->configuration['sharing_settings'] = $form_state->getValue('sharing_settings');

        // @todo: store permissions info and block config in iframe entry context field
        //   add the field iteself

        // @todo: keep the whole config to load block by config and check permissions
        //   review the comment below
        //
        // VisualN Blocks are config entities which basically can't always be identified
        // by some kind of ID. E.g. such blocks used with Panels contrib module are stored
        // as part of panel configuration.
        // On the other hand it may be sill needed to check user permissions to view an
        // iframe based on block content. Because of practically unlimited ways that such
        // blocks can be used (regions, panels etc.) there is no certan way to load them
        // and thus no other way to get info on permissions other than to store the whole config.
        $data = [
          'fetcher_id' => $fetcher_id,
          'fetcher_config' => $fetcher_config,
        ];

        $settings = $this->configuration['sharing_settings'];

        $hash = $this->configuration['iframe_hash'] ?: '';
        if (empty($hash)) {
          $hash = \Drupal::service('visualn_iframe.builder')->generateHash();
          // @todo: check 'langcode' and 'status' values
          $params = [
            'hash' => $hash,
            'status' => 1,
            'langcode' => 'en',
            'name' => $this->configuration['label'],
            'user_id' => \Drupal::currentUser()->id(),
            'displayed' => FALSE,
            'viewed' => FALSE,
            'handler_key' => static::IFRAME_HANDLER_KEY,
            'settings' => $settings,
            'data' => $data,
            'implicit' => FALSE,
          ];
          $iframe_entity = \Drupal::service('visualn_iframe.builder')
            ->createIFrameEntity($params);

          $this->configuration['iframe_hash'] = $hash;

          // @todo: is it really needed, isn't configuration value enough?
          //   note that iframe_hash is set as a #value form element
          $form_state->setValue('iframe_hash', $hash);
        }
        else {
          // Update the iframe entry or create a new one with the same hash.
          $iframe_entity = VisualNIFrame::getIFrameEntityByHash($hash);
          if ($iframe_entity) {
            $iframe_entity->setSettings($settings);
            $iframe_entity->setData($data);
            $iframe_entity->setName($this->configuration['label']);
            $iframe_entity->save();
          }
          else {
            // When a block config is imported on other site, there is no iframe entry
            // so create a new one with the same hash. Also it can happen when
            // an iframe entry was deleted manually.
            //
            // @todo: review the approach
            // For now just create a new 'published' entry with the same hash.
            // There is no info in that case whether it is 'displayed' or 'viewed'.
            // Maybe log a message, or make the workflow configurable via admin UI.
            //
            // @todo: Manual deletion should be prevented by iframe content provider
            //   that would check if there is a block using the hash.


            // @todo: check 'langcode' and 'status' values
            $params = [
              'hash' => $hash,
              'status' => 1,
              'langcode' => 'en',
              'name' => $this->configuration['label'],
              'user_id' => \Drupal::currentUser()->id(),
              'displayed' => FALSE,
              'viewed' => FALSE,
              'handler_key' => static::IFRAME_HANDLER_KEY,
              'settings' => $settings,
              'data' => $data,
              'implicit' => FALSE,
            ];
            $iframe_entity = \Drupal::service('visualn_iframe.builder')
              ->createIFrameEntity($params);
          }
        }

        // @todo: reset cache tag for iframe (since block content could change)
        //   the cache tag itself should use hash a the only way to identify the block build iframe
      }
    }
  }


  /**
   * {@inheritdoc}
   */
  public function build() {
    $fetcher_id = $this->configuration['fetcher_id'];

    if (empty($fetcher_id)) {
      return ['#markup' => ''];
    }

    // create fetcher plugin instance
    $fetcher_config = $this->configuration['fetcher_config'];
    $fetcher_plugin = $this->visualNDrawingFetcherManager->createInstance($fetcher_id, $fetcher_config);

    // get markup from the drawing fetcher
    $build['visualn_block'] = $fetcher_plugin->fetchDrawing();

    // @todo: move this block into a visualn_iframe function or a class
    // @todo: service would be preferable to using \Drupal (assuming it's an option in current context)
    $moduleHandler = \Drupal::service('module_handler');
    // check if visualn_iframe module is enabled
    if ($moduleHandler->moduleExists('visualn_iframe') && $this->configuration['sharing_settings']['sharing_enabled']) {

      $additional_config = \Drupal::config('visualn_block.iframe.settings');
      if ($additional_config->get('allow_blocks_sharing')) {

        // @todo: deal with render cache here (e.g. if module was enabled, disabled and then re-enabled - link won't appear/disapper because of block cache)
        //   also if a visualn_iframe entry is removed manually
        // @todo: rename the variable (everywhere)
        //   initialize in ::create()
        $share_link_builder = \Drupal::service('visualn_iframe.builder');

        // Generate iframe url box markup and attach to the block build
        $hash = $this->configuration['iframe_hash'];
        // @todo: check if the hash belongs here (e.g. not taken drawing entity etc.) if needed
        if ($hash) {
          $iframe_url = NULL;
          // The iframe entry should be created/changed in the block Submit handler
          $iframe_entity = VisualNIFrame::getIFrameEntityByHash($hash);
          if ($iframe_entity) {
            $iframe_url = $share_link_builder->getIFrameUrl($hash);

            $update = FALSE;

            // only change location if empty
            $location = $iframe_entity->getLocation();
            if (empty($location)) {
              $location = \Drupal::service('path.current')->getPath();
              $iframe_entity->setLocation($location);
              $update = TRUE;
            }

            // @todo: use getters and setters
            if (!$iframe_entity->get('displayed')->value) {
              $iframe_entity->set('displayed', TRUE);
              $update = TRUE;
            }

            if ($update) {
              $iframe_entity->save();
            }
          }
          else {
            // @todo: the value should be taken from config (set on IFrame settings page)
            //   Not recommended to enable since no way to check the user
            //   also mention in the setting description text

            $create_by_hash = $additional_config->get('implicit_entries_restore');
            if ($create_by_hash) {
              $settings = $this->configuration['sharing_settings'];
              $data = [];

              // @todo: see the comment regarding adding the full config
              //   to the data in class::blockSubmit()
              $fetcher_id = $this->configuration['fetcher_id'];
              $fetcher_config = $this->configuration['fetcher_config'];
              $data = [
                'fetcher_id' => $fetcher_id,
                'fetcher_config' => $fetcher_config,
              ];

              // @todo: check 'langcode' and 'status' values
              $params = [
                'hash' => $hash,
                'status' => 1,
                'langcode' => 'en',
                'name' => $this->configuration['label'],
                'user_id' => \Drupal::currentUser()->id(),
                'displayed' => TRUE,
                'location' => \Drupal::service('path.current')->getPath(),
                'viewed' => FALSE,
                'handler_key' => static::IFRAME_HANDLER_KEY,
                'settings' => $settings,
                'data' => $data,
                'implicit' => TRUE,
              ];
              $iframe_entity = \Drupal::service('visualn_iframe.builder')
                ->createIFrameEntity($params);

              $iframe_url = $share_link_builder->getIFrameUrl($hash);
            }
            else {
              // If no iframe entry and recreate is not allowed, do not show the link
              // @todo: review message text
              \Drupal::logger('visualn_block')->warning($this->t('IFrame wasn\'t created due to the following reason: disallowed (hash: @hash, path: @path).', [
                '@hash' => $hash,
                '@path' => \Drupal::service('path.current')->getPath(),
              ]));
              //\Drupal::logger('visualn_block')->warning($this->t('IFrame wasn\'t created'));
            }
          }


          if ($iframe_url) {
            // Attach iframe url box markup to the block build
            $build['share_iframe_link'] = $share_link_builder->buildLink($iframe_url);
          }
        }
        else {
          // @todo: log a message (for the case when no hash with enabled sharing)
        }
      }
      // @todo: this should react only on specific settings changes that influence share
      //   link exposition
      $build['#cache']['tags'][] = 'visualn_block_iframe_settings';
      // invalidate cache tag e.g. on entity delete, to restore it if allowed
      if ($iframe_entity) {
        $build['#cache']['tags'][] = 'visualn_iframe:' . $iframe_entity->id();
      }
    }

    return $build;
  }

}

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

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