visualn-8.x-1.x-dev/modules/visualn_embed/src/Form/DrawingEmbedListDialogForm.php

modules/visualn_embed/src/Form/DrawingEmbedListDialogForm.php
<?php

namespace Drupal\visualn_embed\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\editor\Ajax\EditorDialogSave;
use Drupal\Core\Ajax\CloseModalDialogCommand;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\OpenDialogCommand;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Drupal\image\Entity\ImageStyle;
use Drupal\visualn_drawing\Entity\VisualNDrawing;
use Drupal\editor\EditorInterface;

/**
 * Build drawing embed form with list of available drawings
 *
 * The form provides the list of available drawing entities with preview, edit
 * and delete action links for each. Allows to embed (or replace) selected drawings
 * into ckeditor content. Also the form allows to open new drawing (drawing types list) dialog.
 *
 * @ingroup ckeditor_integration
 */
class DrawingEmbedListDialogForm extends FormBase {

  // @todo: rename the class to DrawingSelectDialogForm

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'visualn_embed_drawing_select_dialog';
  }

  /**
   * {@inheritdoc}
   *
   * @param \Drupal\editor\EditorInterface $editor
   *   The editor to which this dialog corresponds.
   */
  public function buildForm(array $form, FormStateInterface $form_state, EditorInterface $editor = NULL, $init_params = array()) {
    // @todo: the library should be connected outside of the form
    $form['#attached']['library'][] = 'visualn_embed/preview-drawing-dialog';

    // The args may be set to manually init default values, e.g. for pager links,
    // see DrawingActionsController::openDialogFromPager().
    if (!empty($init_params) && is_array($init_params)) {
      foreach (['drawing_type', 'drawing_name', 'items_per_page'] as $data_key) {
        if (!empty($init_params[$data_key]) && is_string($init_params[$data_key])) {
          $init_params[$data_key] = $init_params[$data_key];
        }
      }
    }

    // @todo: when an item selected and the resultant set after filters applied
    //   doesn't contain that item, and error message is shown:
    //   "An illegal choice has been detected. Please contact the site administrator."
    // @todo: the pager doesn't keep selected item (since is called via GET request)

    $drawing_entities_list = [];

    // add filters
    $drawing_type_options  = [];
    $drawing_types  = \Drupal::entityTypeManager()->getStorage('visualn_drawing_type')->loadMultiple();
    foreach ($drawing_types as $drawing_type) {
      $drawing_type_options[$drawing_type->id()]  = $drawing_type->label();
    }
    $form['filters'] = [
      '#type' => 'container',
    ];
    $form['filters']['drawing_type'] = [
      '#type' => 'select',
      '#title' => $this->t('Drawing type'),
      '#options' => $drawing_type_options,
      '#default_value' => !empty($init_params['drawing_type']) ? $init_params['drawing_type'] : '',
      '#empty_option' => $this->t('- All -'),
    ];
    $form['filters']['drawing_name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Name'),
      '#default_value' => !empty($init_params['drawing_name']) ? $init_params['drawing_name'] : '',
    ];
    $form['filters']['items_per_page'] = [
      '#type' => 'select',
      '#title' => $this->t('Items per page'),
      '#options' => [10 => 10, 20 => 20, 30 => 30, 40 => 40, 50 => 50],
      '#default_value' => !empty($init_params['items_per_page']) ? $init_params['items_per_page'] : '20',
      //'#default_value' => 10,
    ];
    // @todo: add 'reset filters' button
    $form['filters']['apply'] = [
      '#type' => 'submit',
      '#value' => $this->t('Apply'),
      // copy-paste from update_list submit
      '#ajax' => [
        'callback' => '::ajaxUpdateDrawingsListCallback',
        'event' => 'click',
      ],
      '#limit_validation_errors' => [],
      '#submit' => ['::emptySubmit'],
    ];


    // @todo: check comments in DrawingEmbedListDialogForm::buildForm()
    $input = $form_state->getUserInput();
    $selected_drawing_id = isset($input['editor_object']['data-visualn-drawing-id']) ? $input['editor_object']['data-visualn-drawing-id'] : 0;




    // @todo: maybe add 'Update' button, but open 'New drawing' in a dialog but not modal dialog
    $form['update_list'] = [
      '#type' => 'submit',
      // @todo: rename to refresh
      '#value' => $this->t('Update list'),
      '#ajax' => [
        // @todo: update list also applies filters, is that the expected behaviour?
        'callback' => '::ajaxUpdateDrawingsListCallback',
        'event' => 'click',
      ],
      '#limit_validation_errors' => [],
      '#submit' => ['::emptySubmit'],
    ];

    // @todo: other dialogs should be on top the current one and disable it
    //   maybe related to https://www.drupal.org/project/drupal/issues/2672344

    // @todo: check permissions
    // Create a new drawing button
    $form['new_drawing'] = [
      '#type' => 'submit',
      '#value' => $this->t('New drawing'),
      '#ajax' => [
        'callback' => '::ajaxNewDrawingCallback',
        'event' => 'click',
      ],
      '#limit_validation_errors' => [],
      '#submit' => ['::emptySubmit'],
      // disable button by default, check permissions below
      // also the button is disabled for no available drawing types
      '#disabled' => TRUE,
    ];

    // Check is user has permission to create at least one type of drawings
    $entity_manager = \Drupal::entityTypeManager();
    $access_handler = $entity_manager->getAccessControlHandler('visualn_drawing');
    foreach ($drawing_types as $drawing_type) {
      if ($access_handler->createAccess($drawing_type->id())) {
        $form['new_drawing']['#disabled'] = FALSE;
        break;
      }
    }

    // @todo: make it sticky at the bottom of the table
    $form['actions'] = [
      '#type' => 'actions',
    ];
    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Embed Drawing'),
      '#ajax' => [
        'callback' => '::ajaxSubmitForm',
        'event' => 'click',
      ],
/*
      // @todo: disable the button if there are no drawings in the list or no drawing selected
      //   this one not getting disabled (maybe because the submit is converted to  <button> type)
      '#states' => [
        'disabled' => [
          ':input[name="drawing_id"]' => ['value' => '0'],
        ],
      ],
*/
    ];
    $form['status_messages'] = [
      '#type' => 'status_messages',
      '#weight' => -10,
    ];
    // add wrapper to be used on form refresh if errors found
    $form['#prefix'] = '<div id="drawing-emebed-form-wrapper">';
    $form['#suffix'] = '</div>';
    $form['#attached']['library'][] = 'editor/drupal.editor.dialog';

    // use process callback for drawings list to have filters values already mapped and available
    $form['items_container']['#process'] = [[get_called_class(), 'processDrawingsOptionsList']];
    // the id is used in DrawingActionsController::openDialogFromPager()
    $form['items_container']['#prefix'] = '<div id="visualn-embed-drawing-select-dialog-options-ajax-wrapper">';
    $form['items_container']['#suffix'] = '</div>';

    return $form;
  }

  /**
   * Attach drawing items list process callback
   */
  public static function processDrawingsOptionsList(array $element, FormStateInterface $form_state, $form) {

    // Currently, pager ajax callback response returns only part of the dialog form containing the list of drawing items
    // and thus other form values and attributes do not change, including #action attribute.
    // This takes effect when trying to embed a drawing from a page other than the first one, which causes an error,
    // since when the form is getting rebuilt for subsequent submit, pager value is not getting considered in the
    // database query below.

    // Here pager value is kept in a hidden form element and reused by Embed Drawing button, other submits such
    // as Apply filters and Update list reset the value to show the updated list starting with the first page.
    // @todo: an alternative could be to change form '#action' attribute value in pager ajax response using js command
    //   in DrawingActionsController::updateDialogContentByPager() though Apply filters and Update list submits would
    //   still need to reset it then.
    $triggering_element = $form_state->getTriggeringElement();
    if (!empty($triggering_element)) {
      if (implode(':', $triggering_element['#array_parents']) == 'actions:submit') {
        // it is required to explicitly set pager value only at embed submit
        $input = $form_state->getUserInput();
        if (isset($input['current_page']) && $input['current_page'] !== "") {
          // @note: this implies only one pager on the dialog form, which is always the case
          \Drupal::request()->query->set('page', $input['current_page']);
        }
      }
      else {
        $input = $form_state->getUserInput();
        unset($input['current_page']);
        $form_state->setUserInput($input);
      }
    }
    $element['current_page'] = [
      '#type' => 'hidden',
      '#default_value' => \Drupal::request()->query->get('page', ''),
    ];

    // @todo: check comments in DrawingEmbedListDialogForm::buildForm()
    $input = $form_state->getUserInput();
    $selected_drawing_id = isset($input['editor_object']['data-visualn-drawing-id']) ? $input['editor_object']['data-visualn-drawing-id'] : 0;

    // @todo: the values are not using #tree (i.e. set to FALSE)
    $values = $form_state->getValues();
    $drawing_type = $values['drawing_type'];
    $drawing_name = $values['drawing_name'];
    // @todo: actually it is always set, same as other values
    $items_per_page = $values['items_per_page'] ?: 20;

    // @todo: alter query, add permissions check for entities
    // @todo: uses \Drupal::entityTypeManager() internally
    //   $query = $this->entityTypeManager->getStorage('node');
    //   $query_result = $query->getQuery()
    $query = \Drupal::entityQuery('visualn_drawing');
    //$query->addTag('visualn_drawing_access');
    $query->pager($items_per_page);
    // show only published drawing entities
    $query->condition('status', 1);
    if ($drawing_type) {
      $query->condition('type', $drawing_type);
    }
    if ($drawing_name) {
      // @todo: any need to safe-format (?)
      // the query is already case-insensitive
      $query->condition('name', '%' . $drawing_name . '%', 'LIKE');
    }
    $drawing_ids = $query->execute();

    // @todo: also add selected id or default value if any to show selected item
    //   or reset the value?


    $drawing_entities_list = [];

    // @todo: If it loads full entites, just get ids and labels using an sql query
    // @todo: also check permission and published status
    $drawing_entities  = \Drupal::entityTypeManager()->getStorage('visualn_drawing')->loadMultiple($drawing_ids);
    if (count($drawing_entities)) {
      $drawing_type_thumbnails = static::getDrawingTypesThumbnails();

      foreach ($drawing_entities as $drawing_entity) {

        $drawing_id = $drawing_entity->id();

        // get preview, edit and delete links markup
        $preview_link = '';
        $edit_link = '';
        $delete_link = '';

        // @todo: add per drawing type permissions
        // check permissions
        $user = \Drupal::currentUser();
        if ($drawing_entity->access('view')) {
          $preview_link = Link::createFromRoute(t('preview'), 'visualn_embed.drawing_embed_controller_real_preview', ['visualn_drawing' => $drawing_id], ['attributes' => ['class' => ['use-ajax']]]);
        }
        if ($drawing_entity->access('update')) {
          $edit_link = Link::createFromRoute(t('edit'), 'visualn_embed.drawing_controller_edit', ['visualn_drawing' => $drawing_id], ['attributes' => ['class' => ['use-ajax']]]);
        }
        if ($drawing_entity->access('delete')) {
          $delete_link = Link::createFromRoute(t('delete'), 'visualn_embed.drawing_controller_delete', ['visualn_drawing' => $drawing_id], ['attributes' => ['class' => ['use-ajax']]]);
        }

        // check drawing thumbnail field
        $drawing_thumbnail = '';
        if ($drawing_entity->get('thumbnail')->entity) {
          $drawing_thumbnail = $drawing_entity->get('thumbnail')->entity->getFileUri();
          $drawing_thumbnail = ImageStyle::load(VisualNDrawing::THUMBNAIL_IMAGE_STYLE)->buildUrl($drawing_thumbnail);
        }
        $drawing_entities_list[$drawing_entity->id()] = [
          'name' => $drawing_entity->label(),
          'id' => $drawing_entity->id(),
          'thumbnail_path' => $drawing_thumbnail ?: $drawing_type_thumbnails[$drawing_entity->bundle()],
          'preview_link' => $preview_link,
          'edit_link' => $edit_link,
          'delete_link' => $delete_link,
        ];
      }
    }

    // attach drawing items list
    $element['drawing_id'] = [
      '#type' => 'drawing_radios',
      '#options' => $drawing_entities_list,
      '#empty' => t('No drawings found'),
      //'#required' => TRUE,
    ];

    // preselect current drawing if set (user selected embedded drawing)
    if ($selected_drawing_id) {
      $element['drawing_id']['#default_value'] = $selected_drawing_id;
    }

    // @todo: check if value exists to avoid "An illegal choice has been detected. Please contact the site administrator." message
    //   actually if any value if set and is not present in filtered result, the message shows up

    // @todo:
    // add parameters to the pager link if set: selected_drawing_id, filters values
    $params = [];
    foreach (['drawing_type', 'drawing_name', 'items_per_page'] as $data_key) {
      if ($$data_key) {
        $params[$data_key] = $$data_key;
      }
    }
    $editor = \Drupal::routeMatch()->getParameter('editor');
    $element['pager'] = [
      '#visualn_embed_pager' => TRUE,
      '#type' => 'pager',
      '#parameters' => $params,
      '#route_name' => 'visualn_embed.visualn_drawing_embed_dialog_from_pager',
      '#route_parameters' => ['editor' => $editor->getFilterFormat()->Id()],
    ];

    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);

    // draiwng id is required when "embed drawing" submitted
    // it is used instead of 'required' option in drawing_id tableselect
    // since it is called in #element_validate which errors seems not to be
    // suppressed by #limit_validation_errors (see https://www.drupal.org/node/1488294)
    $drawing_id = $form_state->getValue('drawing_id', NULL);
    if (empty($drawing_id)) {
      $form_state->setErrorByName('drawing_id', $this->t('Drawing is required'));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
/*
    // Display result.
    foreach ($form_state->getValues() as $key => $value) {
      drupal_set_message($key . ': ' . $value);
    }
*/

  }

  /**
   * Emtpy submit to make #limit_validation_errors work
   *
   * @todo: #limit_validation_errors doesn't work without submit callback
   */
  public function emptySubmit(array &$form, FormStateInterface $form_state) {
  }

  /**
   * {@inheritdoc}
   */
  public function ajaxSubmitForm(array &$form, FormStateInterface $form_state) {

    $response = new AjaxResponse();
    if ($form_state->hasAnyErrors()) {
      // @todo: it is possible to replace form markup though if scrolled the user won't
      //   immidiately see the error, not good UX
      //$response->addCommand(new ReplaceCommand('#drawing-emebed-form-wrapper', $form));

      $content = $form;
      $response->addCommand(new OpenModalDialogCommand(t('Choose Drawing'), $content, ['classes' => ['ui-dialog' => 'ui-dialog-visualn']]));

      return $response;
    }

    $drawing_id = $form_state->getValue('drawing_id', 0);
    $data = [
      'drawing_id' => $drawing_id,
      'tag_attributes' => [
        'data-visualn-drawing-id' => $drawing_id,
      ],
    ];

    $response->addCommand(new EditorDialogSave($data));
    $response->addCommand(new CloseModalDialogCommand());

    return $response;
  }

  /**
   * {@inheritdoc}
   */
  public function ajaxUpdateDrawingsListCallback(array &$form, FormStateInterface $form_state) {
    $response = new AjaxResponse();

    // @todo: or maybe update the list form element itself without updating the whole modal with the form
    //   since the drawing doesn't embed
    //   see core/modules/ckeditor/js/ckeditor.js dialog::afterclose

    $content = $form;
    // @todo: add ui-dialog-visualn class instead for consistency with other calls
    $response->addCommand(new OpenModalDialogCommand(t('Choose Drawing'), $content, ['classes' => ['ui-dialog' => 'ui-dialog-visualn']]));

    return $response;
  }

  /**
   * {@inheritdoc}
   */
  public function ajaxNewDrawingCallback(array &$form, FormStateInterface $form_state) {
    $response = new AjaxResponse();

    // @todo: on difference between open dialog and open dialog commands
    //   see https://www.drupal.org/project/entity_browser/issues/2727031



    // @todo: here all downstream dialogs are opened in a simple dialog
    //   but not in a modal dialog for two reasons:
    //   * it doesn't work ...
    //   * for UX reasons


    $links = [];

    // @todo: output a message in case of no available types
    //   or no permission to create any

    // get drawing type thumbnails
    $drawing_type_thumbnails = static::getDrawingTypesThumbnails();

    $entity_manager = \Drupal::entityTypeManager();
    $drawing_types  = $entity_manager->getStorage('visualn_drawing_type')->loadMultiple();
    $access_handler = $entity_manager->getAccessControlHandler('visualn_drawing');
    foreach ($drawing_types as $drawing_type) {
      $type_id = $drawing_type->id();
      if ($access_handler->createAccess($type_id)) {
        $title = [
          '#theme' => 'visualn_embed_new_drawing_type_select_item_label',
          '#name' => $drawing_type->label(),
          '#id' => $type_id,
          '#thumbnail_path' => $drawing_type_thumbnails[$type_id],
          '#description' => trim($drawing_type->get('description')),
        ];

        // see https://www.drupal.org/node/1989646
        $links['link_'.$type_id] = [
          'title' => $title,
          'url' => Url::fromRoute('visualn_embed.new_drawing_controller_build', ['type' => $type_id]),
          'attributes' => [
            'class' => ['use-ajax'],
            'data-dialog-type' => 'dialog',
            'data-dialog-options' => json_encode([
              'target' => 'new-drawing-dialog',
              'classes' => ['ui-dialog' => 'ui-dialog-visualn'],
              'modal' => TRUE,
            ]),
          ],
        ];
      }
    }

    // prepare dialog content
    $content = [
      '#theme' => 'visualn_embed_new_drawing_type_select_links',
      '#items' => [
        '#theme' => 'links',
        '#links' => $links,
      ],
    ];

    // @todo: some outline appering around the modal and on the left when not using CloseModalDialogCommand
    //   though it shouldn't be required, maybe because of focus
    //$response->addCommand(new CloseModalDialogCommand());


    $response->addCommand(new OpenDialogCommand('#new-drawing-dialog',
      $this->t('Choose drawing type'), $content,
      ['classes' => ['ui-dialog' => 'ui-dialog-visualn'], 'modal' => TRUE]));

    return $response;
  }

  // @todo: move into drawing entity type class (or manager class)
  public static function getDrawingTypesThumbnails() {
    $drawing_type_thumbnails = [];
    // @todo: maybe move default thumbnail path into a constant
    // use default drawing type thumbnail
    $default_thumbnail = drupal_get_path('module', 'visualn_drawing') . '/images/drawing-thumbnail-default.png';
    $drawing_types  = \Drupal::entityTypeManager()->getStorage('visualn_drawing_type')->loadMultiple();

    $thumbnail_style = ImageStyle::load(VisualNDrawing::THUMBNAIL_IMAGE_STYLE);
    foreach ($drawing_types as $drawing_type) {
      $thumbnail_path = !empty($drawing_type->get('thumbnail_path')) ? $drawing_type->get('thumbnail_path') : $default_thumbnail;

      // remove leading slash if any
      $thumbnail_path = ltrim($thumbnail_path, '/');

      $styled_thumbnail_uri = $thumbnail_style->buildUri($thumbnail_path);
      if (!file_exists($styled_thumbnail_uri)) {
        $thumbnail_style->createDerivative($thumbnail_path, $styled_thumbnail_uri);
      }
      $styled_thumbnail = file_url_transform_relative(file_create_url($styled_thumbnail_uri));
      $drawing_type_thumbnails[$drawing_type->id()] = $styled_thumbnail;
    }

    return $drawing_type_thumbnails;
  }

}

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

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