imdb_api-1.0.x-dev/src/Plugin/Field/FieldWidget/EpisodeWidget.php

src/Plugin/Field/FieldWidget/EpisodeWidget.php
<?php

namespace Drupal\imdb_api\Plugin\Field\FieldWidget;

use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AppendCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Ajax\MessageCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\imdb_api\Ajax\EpisodeDelete;
use Drupal\imdb_api\Ajax\EpisodeUpdate;

/**
 * Plugin implementation of the 'episode' widget.
 *
 * @FieldWidget(
 *   id = "episode",
 *   module = "imdb_api",
 *   label = @Translation("Episode"),
 *   field_types = {
 *     "episode",
 *   }
 * )
 */
class EpisodeWidget extends ImdbItemWidget {

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    // Retrieve the value of the current item as array and JSON.
    $data = [];
    $value = '';
    if ($item = $items->get($delta)) {
      $value = $item->getValue()['value'] ?? '';

      $data = $value ? Json::decode($value) : [];
    }

    // @todo refactor this.
    $parents = $form['#parents'];
    $field_name = $this->fieldDefinition->getName();
    $storage = $form_state->getStorage();
    $imdb_entity = $storage['imdb_entity'][$delta] ?? [];
    $trigger = $form_state->getTriggeringElement();
    $form_item_data = $form_state->getValue($field_name)[$delta]['value'] ?? '';
    $form_item_removed = !empty($form_state->getValue($field_name)[$delta]['removed']);
    $is_multiple = $this->fieldDefinition->getFieldStorageDefinition()->isMultiple();

    $is_add_more = isset($trigger['#array_parents']) && end($trigger['#array_parents']) == 'add_more';

    if (!$data && $form_item_data) {
      $data = Json::decode($form_item_data);
    }

    // Setting up a temporary default value to skip validation when field is marked as required.
    $value_default_value = !$form_state->getValues() ? '{}' : NULL;

    $element['value'] = $element + [
      '#type' => 'textfield',
      '#default_value' => isset($items[$delta]->value) ? $items[$delta]->value : $value_default_value,
      '#size' => $this->getSetting('size'),
      '#placeholder' => $this->getSetting('placeholder'),
      '#maxlength' => $this->getFieldSetting('max_length'),
      '#attributes' => [
        'class' => [
          'episode-field-' . $delta,
          'visually-hidden',
        ],
      ],
      '#attached' => [
        'library' => [
          'imdb_api/imdb_api.episode.widget',
        ],
      ],
    ];

    $element['removed'] = [
      '#type' => 'checkbox',
      '#default_value' => $form_item_removed,
      '#access' => $is_multiple,
      '#attributes' => [
        'class' => [
          'episode-removed',
          'episode-removed-' . $delta,
          'visually-hidden',
        ],
      ],
    ];

    $element['set'] = [
      '#type' => 'fieldset',
      '#title' => t('Choose a TV show episode'),
      '#collapsible' => FALSE,
      '#collapsed' => FALSE,
      '#access' => !($is_add_more && $form_item_removed)
    ];

    $element['set']['tv_show_container'] = [
      '#type' => 'container',
      '#weight' => '0',
      '#attributes' => [
        'class' => [
          'tv-show-container-' . $delta,
        ],
      ],
    ];

    $element['set']['tv_show_container']['tv_show'] = [
      '#type' => 'textfield',
      '#title' => $this->t('TV Show'),
      '#default_value' => $data ? $data['tv_show'] . ' (' . $data['tv_show_pure_id'] . ')' : '',
    ];

    $element['set']['actions'] = [
      '#type' => 'actions',
      '#weight' => '1',
    ];
    $element['set']['actions']['find_tv_show'] = [
      '#type' => 'submit',
      '#name' => 'find_tv_show_' . $delta,
      '#value' => $this->t('Find TV Show'),
      '#disabled' => $data || $imdb_entity,
      '#submit' => [[$this, 'findTvShowSubmit']],
      '#ajax' => [
        'callback' => [$this, 'findTvShowAjax'],
      ],
      '#limit_validation_errors' => [array_merge($parents, [$field_name])],
      '#attributes' => [
        'tabindex' => -1,
        'class' => [
          'action-find-tv-show-' . $delta,
        ],
      ],
    ];

    $element['set']['actions']['remove_tv_show'] = [
      '#type' => 'submit',
      '#name' => 'remove_tv_show_' . $delta,
      '#value' => $this->t('Remove TV Show'),
      '#limit_validation_errors' => [array_merge($parents, [$field_name])],
      '#access' => $is_multiple,
      '#ajax' => [
        'callback' => [static::class, 'removeTvShowAjax'],
        'effect' => 'fade',
      ],
      '#attributes' => [
        'tabindex' => -1,
        'class' => [
          'action-remove-tv-show-' . $delta,
        ],
      ],
    ];

    if (!$is_multiple) {
      if (!$imdb_entity && $data && $tv_show = $this->imdbApi->createImdbEntity($data['tv_show_pure_id'], 'tv_show')) {
        $imdb_entity = [
          'title' => $tv_show->getTitle(),
          'pure_id' => $tv_show->getPureId(),
          'seasons' => $tv_show->getSeasons(),
          'season' => $data['season'],
          'episode' => $data['episode'],
          'episode_value' => Json::encode($data),
        ];
      }
    }

    if ($imdb_entity && !$is_add_more) {
      $this->generateEpisodes($element, $delta, $imdb_entity);
      $this->generateSeasons($element, $delta, $imdb_entity);
    }
    elseif ($data) {
      $this->generateStaticDetails($element, $delta, $data);
    }
    else {
      $this->generateTvShowContainers($element, $delta);
    }

    return $element;
  }

  private function generateStaticDetails(&$element, $delta, $data) {
    $element['set']['season_container'] = [
      '#type' => 'container',
      '#weight' => '2',
      '#attributes' => [
        'class' => [
          'season-container-n' . $delta,
        ],
      ],
    ];
    $element['set']['season_container']['season'] = [
      '#markup' => $this->t('Season #@season', ['@season' => $data['season']]),
    ];
    $element['set']['episode_container'] = [
      '#type' => 'container',
      '#weight' => '3',
      '#attributes' => [
        'class' => [
          'episode-container-n' . $delta,
        ],
      ],
    ];
    $element['set']['episode_container']['episode'] = [
      '#markup' => $this->t('Episode #@episode: %title (@year)', [
        '@episode' => $data['episode'],
        '%title' => $data['title'],
        '@year' => $data['year'] ?? '',
      ]),
    ];
  }

  private function generateTvShowContainers(&$element, $delta) {
    $element['set']['season_container'] = [
      '#type' => 'container',
      '#weight' => '2',
      '#attributes' => [
        'class' => [
          'season-container-n' . $delta,
          'visually-hidden',
        ],
      ],
    ];
    $element['set']['episode_container'] = [
      '#type' => 'container',
      '#weight' => '3',
      '#attributes' => [
        'class' => [
          'episode-container-n' . $delta,
          'visually-hidden',
        ],
      ],
    ];
  }

  private function generateSeasons(&$element, $delta, $data, $default_value = NULL) {
    $options = [];
    foreach ($data['seasons'] as $season) {
      $options[$season->season] = $season->season;
    }

    $element['set']['season_container'] = [
      '#type' => 'container',
      '#weight' => '2',
      '#attributes' => [
        'class' => [
          'season-container-n' . $delta,
          'visually-hidden',
        ],
      ],
    ];
    $element['set']['season_container']['season'] = [
      '#type' => 'select',
      '#title' => $this->t('Season'),
      '#options' => $options,
      '#default_value' => $default_value ? $default_value : array_key_first($data['seasons']),
      '#ajax' => [
        'callback' => [$this, 'changeSeason'],
        'event' => 'change',
      ],
      '#attributes' => [
        'tabindex' => -1,
      ],
    ];

    if (isset($data['season'])) {
      $element['set']['season_container']['season']['#default_value'] = $data['season'];
      unset($element['set']['season_container']['#attributes']['class'][1]);
    }
  }

  private function generateEpisodes(&$element, $delta, $data, $default_value = NULL) {
    list('title' => $title, 'pure_id' => $pure_id, 'seasons' => $seasons) = $data;

    $episode_value = '';

    foreach ($seasons as $season) {
      $options = [];

      foreach ($season->episodes as $episode) {
        if (preg_match('/^\/title\/(.+)\/$/', $episode->id, $match)) {
          $year = isset($episode->year) ? $episode->year : '';

          // @todo needs to be validated before submit.
          $episode_data = Json::encode([
            'episode' => $episode->episode,
            'title' => $episode->title,
            'pure_id' => $match[1],
            'year' => $year,
            'season' => $season->season,
            'tv_show' => $title,
            'tv_show_pure_id' => $pure_id,
            'delta' => $delta,
          ]);

          $options[$episode_data] = 'Episode #' . $episode->episode . ': ' . $episode->title . ' (' . $year . ')';

          if (isset($data['season']) && $data['season'] == $season->season) {
            if (isset($data['episode']) && $data['episode'] == $episode->episode) {
              $episode_value = $data['episode_value'] ?? '';
            }
          }
        }
      }

      $episode_container = 'episode_container_s' . $season->season;
      $element['set'][$episode_container] = [
        '#type' => 'container',
        '#weight' => '3',
        '#attributes' => [
          'class' => [
            'episode-container-n' . $delta . '-s',
            'episode-container-n' . $delta . '-s' . $season->season,
            'visually-hidden',
          ],
        ],
      ];
      $element['set'][$episode_container]['episode'] = [
        '#type' => 'select',
        '#title' => $this->t('Episode'),
        '#options' => $options,
        '#default_value' => $default_value ? $default_value : array_key_first($options),
        '#ajax' => [
          'callback' => [$this, 'changeEpisode'],
          'event' => 'change',
        ],
        '#attributes' => [
          'tabindex' => -1,
        ],
      ];
    }

    if (isset($data['season'])) {
      unset($element['set']['episode_container_s' . $data['season']]['#attributes']['class'][2]);
    }
    if ($episode_value) {
      $element['set']['episode_container_s' . $data['season']]['episode']['#default_value'] = $episode_value;
    }
  }

  public function findTvShowSubmit(array $form, FormStateInterface $form_state) {
    $button = $form_state->getTriggeringElement();

    // Go one level up in the form, to the widgets container.
    $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -4));
    $field_name = $element['#field_name'];

    preg_match('/^find_tv_show_(\d+)$/', $button['#name'], $matches);

    $set = $form_state->getValue($field_name);
    $tv_show = $set[$matches[1]]['set']['tv_show_container']['tv_show'] ?? '';

    if ($tv_show && $entity = $this->imdbApi->createImdbEntity($tv_show, 'tv_show')) {
      $imdb_entity[$matches[1]] = [
        'title' => $entity->getTitle(),
        'pure_id' => $entity->getPureId(),
        'seasons' => $entity->getSeasons(),
      ];
      $form_state->set('imdb_entity', $imdb_entity);

      $set[$matches[1]]['set']['tv_show_container']['tv_show'] = $entity->getTitle() . ' (' . $entity->getPureId() . ')';
      $form_state->setValue($field_name, $set);
    }

    $form_state->setRebuild();
  }

  private function generateTvShowAjax(FormStateInterface $form_state, $response, $element, $delta) {
    $field_name = $element['#field_name'];

    $tv_show = $form_state->getValue($field_name)[$delta]['set']['tv_show_container']['tv_show'] ?? '';

    if (!$tv_show || !preg_match('/(?>tt)\d+/', $tv_show)) {
      $message = $this->t('No TV show with name "%name" found.', ['%name' => $tv_show]);
      $response->addCommand(new MessageCommand($message, 'div.tv-show-container-' . $delta, ['type' => 'warning'], TRUE));

      return;
    }

    $element[$delta]['set']['tv_show_container']['tv_show']['#value'] = $tv_show;

    $response->addCommand(new ReplaceCommand('div.tv-show-container-' . $delta, $element[$delta]['set']['tv_show_container']));
    $response->addCommand(new ReplaceCommand('input.action-find-tv-show-' . $delta, $element[$delta]['set']['actions']['find_tv_show']));
  }

  private function generateDetailsAjax(FormStateInterface $form_state, $response, $element, $delta) {
    $seasons = $form_state->getStorage()['imdb_entity'][$delta]['seasons'] ?? [];

    if (!$seasons) {
      return;
    }

    $response->addCommand(new ReplaceCommand('div.season-container-n' . $delta, $element[$delta]['set']['season_container']));

    foreach ($seasons as $season) {
      $response->addCommand(new AppendCommand('div.episode-container-n' . $delta, $element[$delta]['set']['episode_container_s' . $season->season]));
    }

    // Update an actual widget field.
    $response->addCommand(new EpisodeUpdate($element[$delta]['set']['episode_container_s' . reset($seasons)->season]['episode']['#default_value']));

    // Show season and episode fields if TV show is chosen.
    $response->addCommand(new InvokeCommand('div.season-container-n' . $delta, 'removeClass', ['visually-hidden']));
    $response->addCommand(new InvokeCommand('div.episode-container-n' . $delta, 'removeClass', ['visually-hidden']));
    $response->addCommand(new InvokeCommand('div.episode-container-n' . $delta . '-s' . reset($seasons)->season, 'removeClass', ['visually-hidden']));
  }

  public function findTvShowAjax(array $form, FormStateInterface $form_state) {
    $response = new AjaxResponse();

    $button = $form_state->getTriggeringElement();

    $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -4));

    preg_match('/^find_tv_show_(\d+)$/', $button['#name'], $matches);

    $this->generateTvShowAjax($form_state, $response, $element, $matches[1]);
    $this->generateDetailsAjax($form_state, $response, $element, $matches[1]);

    return $response;
  }

  public function changeSeason(array $form, FormStateInterface $form_state) {
    $response = new AjaxResponse();

    $trigger = $form_state->getTriggeringElement();

    $element = NestedArray::getValue($form, array_slice($trigger['#array_parents'], 0, -4));

    $delta = $trigger['#array_parents'][2];

    $data = array_key_first($element[$delta]['set']['episode_container_s' . $trigger['#value']]['episode']['#options']);

    // Update an actual widget field.
    $response->addCommand(new EpisodeUpdate($data));

    $response->addCommand(new InvokeCommand('div.episode-container-n' . $delta . '-s', 'addClass', ['visually-hidden']));
    $response->addCommand(new InvokeCommand('div.episode-container-n' . $delta . '-s' . $trigger['#value'], 'removeClass', ['visually-hidden']));

    return $response;
  }

  public function changeEpisode(array $form, FormStateInterface $form_state) {
    $response = new AjaxResponse();

    $trigger = $form_state->getTriggeringElement();

    // Update an actual widget field.
    $response->addCommand(new EpisodeUpdate($trigger['#value']));

    return $response;
  }

  public static function removeTvShowAjax(array &$form, FormStateInterface $form_state) {
    $response = new AjaxResponse();

    $trigger = $form_state->getTriggeringElement()['#name'];
    if (preg_match('/^remove_tv_show_(\d+)$/', $trigger, $matches)) {
      $response->addCommand(new EpisodeDelete($matches[1]));
    }

    return $response;
  }

}

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

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