Pull merge.
[yaffs-website] / web / core / modules / migrate / src / Plugin / migrate / process / Download.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\migrate\MigrateException;
8 use Drupal\migrate\MigrateExecutableInterface;
9 use Drupal\migrate\Row;
10 use GuzzleHttp\Client;
11 use Symfony\Component\DependencyInjection\ContainerInterface;
12
13 /**
14  * Downloads a file from a HTTP(S) remote location into the local file system.
15  *
16  * The source value is an array of two values:
17  * - source URL, e.g. 'http://www.example.com/img/foo.img'
18  * - destination URI, e.g. 'public://images/foo.img'
19  *
20  * Available configuration keys:
21  * - file_exists: (optional) Replace behavior when the destination file already
22  *   exists:
23  *   - 'replace' - (default) Replace the existing file.
24  *   - 'rename' - Append _{incrementing number} until the filename is
25  *     unique.
26  *   - 'use existing' - Do nothing and return FALSE.
27  * - guzzle_options: (optional)
28  *   @link http://docs.guzzlephp.org/en/latest/request-options.html Array of request options for Guzzle. @endlink
29  *
30  * Examples:
31  *
32  * @code
33  * process:
34  *   plugin: download
35  *   source:
36  *     - source_url
37  *     - destination_uri
38  * @endcode
39  *
40  * This will download source_url to destination_uri.
41  *
42  * @code
43  * process:
44  *   plugin: download
45  *   source:
46  *     - source_url
47  *     - destination_uri
48  *   file_exists: rename
49  * @endcode
50  *
51  * This will download source_url to destination_uri and ensure that the
52  * destination URI is unique. If a file with the same name exists at the
53  * destination, a numbered suffix like '_0' will be appended to make it unique.
54  *
55  * @MigrateProcessPlugin(
56  *   id = "download"
57  * )
58  */
59 class Download extends FileProcessBase implements ContainerFactoryPluginInterface {
60
61   /**
62    * The file system service.
63    *
64    * @var \Drupal\Core\File\FileSystemInterface
65    */
66   protected $fileSystem;
67
68   /**
69    * The Guzzle HTTP Client service.
70    *
71    * @var \GuzzleHttp\Client
72    */
73   protected $httpClient;
74
75   /**
76    * Constructs a download 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\File\FileSystemInterface $file_system
85    *   The file system service.
86    * @param \GuzzleHttp\Client $http_client
87    *   The HTTP client.
88    */
89   public function __construct(array $configuration, $plugin_id, array $plugin_definition, FileSystemInterface $file_system, Client $http_client) {
90     $configuration += [
91       'guzzle_options' => [],
92     ];
93     parent::__construct($configuration, $plugin_id, $plugin_definition);
94     $this->fileSystem = $file_system;
95     $this->httpClient = $http_client;
96   }
97
98   /**
99    * {@inheritdoc}
100    */
101   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
102     return new static(
103       $configuration,
104       $plugin_id,
105       $plugin_definition,
106       $container->get('file_system'),
107       $container->get('http_client')
108     );
109   }
110
111   /**
112    * {@inheritdoc}
113    */
114   public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
115     // If we're stubbing a file entity, return a uri of NULL so it will get
116     // stubbed by the general process.
117     if ($row->isStub()) {
118       return NULL;
119     }
120     list($source, $destination) = $value;
121
122     // Modify the destination filename if necessary.
123     $final_destination = file_destination($destination, $this->configuration['file_exists']);
124
125     // Reuse if file exists.
126     if (!$final_destination) {
127       return $destination;
128     }
129
130     // Try opening the file first, to avoid calling file_prepare_directory()
131     // unnecessarily. We're suppressing fopen() errors because we want to try
132     // to prepare the directory before we give up and fail.
133     $destination_stream = @fopen($final_destination, 'w');
134     if (!$destination_stream) {
135       // If fopen didn't work, make sure there's a writable directory in place.
136       $dir = $this->fileSystem->dirname($final_destination);
137       if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
138         throw new MigrateException("Could not create or write to directory '$dir'");
139       }
140       // Let's try that fopen again.
141       $destination_stream = @fopen($final_destination, 'w');
142       if (!$destination_stream) {
143         throw new MigrateException("Could not write to file '$final_destination'");
144       }
145     }
146
147     // Stream the request body directly to the final destination stream.
148     $this->configuration['guzzle_options']['sink'] = $destination_stream;
149
150     try {
151       // Make the request. Guzzle throws an exception for anything but 200.
152       $this->httpClient->get($source, $this->configuration['guzzle_options']);
153     }
154     catch (\Exception $e) {
155       throw new MigrateException("{$e->getMessage()} ($source)");
156     }
157
158     return $final_destination;
159   }
160
161 }