Added missing modules, including some as submodules.
[yaffs-website] / web / modules / contrib / pdf_to_imagefield / pdf_to_imagefield.module
diff --git a/web/modules/contrib/pdf_to_imagefield/pdf_to_imagefield.module b/web/modules/contrib/pdf_to_imagefield/pdf_to_imagefield.module
new file mode 100644 (file)
index 0000000..1802320
--- /dev/null
@@ -0,0 +1,269 @@
+<?php
+
+/**
+ * @file
+ * Autogenerates PDF preview into an image field.
+ */
+
+/**
+ * Implements hook_entity_presave().
+ */
+function pdf_to_imagefield_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
+  if ($entity instanceof \Drupal\Core\Entity\ContentEntityInterface) {
+    foreach ($entity->getFieldDefinitions() as $field_definition) {
+      if ($field_definition->getType() == 'file' && $field_definition->getThirdPartySetting('pdf_to_imagefield', 'enable')) {
+        $image_style = \Drupal::entityTypeManager()->getStorage('image_style')->load($field_definition->getThirdPartySetting('pdf_to_imagefield', 'image_style'));
+        $target_field = $entity->getFieldDefinition($field_definition->getThirdPartySetting('pdf_to_imagefield', 'target_field'));
+        $force_image_toolkit = $field_definition->getThirdPartySetting('pdf_to_imagefield', 'force_image_toolkit_enable') && $field_definition->getThirdPartySetting('pdf_to_imagefield', 'force_image_toolkit') ? $field_definition->getThirdPartySetting('pdf_to_imagefield', 'force_image_toolkit') : FALSE;
+
+        if ($image_style && $target_field) {
+          $files = array_map(function ($item) {
+            return $item['target_id'];
+          }, $entity->get($field_definition->getName())->getValue());
+
+          $file_entities = !empty($files) ? \Drupal::entityTypeManager()->getStorage($field_definition->getFieldStorageDefinition()->getSetting('target_type'))->loadMultiple($files) : [];
+          // Now we remap the file entities array from being keyed by fid to
+          // being keyed by delta.
+          $files = [];
+          foreach ($entity->get($field_definition->getName())->getValue() as $delta => $item) {
+            $files[$delta] = $file_entities[$item['target_id']];
+          }
+
+          $original_entity = isset($entity->original) ? $entity->original : NULL;
+          if (!$entity->isNew() && $entity->isNewRevision()) {
+            // If it is a new revision, the $entity->original will have a
+            // pointer the "current" revision. While we need the just previous
+            // revision in order to see if files and previews have changed. At
+            // least paragraphs module does not promote its new revisions as
+            // "current", so we need this hack in order to recognize truly
+            // "latest" revision when running on paragraph entities.
+            // TODO: is there any better way to find the latest (not current)
+            // revision of an entity than using its entity query?
+            $query = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId())->getQuery();
+            $query->allRevisions();
+            $query->condition($entity->getEntityType()->getKey('id'), $entity->id());
+            if ($entity->isTranslatable()) {
+              $query->condition($entity->getEntityType()->getKey('langcode'), $entity->language()->getId());
+            }
+            $revision_id = max(array_keys($query->execute()));
+
+            // The revision might be not available, if that's a translatable
+            // entity whose new translation is being saved right now.
+            if ($revision_id) {
+              $original_entity = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId())->loadRevision($revision_id);
+            }
+          }
+
+          if ($entity->isTranslatable() && $original_entity && $original_entity->hasTranslation($entity->language()->getId())) {
+            $original_entity = $original_entity->getTranslation($entity->language()->getId());
+          }
+
+          $old_files = isset($original_entity) ? $original_entity->get($field_definition->getName())->getValue() : [];
+
+          // We only override missing previews, this way there is still a
+          // possibility to specify the preview images through other means (be
+          // it programmatically or via UI).
+          $previews = array_slice($entity->get($target_field->getName())->getValue(), 0, count($files));
+          $old_previews = isset($original_entity) ? $original_entity->get($target_field->getName())->getValue() : [];
+
+          foreach ($files as $delta => $file) {
+            // We only change preview if it is missing or the underlying file
+            // has changed while its preview has not been updated.
+            $is_file_changed = (isset($files[$delta]) xor isset($old_files[$delta])) || $files[$delta]->id() != $old_files[$delta]['target_id'];
+
+            $is_preview_changed = (isset($previews[$delta]) xor isset($old_previews[$delta]));
+            if (isset($previews[$delta]['target_id']) && isset($old_previews[$delta]['target_id'])) {
+              $is_preview_changed = $is_preview_changed || $previews[$delta]['target_id'] != $old_previews[$delta]['target_id'];
+            }
+
+            if ((!isset($previews[$delta]) || ($is_file_changed && !$is_preview_changed)) && $file->getMimeType() == 'application/pdf') {
+              $target_uri = $image_style->buildUri($file->getFileUri());
+              if ($force_image_toolkit) {
+                // Unfortunately, ImageStyle::createDerivative does not use
+                // dependency injection correctly. It accesses
+                // \Drupal::service() directly instead of injecting its
+                // dependencies in constructor.
+                // So we just outsmart it by temporarily swapping the active
+                // image toolkit.
+                $image_factory = \Drupal::service('image.factory');
+                $real_image_toolkit = $image_factory->getToolkitId();
+                $image_factory->setToolkitId('imagemagick');
+              }
+              $image_style->flush($file->getFileUri());
+              $image_style->createDerivative($file->getFileUri(), $target_uri);
+
+              if ($force_image_toolkit) {
+                // Now revert back the toolkit in the global image factory.
+                $image_factory->setToolkitId($real_image_toolkit);
+              }
+
+              $new_preview = \Drupal::entityTypeManager()->getStorage($target_field->getFieldStorageDefinition()->getSetting('target_type'))->create([
+                'uid' => \Drupal::currentUser()->id(),
+                'uri' => $target_uri,
+              ]);
+              $new_preview->setPermanent();
+              $new_preview->save();
+              $new_preview = $new_preview->id();
+
+              $previews[$delta] = [
+                'target_id' => $new_preview,
+                'alt' => t('Preview of @file_name', [
+                  '@file_name' => $file->getFilename(),
+                ]),
+              ];
+            }
+          }
+
+          $entity->set($target_field->getName(), $previews);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_imagemagick_arguments_alter().
+ */
+function pdf_to_imagefield_imagemagick_arguments_alter(\Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit $toolkit, $command) {
+  switch ($command) {
+    case 'convert':
+      // TODO: For some reason some versions/setups of ImageMagick do not
+      // correctly recognize PDF mime-type, so we additionally check for
+      // extension. So far no side effects have been reported regarding this
+      // intrusion.
+      if ($toolkit->getSourceFormat() == 'PDF' || preg_match('#\.pdf$#i', $toolkit->getSource())) {
+        // Insert the -flatten argument right after reading the PDF file.
+        // Otherwise the resulting images may have issues with background
+        // colors.
+        $toolkit->prependArgument('-flatten');
+      }
+      break;
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function pdf_to_imagefield_form_field_config_edit_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
+  $field_config = $form_state->getBuildInfo()['callback_object']->getEntity();
+
+  if (pdf_to_imagefield_is_eligible($field_config)) {
+    // Since this file field accepts PDFs, we can hook in with our stuff.
+    $form['third_party_settings']['pdf_to_imagefield'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('PDF preview autogeneration'),
+    );
+
+    $form['third_party_settings']['pdf_to_imagefield']['enable'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Generate 1st page preview'),
+      '#description' => t('Automatically generate 1st page PDF preview and save it into an arbitrary image field on the same entity.'),
+      '#default_value' => $field_config->getThirdPartySetting('pdf_to_imagefield', 'enable'),
+    );
+
+    // The rest of our settings should be visible only if the feature is
+    // actually enabled.
+    $states = [
+      'visible' => [
+        ':input[name$="[pdf_to_imagefield][enable]"]' => ['checked' => TRUE],
+      ],
+    ];
+
+    $form['third_party_settings']['pdf_to_imagefield']['target_field'] = array(
+      '#type' => 'select',
+      '#title' => t('Store 1st page image preview in'),
+      '#description' => t('Specify an image field where the 1st page preview of the PDF should be saved.'),
+      '#options' => pdf_to_imagefield_allowed_image_fields($field_config->getTargetEntityTypeId(), $field_config->getTargetBundle()),
+      '#states' => $states,
+      '#default_value' => $field_config->getThirdPartySetting('pdf_to_imagefield', 'target_field'),
+    );
+
+    $form['third_party_settings']['pdf_to_imagefield']['image_style'] = array(
+      '#type' => 'select',
+      '#title' => t('Image style'),
+      '#description' => t('Specify image style to use for PDF to image conversion.'),
+      '#options' => pdf_to_imagefield_allowed_image_styles(),
+      '#states' => $states,
+      '#default_value' => $field_config->getThirdPartySetting('pdf_to_imagefield', 'image_style'),
+    );
+
+    $form['third_party_settings']['pdf_to_imagefield']['force_image_toolkit_enable'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Force a specific image toolkit'),
+      '#description' => t('Force to use a specific image toolkit for PDF to image conversion instead of the default image toolkit.'),
+      '#default_value' => $field_config->getThirdPartySetting('pdf_to_imagefield', 'force_image_toolkit_enable'),
+      '#states' => $states,
+    );
+
+    $image_toolkit_options = [];
+    foreach (\Drupal::service('image.toolkit.manager')->getAvailableToolkits() as $id => $definition) {
+      $image_toolkit_options[$id] = $definition['title'];
+    }
+
+    $form['third_party_settings']['pdf_to_imagefield']['force_image_toolkit'] = array(
+      '#type' => 'radios',
+      '#title' => t('Image toolkit to force'),
+      '#options' => $image_toolkit_options,
+      '#default_value' => $field_config->getThirdPartySetting('pdf_to_imagefield', 'force_image_toolkit'),
+      '#states' => array_merge_recursive($states, ['visible' => [
+        ':input[name$="[pdf_to_imagefield][force_image_toolkit_enable]"]' => ['checked' => TRUE],
+      ]]),
+    );
+  }
+}
+
+/**
+ * Determine whether the field config is eligible for PDF to image manipulation.
+ *
+ * @param \Drupal\Core\Field\FieldConfigInterface $field_config
+ *   Field config entity object whose eligibility to determine
+ *
+ * @return bool
+ *   Whether the provided field config entity is eligible for PDF to image
+ *   manipulation
+ */
+function pdf_to_imagefield_is_eligible(\Drupal\Core\Field\FieldConfigInterface $field_config) {
+  return $field_config->getType() == 'file' && preg_match('/(^|\s|,)pdf($|\s|,)/i', $field_config->getSetting('file_extensions'));
+}
+
+/**
+ * Fetch a list of image fields where the preview could be saved.
+ *
+ * @param string $entity_type
+ *   Entity type where to conduct the search of allowed image fields
+ * @param string $bundle
+ *   Bundle where to conduct the search of allowed image fields
+ *
+ * @return array
+ *   Array of possible field names where PDF preview could be saved. Keys are
+ *   field names whereas values are their human labels
+ */
+function pdf_to_imagefield_allowed_image_fields($entity_type, $bundle) {
+  $allowed_values = [];
+
+  foreach (\Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $bundle) as $field_definition) {
+    if ($field_definition->getType() == 'image') {
+      $allowed_values[$field_definition->getName()] = $field_definition->getLabel();
+    }
+  }
+
+  return $allowed_values;
+}
+
+/**
+ * Fetch a list of eligible image styles that can be used for PDF conversion.
+ *
+ * @return array
+ *   Array of possible image styles that can be used for PDF preview
+ *   generation. Keys are image style machine-names whereas values are their
+ *   human labels
+ */
+function pdf_to_imagefield_allowed_image_styles() {
+  $allowed_values = [];
+
+  foreach (\Drupal::entityTypeManager()->getStorage('image_style')->loadMultiple() as $image_style) {
+    $allowed_values[$image_style->id()] = $image_style->label();
+  }
+
+  return $allowed_values;
+}