88f22123224253f4ced725ab106909ce1bb7e770
[yaffs-website] / web / core / modules / migrate / src / Plugin / migrate / process / FileCopy.php
1 <?php
2
3 namespace Drupal\migrate\Plugin\migrate\process;
4
5 use Drupal\Core\File\FileSystemInterface;
6 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
7 use Drupal\Core\StreamWrapper\LocalStream;
8 use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
9 use Drupal\migrate\MigrateException;
10 use Drupal\migrate\MigrateExecutableInterface;
11 use Drupal\migrate\Plugin\MigrateProcessInterface;
12 use Drupal\migrate\Row;
13 use Symfony\Component\DependencyInjection\ContainerInterface;
14
15 /**
16  * Copies or moves a local file from one place into another.
17  *
18  * The file can be moved, reused, or set to be automatically renamed if a
19  * duplicate exists.
20  *
21  * The source value is an indexed array of two values:
22  * - The source path or URI, e.g. '/path/to/foo.txt' or 'public://bar.txt'.
23  * - The destination URI, e.g. 'public://foo.txt'.
24  *
25  * Available configuration keys:
26  * - move: (optional) Boolean, if TRUE, move the file, otherwise copy the file.
27  *   Defaults to FALSE.
28  * - file_exists: (optional) Replace behavior when the destination file already
29  *   exists:
30  *   - 'replace' - (default) Replace the existing file.
31  *   - 'rename' - Append _{incrementing number} until the filename is
32  *       unique.
33  *   - 'use existing' - Do nothing and return FALSE.
34  *
35  * Examples:
36  *
37  * @code
38  * process:
39  *   path_to_file:
40  *     plugin: file_copy
41  *     source:
42  *       - /path/to/file.png
43  *       - public://new/path/to/file.png
44  * @endcode
45  *
46  * @see \Drupal\migrate\Plugin\MigrateProcessInterface
47  *
48  * @MigrateProcessPlugin(
49  *   id = "file_copy"
50  * )
51  */
52 class FileCopy extends FileProcessBase implements ContainerFactoryPluginInterface {
53
54   /**
55    * The stream wrapper manager service.
56    *
57    * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
58    */
59   protected $streamWrapperManager;
60
61   /**
62    * The file system service.
63    *
64    * @var \Drupal\Core\File\FileSystemInterface
65    */
66   protected $fileSystem;
67
68   /**
69    * An instance of the download process plugin.
70    *
71    * @var \Drupal\migrate\Plugin\MigrateProcessInterface
72    */
73   protected $downloadPlugin;
74
75   /**
76    * Constructs a file_copy process plugin.
77    *
78    * @param array $configuration
79    *   The plugin configuration.
80    * @param string $plugin_id
81    *   The plugin ID.
82    * @param mixed $plugin_definition
83    *   The plugin definition.
84    * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrappers
85    *   The stream wrapper manager service.
86    * @param \Drupal\Core\File\FileSystemInterface $file_system
87    *   The file system service.
88    * @param \Drupal\migrate\Plugin\MigrateProcessInterface $download_plugin
89    *   An instance of the download plugin for handling remote URIs.
90    */
91   public function __construct(array $configuration, $plugin_id, array $plugin_definition, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system, MigrateProcessInterface $download_plugin) {
92     $configuration += [
93       'move' => FALSE,
94     ];
95     parent::__construct($configuration, $plugin_id, $plugin_definition);
96     $this->streamWrapperManager = $stream_wrappers;
97     $this->fileSystem = $file_system;
98     $this->downloadPlugin = $download_plugin;
99   }
100
101   /**
102    * {@inheritdoc}
103    */
104   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
105     return new static(
106       $configuration,
107       $plugin_id,
108       $plugin_definition,
109       $container->get('stream_wrapper_manager'),
110       $container->get('file_system'),
111       $container->get('plugin.manager.migrate.process')->createInstance('download', $configuration)
112     );
113   }
114
115   /**
116    * {@inheritdoc}
117    */
118   public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
119     // If we're stubbing a file entity, return a URI of NULL so it will get
120     // stubbed by the general process.
121     if ($row->isStub()) {
122       return NULL;
123     }
124     list($source, $destination) = $value;
125
126     // If the source path or URI represents a remote resource, delegate to the
127     // download plugin.
128     if (!$this->isLocalUri($source)) {
129       return $this->downloadPlugin->transform($value, $migrate_executable, $row, $destination_property);
130     }
131
132     // Ensure the source file exists, if it's a local URI or path.
133     if (!file_exists($source)) {
134       throw new MigrateException("File '$source' does not exist");
135     }
136
137     // If the start and end file is exactly the same, there is nothing to do.
138     if ($this->isLocationUnchanged($source, $destination)) {
139       return $destination;
140     }
141
142     // Check if a writable directory exists, and if not try to create it.
143     $dir = $this->getDirectory($destination);
144     // If the directory exists and is writable, avoid file_prepare_directory()
145     // call and write the file to destination.
146     if (!is_dir($dir) || !is_writable($dir)) {
147       if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
148         throw new MigrateException("Could not create or write to directory '$dir'");
149       }
150     }
151
152     $final_destination = $this->writeFile($source, $destination, $this->configuration['file_exists']);
153     if ($final_destination) {
154       return $final_destination;
155     }
156     throw new MigrateException("File $source could not be copied to $destination");
157   }
158
159   /**
160    * Tries to move or copy a file.
161    *
162    * @param string $source
163    *   The source path or URI.
164    * @param string $destination
165    *   The destination path or URI.
166    * @param int $replace
167    *   (optional) FILE_EXISTS_REPLACE (default) or FILE_EXISTS_RENAME.
168    *
169    * @return string|bool
170    *   File destination on success, FALSE on failure.
171    */
172   protected function writeFile($source, $destination, $replace = FILE_EXISTS_REPLACE) {
173     // Check if there is a destination available for copying. If there isn't,
174     // it already exists at the destination and the replace flag tells us to not
175     // replace it. In that case, return the original destination.
176     if (!($final_destination = file_destination($destination, $replace))) {
177       return $destination;
178     }
179     $function = 'file_unmanaged_' . ($this->configuration['move'] ? 'move' : 'copy');
180     return $function($source, $destination, $replace);
181   }
182
183   /**
184    * Returns the directory component of a URI or path.
185    *
186    * For URIs like public://foo.txt, the full physical path of public://
187    * will be returned, since a scheme by itself will trip up certain file
188    * API functions (such as file_prepare_directory()).
189    *
190    * @param string $uri
191    *   The URI or path.
192    *
193    * @return string|false
194    *   The directory component of the path or URI, or FALSE if it could not
195    *   be determined.
196    */
197   protected function getDirectory($uri) {
198     $dir = $this->fileSystem->dirname($uri);
199     if (substr($dir, -3) == '://') {
200       return $this->fileSystem->realpath($dir);
201     }
202     return $dir;
203   }
204
205   /**
206    * Determines if the source and destination URIs represent identical paths.
207    *
208    * @param string $source
209    *   The source URI.
210    * @param string $destination
211    *   The destination URI.
212    *
213    * @return bool
214    *   TRUE if the source and destination URIs refer to the same physical path,
215    *   otherwise FALSE.
216    */
217   protected function isLocationUnchanged($source, $destination) {
218     return $this->fileSystem->realpath($source) === $this->fileSystem->realpath($destination);
219   }
220
221   /**
222    * Determines if the given URI or path is considered local.
223    *
224    * A URI or path is considered local if it either has no scheme component,
225    * or the scheme is implemented by a stream wrapper which extends
226    * \Drupal\Core\StreamWrapper\LocalStream.
227    *
228    * @param string $uri
229    *   The URI or path to test.
230    *
231    * @return bool
232    */
233   protected function isLocalUri($uri) {
234     $scheme = $this->fileSystem->uriScheme($uri);
235
236     // The vfs scheme is vfsStream, which is used in testing. vfsStream is a
237     // simulated file system that exists only in memory, but should be treated
238     // as a local resource.
239     if ($scheme == 'vfs') {
240       $scheme = FALSE;
241     }
242     return $scheme === FALSE || $this->streamWrapperManager->getViaScheme($scheme) instanceof LocalStream;
243   }
244
245 }