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