EntityFile.php 9.69 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Contains \Drupal\file\Plugin\migrate\destination\EntityFile.
6 7
 */

8
namespace Drupal\file\Plugin\migrate\destination;
9

10
use Drupal\Core\Entity\EntityManagerInterface;
11
use Drupal\Core\Entity\EntityStorageInterface;
12 13
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\UriItem;
14 15 16
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\StreamWrapper\LocalStream;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
17 18
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;
19
use Drupal\migrate\MigrateException;
20
use Drupal\migrate\Plugin\migrate\destination\EntityContentBase;
21
use Symfony\Component\DependencyInjection\ContainerInterface;
22 23

/**
24 25 26
 * Every migration that uses this destination must have an optional
 * dependency on the d6_file migration to ensure it runs first.
 *
27 28 29 30 31 32
 * @MigrateDestination(
 *   id = "entity:file"
 * )
 */
class EntityFile extends EntityContentBase {

33 34 35 36 37 38 39 40 41 42
  /**
   * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
   */
  protected $streamWrapperManager;

  /**
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

43 44 45
  /**
   * {@inheritdoc}
   */
46
  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system) {
47 48 49 50 51
    $configuration += array(
      'source_base_path' => '',
      'source_path_property' => 'filepath',
      'destination_path_property' => 'uri',
      'move' => FALSE,
52
      'urlencode' => FALSE,
53
    );
54
    parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager, $field_type_manager);
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72

    $this->streamWrapperManager = $stream_wrappers;
    $this->fileSystem = $file_system;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
    $entity_type = static::getEntityTypeId($plugin_id);
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $migration,
      $container->get('entity.manager')->getStorage($entity_type),
      array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
      $container->get('entity.manager'),
73
      $container->get('plugin.manager.field.field_type'),
74 75 76
      $container->get('stream_wrapper_manager'),
      $container->get('file_system')
    );
77 78
  }

79 80 81 82
  /**
   * {@inheritdoc}
   */
  protected function getEntity(Row $row, array $old_destination_id_values) {
83 84 85 86 87 88
    // For stub rows, there is no real file to deal with, let the stubbing
    // process take its default path.
    if ($row->isStub()) {
      return parent::getEntity($row, $old_destination_id_values);
    }

89 90 91 92 93 94 95 96 97 98
    $destination = $row->getDestinationProperty($this->configuration['destination_path_property']);
    $entity = $this->storage->loadByProperties(['uri' => $destination]);
    if ($entity) {
      return reset($entity);
    }
    else {
      return parent::getEntity($row, $old_destination_id_values);
    }
  }

99 100 101 102
  /**
   * {@inheritdoc}
   */
  public function import(Row $row, array $old_destination_id_values = array()) {
103 104 105 106 107 108
    // For stub rows, there is no real file to deal with, let the stubbing
    // process create the stub entity.
    if ($row->isStub()) {
      return parent::import($row, $old_destination_id_values);
    }

109
    $file = $row->getSourceProperty($this->configuration['source_path_property']);
110
    $destination = $row->getDestinationProperty($this->configuration['destination_path_property']);
111
    $source = $this->configuration['source_base_path'] . $file;
112

113 114
    // Ensure the source file exists, if it's a local URI or path.
    if ($this->isLocalUri($source) && !file_exists($source)) {
115
      throw new MigrateException("File '$source' does not exist.");
116 117
    }

118
    // If the start and end file is exactly the same, there is nothing to do.
119
    if ($this->isLocationUnchanged($source, $destination)) {
120 121 122
      return parent::import($row, $old_destination_id_values);
    }

123 124 125 126 127 128 129 130
    $replace = $this->getOverwriteMode($row);
    $success = $this->writeFile($source, $destination, $replace);
    if (!$success) {
      $dir = $this->getDirectory($destination);
      if (file_prepare_directory($dir, FILE_CREATE_DIRECTORY)) {
        $success = $this->writeFile($source, $destination, $replace);
      }
      else {
131
        throw new MigrateException("Could not create directory '$dir'");
132 133
      }
    }
134

135 136 137 138
    if ($success) {
      return parent::import($row, $old_destination_id_values);
    }
    else {
139
      throw new MigrateException("File $source could not be copied to $destination.");
140 141 142 143 144 145 146 147 148 149 150 151 152
    }
  }

  /**
   * Tries to move or copy a file.
   *
   * @param string $source
   *  The source path or URI.
   * @param string $destination
   *  The destination path or URI.
   * @param integer $replace
   *  FILE_EXISTS_REPLACE (default) or FILE_EXISTS_RENAME.
   *
153
   * @return bool
154 155 156
   *  TRUE on success, FALSE on failure.
   */
  protected function writeFile($source, $destination, $replace = FILE_EXISTS_REPLACE) {
157
    if ($this->configuration['move']) {
158
      return (boolean) file_unmanaged_move($source, $destination, $replace);
159 160 161 162
    }
    else {
      $destination = file_destination($destination, $replace);
      $source = $this->urlencode($source);
163
      return @copy($source, $destination);
164
    }
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
  }

  /**
   * Determines how to handle file conflicts.
   *
   * @param \Drupal\migrate\Row $row
   *
   * @return integer
   *  Either FILE_EXISTS_REPLACE (default) or FILE_EXISTS_RENAME, depending
   *  on the current configuration.
   */
  protected function getOverwriteMode(Row $row) {
    if (!empty($this->configuration['rename'])) {
      $entity_id = $row->getDestinationProperty($this->getKey('id'));
      if ($entity_id && ($entity = $this->storage->load($entity_id))) {
        return FILE_EXISTS_RENAME;
      }
    }
    return FILE_EXISTS_REPLACE;
  }

  /**
   * Returns the directory component of a URI or path.
   *
   * For URIs like public://foo.txt, the full physical path of public://
   * will be returned, since a scheme by itself will trip up certain file
   * API functions (such as file_prepare_directory()).
   *
   * @param string $uri
   *  The URI or path.
   *
196
   * @return string|false
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
   *  The directory component of the path or URI, or FALSE if it could not
   *  be determined.
   */
  protected function getDirectory($uri) {
    $dir = $this->fileSystem->dirname($uri);
    if (substr($dir, -3) == '://') {
      return $this->fileSystem->realpath($dir);
    }
    else {
      return $dir;
    }
  }

  /**
   * Returns if the source and destination URIs represent identical paths.
   * If either URI is a remote stream, will return FALSE.
   *
   * @param string $source
   *  The source URI.
   * @param string $destination
   *  The destination URI.
   *
219
   * @return bool
220 221 222 223 224 225
   *  TRUE if the source and destination URIs refer to the same physical path,
   *  otherwise FALSE.
   */
  protected function isLocationUnchanged($source, $destination) {
    if ($this->isLocalUri($source) && $this->isLocalUri($destination)) {
      return $this->fileSystem->realpath($source) === $this->fileSystem->realpath($destination);
226 227
    }
    else {
228
      return FALSE;
229 230 231
    }
  }

232 233 234 235 236 237 238 239 240 241
  /**
   * Returns if the given URI or path is considered local.
   *
   * A URI or path is considered local if it either has no scheme component,
   * or the scheme is implemented by a stream wrapper which extends
   * \Drupal\Core\StreamWrapper\LocalStream.
   *
   * @param string $uri
   *  The URI or path to test.
   *
242
   * @return bool
243 244 245 246 247 248
   */
  protected function isLocalUri($uri) {
    $scheme = $this->fileSystem->uriScheme($uri);
    return $scheme === FALSE || $this->streamWrapperManager->getViaScheme($scheme) instanceof LocalStream;
  }

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
  /**
   * Urlencode all the components of a remote filename.
   *
   * @param string $filename
   *   The filename of the file to be urlencoded.
   *
   * @return string
   *   The urlencoded filename.
   */
  protected function urlencode($filename) {
    // Only apply to a full URL
    if ($this->configuration['urlencode'] && strpos($filename, '://')) {
      $components = explode('/', $filename);
      foreach ($components as $key => $component) {
        $components[$key] = rawurlencode($component);
      }
      $filename = implode('/', $components);
      // Actually, we don't want certain characters encoded
      $filename = str_replace('%3A', ':', $filename);
      $filename = str_replace('%3F', '?', $filename);
      $filename = str_replace('%26', '&', $filename);
270
    }
271
    return $filename;
272 273
  }

274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
  /**
   * {@inheritdoc}
   */
  protected function processStubRow(Row $row) {
    // We stub the uri value ourselves so we can create a real stub file for it.
    if (!$row->getDestinationProperty('uri')) {
      $field_definitions = $this->entityManager
        ->getFieldDefinitions($this->storage->getEntityTypeId(),
          $this->getKey('bundle'));
      $value = UriItem::generateSampleValue($field_definitions['uri']);
      if (empty($value)) {
        throw new MigrateException('Stubbing failed, unable to generate value for field uri');
      }
      // generateSampleValue() wraps the value in an array.
      $value = reset($value);
      // Make it into a proper public file uri, stripping off the existing
      // scheme if present.
      $value = 'public://' . preg_replace('|^[a-z]+://|i', '', $value);
      // Create a real file, so File::preSave() can do filesize() on it.
      touch($value);
      $row->setDestinationProperty('uri', $value);
    }
    parent::processStubRow($row);
  }

299
}