crossword-8.x-1.x-dev/modules/crossword_image/src/CrosswordImageService.php
modules/crossword_image/src/CrosswordImageService.php
<?php
namespace Drupal\crossword_image;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\File\FileSystemInterface;
use Drupal\crossword\CrosswordDataServiceInterface;
use Drupal\file\FileInterface;
use Drupal\Core\File\FileSystem;
use Drupal\file\FileUsage\DatabaseFileUsageBackend;
/**
* CrosswordImageService creates images from crossword files.
*
* This service takes a Crossword file and uses a specified crossword_image
* plugin to generate and/or retrieve an image that represents the crossword.
*/
class CrosswordImageService implements CrosswordImageServiceInterface {
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystem
*/
protected $fileSystem;
/**
* Crossword image plugin manager.
*
* @var \Drupal\crossword_image\CrosswordImageManager
*/
protected $crosswordImageManager;
/**
* File usage.
*
* @var \Drupal\file\FileUsage\DatabaseFileUsageBackend
*/
protected $fileUsage;
/**
* File storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $fileStorage;
/**
* The database connection used to store file usage information.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* The crossword data service.
*
* @var \Drupal\crossword\CrosswordDataServiceInterface
*/
protected $crosswordDataService;
/**
* Construct the Crossword Image Service.
*
* @param \Drupal\Core\File\FileSystem $file_system
* The file system.
* @param \Drupal\file\FileUsage\DatabaseFileUsageBackend $file_usage
* File usage service.
* @param \Drupal\crossword_image\CrosswordImageManager $crossword_image_manager
* Crossword image plugin manager.
* @param \Drupal\Core\Entity\EntityTypeManager $entity_type_manager
* Entity type manager.
* @param \Drupal\Core\Database\Connection $connection
* Database connection.
*/
public function __construct(FileSystem $file_system, DatabaseFileUsageBackend $file_usage, CrosswordImageManager $crossword_image_manager, EntityTypeManager $entity_type_manager, Connection $connection, CrosswordDataServiceInterface $crossword_data_service) {
$this->fileSystem = $file_system;
$this->fileUsage = $file_usage;
$this->crosswordImageManager = $crossword_image_manager;
$this->fileStorage = $entity_type_manager->getStorage('file');
$this->connection = $connection;
$this->crosswordDataService = $crossword_data_service;
}
/**
* {@inheritdoc}
*/
public function getImageUri(FileInterface $file, string $plugin_id) {
if (empty($this->crosswordDataService->getData($file))) {
return NULL;
}
$plugin = $this->getCrosswordImagePlugin($plugin_id);
if (empty($plugin)) {
return NULL;
}
$destination_uri = $this->getDestinationUri($file, $plugin);
if (file_exists($destination_uri)) {
// Check if the existing image is older than the file itself.
if (filemtime($file->getFileUri()) <= filemtime($destination_uri)) {
// The existing image can be used, nothing to do.
return $destination_uri;
}
else {
// Delete the existing but out-of-date image.
$this->fileSystem->delete($destination_uri);
image_path_flush($destination_uri);
}
}
if ($this->saveNewImageResource($file, $plugin, $destination_uri)) {
return $destination_uri;
}
}
/**
* {@inheritdoc}
*/
public function getImageEntity(FileInterface $file, string $plugin_id) {
if (empty($this->crosswordDataService->getData($file))) {
return NULL;
}
$destination_uri = $this->getImageUri($file, $plugin_id);
$values = [
'uri' => $destination_uri,
];
$existing = $this->fileStorage->loadByProperties($values);
if ($existing) {
return reset($existing);
}
else {
return $this->saveImageEntity($file, $destination_uri);
}
}
/**
* Creates and saves image representation of the crossword file.
*
* @param \Drupal\file\FileInterface $file
* The file from which the image is to be created.
* @param CrosswordImagePluginInterface $plugin
* The loaded crossword_image plugin.
* @param string $destination_uri
* The destination of the new file.
*
* @return bool
* Returns TRUE upon success, FALSE upon failure.
*/
protected function saveNewImageResource(FileInterface $file, CrosswordImagePluginInterface $plugin, $destination_uri) {
$toolkit = $plugin->getToolkit();
$toolkit->setType($plugin->getType());
$toolkit->setImage($plugin->createImageResource($file));
$directory = $this->fileSystem->dirname($destination_uri);
$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
$success = $toolkit->save($destination_uri);
if ($success) {
$this->saveImageEntity($file, $destination_uri);
}
return $success;
}
/**
* Gets the destination URI of the file.
*
* By default the image is saved in a subdirectory of the directory that
* holds the crossword file itself.
*
* @param \Drupal\file\FileInterface $file
* The file that is being converted.
* @param CrosswordImagePluginInterface $plugin
* The loaded crossword_image plugin.
*
* @return string
* The destination URI.
*/
protected function getDestinationUri(FileInterface $file, CrosswordImagePluginInterface $plugin) {
$file_dir = dirname($file->getFileUri());
if ($file_dir == 'public:') {
$output_path = 'public://crossword';
}
else {
$output_path = $file_dir . '/crossword';
}
$extension = image_type_to_extension($plugin->getType(), FALSE);
$filename = "{$file->id()}-{$plugin->getPluginId()}.$extension";
return $output_path . '/' . $filename;
}
/**
* Save a file entity for the image file (a managed 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 \Drupal\file\FileInterface
* The file entity of the image.
*/
protected function saveImageEntity(FileInterface $file, $destination_uri) {
// Manage the newly saved image file.
$values = [
'uri' => $destination_uri,
];
$existing = $this->fileStorage->loadByProperties($values);
if ($existing) {
$image = reset($existing);
$image->save();
return $image;
}
else {
/** @var \Drupal\file\FileInterface $image */
$image = $this->fileStorage->create($values);
$image->setPermanent();
$image->save();
$this->fileUsage->add($image, 'crossword', 'file', $file->id());
return $image;
}
}
/**
* Loads a plugin if it exists.
*
* @param string $plugin_id
* Crossword Image plugin id.
*
* @return CrosswordImagePluginInterface|null
* The plugin or null.
*/
protected function getCrosswordImagePlugin(string $plugin_id) {
try {
$plugin = $this->crosswordImageManager->createInstance($plugin_id);
}
catch (PluginNotFoundException $e) {
return NULL;
}
return $plugin;
}
/**
* {@inheritdoc}
*
* Terribly brute force and should be used sparingly!
*/
public function regenerateCrosswordImages(array $crossword_image_plugin_ids = []) {
// 1. Get all images registered by crossword module.
// Then for each image...
// 2. Parse name to find fid of crossword and crossword_image_plugin id.
// 3. Delete the image.
// 4. Generate new image from info in step 2.
// Steps 2-4 happen in a batch.
$query = $this->connection->select('file_usage', 'f')
->fields('f', ['fid'])
->condition('module', 'crossword');
$usage = $query->execute()->fetchAll();
$image_fids = array_column($usage, 'fid');
$chunk_size = 10;
$chunks = array_chunk($image_fids, $chunk_size);
$batch = [
'title' => 'Regenerating Crossword Images',
'finished' => '\Drupal\crossword_image\CrosswordImageService::crosswordImageRegenerateBatchFinished',
'operations' => [],
];
foreach ($chunks as $chunk) {
$batch['operations'][] = [
'\Drupal\crossword_image\CrosswordImageService::crosswordImageRegenerateBatchOp',
[$chunk, $crossword_image_plugin_ids],
];
}
batch_set($batch);
}
/**
* Batch op function for regenerating images.
*
* See function regenerateAllImages().
*/
public static function crosswordImageRegenerateBatchOp(array $image_fids, array $crossword_image_plugin_ids, &$context) {
$crossword_image_service = \Drupal::service('crossword.image_service');
$file_storage = \Drupal::service('entity_type.manager')->getStorage('file');
// Parse image file names.
$items = [];
foreach ($image_fids as $fid) {
if ($image = $file_storage->load($fid)) {
$image_filename = $image->getFilename();
$crossword_fid = explode("-", $image_filename)[0];
$crossword_image_plugin = explode(".", explode("-", $image_filename)[1])[0];
$items[] = [
'image' => $fid,
'image_uri' => $image->getFileUri(),
'crossword_fid' => $crossword_fid,
'crossword_image_plugin' => $crossword_image_plugin,
];
}
}
// Delete and regenerate.
foreach ($items as $item) {
if (empty($crossword_image_plugin_ids) || in_array($item['crossword_image_plugin'], $crossword_image_plugin_ids, TRUE)) {
if ($crossword_file = $file_storage->load($item['crossword_fid'])) {
// Step 3.
$crossword_image_service->fileSystem->delete($item['image_uri']);
image_path_flush($item['image_uri']);
// Step 4.
$plugin = $crossword_image_service->getCrosswordImagePlugin($item['crossword_image_plugin']);
$crossword_image_service->saveNewImageResource($crossword_file, $plugin, $item['image_uri']);
}
}
}
}
/**
* Finished callback.
*/
public static function crosswordImageRegenerateBatchFinished($success, array $results, array $operations) {
\Drupal::messenger()->addStatus(t('Crossword Image Regeneration is complete. Check the <a href="@url">files overview page</a> to see updates.', ['@url' => '/admin/content/files']));
}
}
