custom_field-1.0.x-dev/src/Plugin/CustomField/FeedsType/FileTarget.php

src/Plugin/CustomField/FeedsType/FileTarget.php
<?php

declare(strict_types=1);

namespace Drupal\custom_field\Plugin\CustomField\FeedsType;

use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\FileExists;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Utility\Token;
use Drupal\custom_field\Attribute\CustomFieldFeedsType;
use Drupal\feeds\Exception\EmptyFeedException;
use Drupal\feeds\Exception\TargetValidationException;
use Drupal\file\FileInterface;
use Drupal\file\FileRepositoryInterface;
use GuzzleHttp\ClientInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Plugin implementation of the 'file' feeds type.
 */
#[CustomFieldFeedsType(
  id: 'file',
  label: new TranslatableMarkup('File'),
  mark_unique: FALSE,
)]
class FileTarget extends EntityReferenceTarget {

  /**
   * The http client.
   *
   * @var \GuzzleHttp\ClientInterface
   */
  protected ClientInterface $client;

  /**
   * The list of allowed file extensions.
   *
   * @var array
   */
  protected array $fileExtensions;

  /**
   * Token service.
   *
   * @var \Drupal\Core\Utility\Token
   */
  protected Token $token;

  /**
   * The file and stream wrapper helper.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected FileSystemInterface $fileSystem;

  /**
   * The file repository.
   *
   * @var \Drupal\file\FileRepositoryInterface
   */
  protected FileRepositoryInterface $fileRepository;

  /**
   * The system.file configuration.
   *
   * @var \Drupal\Core\Config\ImmutableConfig
   */
  protected ImmutableConfig $fileConfig;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
    $instance->client = $container->get('http_client');
    $instance->token = $container->get('token');
    $instance->fileSystem = $container->get('file_system');
    $instance->fileRepository = $container->get('file.repository');
    $instance->fileConfig = $container->get('config.factory')->get('system.file');
    $file_extensions = $configuration['field_settings']['file_extensions'] ?? '';
    $instance->fileExtensions = array_keys(explode(' ', (string) $file_extensions));

    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration(): array {
    return ['existing' => FileExists::Error->name] + parent::defaultConfiguration();
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(int $delta, array $configuration): array {
    $form = parent::buildConfigurationForm($delta, $configuration);
    $options = [
      FileExists::Replace->name => $this->t('Replace'),
      FileExists::Rename->name => $this->t('Rename'),
      FileExists::Error->name => $this->t('Ignore'),
    ];

    $form['existing'] = [
      '#type' => 'select',
      '#title' => $this->t('Handle existing files'),
      '#options' => $options,
      '#default_value' => $configuration['existing'],
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function getSummary(array $configuration): array {
    $summary = parent::getSummary($configuration);

    switch ($configuration['existing']) {
      case FileExists::Replace->name:
        $message = 'Replace';
        break;

      case FileExists::Rename->name:
        $message = 'Rename';
        break;

      case FileExists::Error->name:
        $message = 'Ignore';
        break;

      default:
        $message = '';
    }

    $summary[] = $this->t('Existing files: %existing', ['%existing' => $message]);

    return $summary;
  }

  /**
   * {@inheritdoc}
   *
   * @throws \GuzzleHttp\Exception\GuzzleException
   */
  public function prepareValue(mixed $value, array $configuration, string $langcode): ?int {
    return !empty($value) ? (int) $this->getFile($value, $configuration, $langcode) : NULL;
  }

  /**
   * {@inheritdoc}
   *
   * Filesize and MIME-type aren't sensible fields to match on so these are
   * filtered out.
   */
  protected function filterFieldTypes(FieldStorageDefinitionInterface $field): bool {
    $ignore_fields = [
      'filesize',
      'filemime',
    ];

    return !in_array($field->getName(), $ignore_fields) && parent::filterFieldTypes($field);
  }

  /**
   * {@inheritdoc}
   *
   * The file entity doesn't support any bundles. Providing an empty array here
   * will prevent the bundle check from being added in the find entity query.
   */
  protected function getBundles(): array {
    return [];
  }

  /**
   * Searches for an entity by entity key.
   *
   * @param string $field
   *   The subfield to search in.
   * @param array $configuration
   *   The feeds configuration array for the custom field.
   * @param string $search
   *   The value to search for.
   * @param string $langcode
   *   The feeds language code.
   *
   * @return int|bool
   *   The entity id, or false, if not found.
   */
  protected function findEntity(string $field, array $configuration, string $search, string $langcode): bool|int {
    $entities = $this->findEntities($field, $configuration, $search, $langcode);
    if (!empty($entities)) {
      return reset($entities);
    }
    return FALSE;
  }

  /**
   * Returns a file id given a url.
   *
   * @param string $value
   *   A URL file object.
   * @param array $configuration
   *   The feeds configuration array for the field.
   * @param string $langcode
   *   The feeds language code.
   *
   * @return bool|int
   *   The file id.
   *
   * @throws \GuzzleHttp\Exception\GuzzleException
   */
  protected function getFile(string $value, array $configuration, string $langcode): bool|int {
    if (empty($value)) {
      // No file.
      throw new EmptyFeedException('The given file url is empty.');
    }

    // Perform a lookup against the value using the configured reference method.
    if (FALSE !== ($fid = $this->findEntity($configuration['reference_by'], $configuration, $value, $langcode))) {
      return $fid;
    }

    // Compose file path.
    $filepath = $this->getDestinationDirectory() . 'FileTarget.php/' . $this->getFileName($value);

    switch ($configuration['existing']) {
      case FileExists::Error:
        if (file_exists($filepath) && $fid = $this->findEntity('uri', $configuration, $filepath, $langcode)) {
          return $fid;
        }
        if ($file = $this->writeData($this->getContent($value), $filepath, FileExists::Replace)) {
          return (int) $file->id();
        }
        break;

      default:
        if ($file = $this->writeData($this->getContent($value), $filepath, $configuration['existing'])) {
          return (int) $file->id();
        }
    }

    // Something bad happened while trying to save the file to the database. We
    // need to throw an exception so that we don't save an incomplete field
    // value.
    throw new TargetValidationException(sprintf('There was an error saving the file: %s', $filepath));
  }

  /**
   * Prepares destination directory and returns its path.
   *
   * @return string
   *   The directory to save the file to.
   */
  protected function getDestinationDirectory(): string {
    $file_directory = $this->configuration['field_settings']['file_directory'] ?? '';
    $destination = $this->token->replace($this->configuration['uri_scheme'] . '://' . trim($file_directory, '/'));
    $this->fileSystem->prepareDirectory($destination, FileSystemInterface::MODIFY_PERMISSIONS | FileSystemInterface::CREATE_DIRECTORY);

    return $destination;
  }

  /**
   * Extracts the file name from the given url and checks for valid extension.
   *
   * @param string $url
   *   The URL to get the file name for.
   *
   * @return string
   *   The file name.
   *
   * @throws \Drupal\feeds\Exception\TargetValidationException
   *   In case the file extension is not valid.
   */
  protected function getFileName($url): string {
    $filename = trim($this->fileSystem->basename($url), " \t\n\r\0\x0B.");
    // Remove query string from file name, if it has one.
    [$filename] = explode('?', $filename);
    $extension = substr($filename, strrpos($filename, '.') + 1);

    if (!preg_grep('/' . $extension . '/i', $this->fileExtensions)) {
      throw new TargetValidationException(sprintf('The file, %s, failed to save because the extension, %s, is invalid.', $url, $extension));
    }

    return $filename;
  }

  /**
   * Attempts to download the file at the given url.
   *
   * @param string $url
   *   The URL to download a file from.
   *
   * @return string
   *   The file contents.
   *
   * @throws \Drupal\feeds\Exception\TargetValidationException|\GuzzleHttp\Exception\GuzzleException
   *   In case the file could not be downloaded.
   */
  protected function getContent($url): string {
    $response = $this->client->request('GET', $url);

    if ($response->getStatusCode() >= 400) {
      throw new TargetValidationException(sprintf('Download of %s failed with code %d.', $url, $response->getStatusCode()));
    }

    return (string) $response->getBody();
  }

  /**
   * {@inheritdoc}
   */
  protected function createEntity($label, array $configuration, string $feeds_langcode): bool|int|string {
    $target_type = $this->configuration['target_type'];
    if (!strlen(trim($label))) {
      return FALSE;
    }

    $bundles = $this->getBundles();

    $entity = $this->entityTypeManager->getStorage($target_type)->create([
      $this->getLabelKey() => $label,
      $this->getBundleKey() => reset($bundles),
      'uri' => $label,
    ]);
    $entity->save();

    return $entity->id();
  }

  /**
   * Saves a file to the specified destination and creates a database entry.
   *
   * @param string $data
   *   A string containing the contents of the file.
   * @param string|null $destination
   *   (optional) A string containing the destination URI. This must be a stream
   *   wrapper URI. If no value or NULL is provided, a randomized name will be
   *   generated and the file will be saved using Drupal's default files scheme,
   *   usually "public://".
   * @param int|\Drupal\Core\File\FileExists $replace
   *   (optional) The replace behavior when the destination file already exists.
   *   Possible values include:
   *   - FileExists::Replace: Replace the existing file. If a
   *     managed file with the destination name exists, then its database entry
   *     will be updated. If no database entry is found, then a new one will be
   *     created.
   *   - FileExists::Rename: (default) Append
   *     _{incrementing number} until the filename is unique.
   *   - FileExists::Error: Do nothing and return FALSE.
   *
   * @return \Drupal\file\FileInterface|false
   *   A file entity, or FALSE on error.
   */
  protected function writeData(string $data, ?string $destination = NULL, int|FileExists $replace = FileExists::Rename): bool|FileInterface {
    if (empty($destination)) {
      $destination = $this->fileConfig->get('default_scheme') . '://';
    }
    try {
      return $this->fileRepository->writeData($data, $destination, $replace);
    }
    catch (EntityStorageException | FileException $e) {
      return FALSE;
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function hasAutocreateSupport(): bool {
    return FALSE;
  }

}

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

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