Commit 5d8735f5 authored by Dan Flanagan's avatar Dan Flanagan Committed by Dan Flanagan
Browse files

Issue #3157484 by danflanagan8: Remove CrosswordImageFactory and CrosswordThumbmail field formatter

parent 61fc8249
Loading
Loading
Loading
Loading
+2 −4
Original line number Diff line number Diff line
@@ -58,15 +58,13 @@ puzzle can easily intermingle with other fields on the node (or other entity)
to which the Crossword field is attached. Of the formatters mentioned so far,
all except for the Crossword Solution formatter handle provide all of the js
necessary to make the puzzle fully playable in the browser. The remaining
formatters are used to render the file as a link (File Download Link and
Generic File) or an image (Crossword Thumbnail).
formatter is used to render the file as a link (Generic File).

=Crossword Images=
The crossword_image submodule provides a plugin system to generate images
from crossword files. There are Field Formatters included in this module
which allow you to display Crossword fields using an image generated
by one of these plugins. Eventually the Crossword Thumbnail formatter will
be removed in deference to the Crossword Image (rendered) formatter.
by one of these plugins.

=Printing a Puzzle=
There is a library provided by this module that can be used to make the
+44 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Post-update functions crossword module.
 */

use Drupal\Core\Config\Entity\ConfigEntityUpdater;
use Drupal\Core\Entity\Display\EntityDisplayInterface;

/**
 * Convert any instance of the crossword_thumbnail formatter
 * to use the crossword_image_rendered formatter. (Issue 3157484)
 */
function crossword_post_update_image_formatter(&$sandbox) {
  $config_entity_updater = \Drupal::classResolver(ConfigEntityUpdater::class);
  /** @var \Drupal\Core\Field\FormatterPluginManager $field_formatter_manager */
  $field_formatter_manager = \Drupal::service('plugin.manager.field.formatter');

  $formatter_callback = function (EntityDisplayInterface $display) use ($field_formatter_manager) {
    $needs_save = FALSE;
    foreach ($display->getComponents() as $field_name => $component) {
      if (empty($component['type'])) {
        continue;
      }
      $plugin_definition = $field_formatter_manager->getDefinition($component['type'], FALSE);
      if (is_a($plugin_definition['class'], Drupal\crossword\Plugin\Field\FieldFormatter\CrosswordThumbnail::class, TRUE)) {
        // The deprecated formatter is in use. Make sure crossword_image is installed.
        if (!\Drupal::moduleHandler()->moduleExists('crossword_image')) {
          \Drupal::service('module_installer')->install(['crossword_image']);
          $field_formatter_manager->clearCachedDefinitions();
        }
        // The settings can stay the same, but we change the type.
        $component['type'] = 'crossword_image_rendered';
        $display->setComponent($field_name, $component);
        $needs_save = TRUE;
      }
    }

    return $needs_save;
  };

  $config_entity_updater->update($sandbox, 'entity_view_display', $formatter_callback);
}

src/CrosswordImageFactory.php

deleted100644 → 0
+0 −137
Original line number Diff line number Diff line
<?php

namespace Drupal\crossword;

use Drupal\file\FileInterface;
use Drupal\Core\File\FileSystem;
use Drupal\Core\File\FileSystemInterface;

/**
 * A Class to manage generating images from a crossword file.
 */
class CrosswordImageFactory {

  /**
   * The file system service.
   *
   * @var \Drupal\Core\File\FileSystem
   */
  protected $fileSystem;

  /**
   * The crossword file parser plugin manage.
   *
   * @var \Drupal\crossword\CrosswordFileParserManager
   */
  protected $parserManager;

  /**
   * Construct the Crossword Image Factory.
   */
  public function __construct(FileSystem $file_system, CrosswordFileParserManager $parser_manager) {
    $this->fileSystem = $file_system;
    $this->parserManager = $parser_manager;
  }

  /**
   * Returns the uri to a thumbanil preview for the crossword file.
   *
   * @param Drupal\file\FileInterface $file
   *   The file representing the crossword puzzle.
   *
   * @return string
   *   The uri to a thumbnail version of the crossword.
   */
  public function getThumbnailUri(FileInterface $file) {
    $destination_uri = $this->getDestinationUri($file, 'thumbnail', 'png');
    if (file_exists($destination_uri)) {
      // Check if the existing thumbnail is older than the file itself.
      if (filemtime($file->getFileUri()) <= filemtime($destination_uri)) {
        // The existing thumbnail can be used, nothing to do.
        return $destination_uri;
      }
      else {
        // Delete the existing but out-of-date thumbnail.
        $this->fileSystem->delete($destination_uri);
        image_path_flush($destination_uri);
      }
    }
    if ($this->createThumbnail($file, $destination_uri)) {
      return $destination_uri;
    }
  }

  /**
   * Creates a thumbnail image from the crossword file.
   *
   * @param \Drupal\file\FileInterface $file
   *   The file from which the image is to be created.
   * @param string $destination_uri
   *   The destination of the new file.
   *
   * @return bool
   *   Returns TRUE upon success, FALSE upon failure.
   */
  protected function createThumbnail(FileInterface $file, $destination_uri) {
    $parser = $this->parserManager->loadCrosswordFileParserFromInput($file);
    if (!$parser) {
      return NULL;
    }
    $data = $parser->parse();
    $image = $this->getNewThumbnailImage($data);
    ob_start();
    imagepng($image);
    $image_data = ob_get_clean();
    $directory = $this->fileSystem->dirname($destination_uri);
    $this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
    return $this->fileSystem->saveData($image_data, $destination_uri, FileSystemInterface::EXISTS_REPLACE);
  }

  /**
   * Returns an image resource representing a preview of the puzzle.
   *
   * @param array $data
   *   The parsed crossword file.
   *
   * @return resource
   *   An image resource to be used as a thumbnail.
   */
  protected function getNewThumbnailImage(array $data) {
    $grid = $data['puzzle']['grid'];
    $square_size = 20;
    $line_size = 2;
    $width = count($grid[0]) * $square_size + $line_size / 2;
    $height = count($grid) * $square_size + $line_size / 2;
    $image = imagecreatetruecolor($width, $height);
    $white = imagecolorallocate($image, 255, 255, 255);
    foreach ($grid as $row_index => $row) {
      foreach ($row as $col_index => $square) {
        if ($square['fill'] !== NULL) {
          $color = $white;
          imagefilledrectangle($image, $col_index * $square_size + $line_size / 2, $row_index * $square_size + $line_size / 2, ($col_index + 1) * $square_size - $line_size / 2, ($row_index + 1) * $square_size - $line_size / 2, $color);
        }
      }
    }
    return $image;
  }

  /**
   * Gets the destination URI of the file.
   *
   * @param \Drupal\file\FileInterface $file
   *   The file that is being converted.
   * @param string $image_type
   *   The type of image (e.g. thumbnail) being created.
   * @param string $extension
   *   The extension of image being created.
   *
   * @return string
   *   The destination URI.
   */
  protected function getDestinationUri(FileInterface $file, $image_type = 'thumbnail', $extension = 'png') {
    $output_path = dirname($file->getFileUri()) . '/crossword';
    $filename = "{$file->id()}-$image_type.$extension";
    return $output_path . '/' . $filename;
  }

}
+0 −157
Original line number Diff line number Diff line
<?php

namespace Drupal\crossword\Plugin\Field\FieldFormatter;

use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Cache\Cache;
use Drupal\image\Plugin\Field\FieldFormatter\ImageFormatter;
use Drupal\crossword\CrosswordImageFactory;

/**
 * Plugin implementation of the 'crossword_thumbnail' formatter.
 *
 * @FieldFormatter(
 *   id = "crossword_thumbnail",
 *   label = @Translation("Crossword Thumbnail"),
 *   field_types = {
 *     "crossword"
 *   },
 * )
 */
class CrosswordThumbnail extends ImageFormatter implements ContainerFactoryPluginInterface {

  /**
   * The crossword image factory service.
   *
   * @var \Drupal\crossword\CrosswordImageFactory
   */
  protected $crosswordImageFactory;

  /**
   * Constructs an ImageFormatter object.
   *
   * @param string $plugin_id
   *   The plugin_id for the formatter.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The definition of the field to which the formatter is associated.
   * @param array $settings
   *   The formatter settings.
   * @param string $label
   *   The formatter label display setting.
   * @param string $view_mode
   *   The view mode.
   * @param array $third_party_settings
   *   Any third party settings settings.
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   * @param \Drupal\Core\Entity\EntityStorageInterface $image_style_storage
   *   The image style storage.
   * @param \Drupal\crossword\CrosswordImageFactory $crossword_image_factory
   *   The crossword image factory service.
   */
  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, AccountInterface $current_user, EntityStorageInterface $image_style_storage, CrosswordImageFactory $crossword_image_factory) {
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $current_user, $image_style_storage);
    $this->crosswordImageFactory = $crossword_image_factory;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $plugin_id,
      $plugin_definition,
      $configuration['field_definition'],
      $configuration['settings'],
      $configuration['label'],
      $configuration['view_mode'],
      $configuration['third_party_settings'],
      $container->get('current_user'),
      $container->get('entity_type.manager')->getStorage('image_style'),
      $container->get('crossword.image_factory')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];
    $files = $this->getEntitiesToView($items, $langcode);

    // Early opt-out if the field is empty.
    if (empty($files)) {
      return $elements;
    }

    $url = NULL;
    $image_link_setting = $this->getSetting('image_link');
    // Check if the formatter involves a link.
    if ($image_link_setting == 'content') {
      $entity = $items->getEntity();
      if (!$entity->isNew()) {
        $url = $entity->toUrl();
      }
    }
    elseif ($image_link_setting == 'file') {
      $link_file = TRUE;
    }

    $image_style_setting = $this->getSetting('image_style');

    // Collect cache tags to be added for each item in the field.
    $base_cache_tags = [];
    if (!empty($image_style_setting)) {
      $image_style = $this->imageStyleStorage->load($image_style_setting);
      $base_cache_tags = $image_style->getCacheTags();
    }

    foreach ($files as $delta => $file) {
      $image_uri = $this->crosswordImageFactory->getThumbnailUri($file);
      $cache_contexts = [];
      if (isset($link_file)) {

        // @todo Wrap in file_url_transform_relative(). This is currently
        // impossible. As a work-around, we currently add the 'url.site' cache
        // context to ensure different file URLs are generated for different
        // sites in a multisite setup, including HTTP and HTTPS versions of the
        // same site. Fix in https://www.drupal.org/node/2646744.
        $url = Url::fromUri(file_create_url($image_uri));
        $cache_contexts[] = 'url.site';
      }
      $cache_tags = Cache::mergeTags($base_cache_tags, $file->getCacheTags());

      // Extract field item attributes for the theme function, and unset them
      // from the $item so that the field template does not re-render them.
      $item = $file->_referringItem;
      $item_attributes = $item->_attributes;
      unset($item->_attributes);

      $item->uri = $image_uri;
      $item->alt = $file->getFilename();

      $elements[$delta] = [
        '#theme' => 'image_formatter',
        '#item' => $item,
        '#item_attributes' => $item_attributes,
        '#image_style' => $image_style_setting,
        '#url' => $url,
        '#cache' => [
          'tags' => $cache_tags,
          'contexts' => $cache_contexts,
        ],
      ];
    }

    return $elements;
  }

}