PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta.3
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta.3
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / vendor / wp-php-toolkit / data-liberation / Importer / class-attachmentdownloader.php
jetpack / vendor / wp-php-toolkit / data-liberation / Importer Last commit date
class-attachmentdownloader.php 2 weeks ago class-attachmentdownloaderevent.php 2 weeks ago class-entityimporter.php 2 weeks ago class-entityiteratorchain.php 2 weeks ago class-filevisitorevent.php 2 weeks ago class-importsession.php 2 weeks ago class-importutils.php 2 weeks ago class-retryfrontloadingiterator.php 2 weeks ago class-streamimporter.php 2 weeks ago
class-attachmentdownloader.php
286 lines
1 <?php
2
3 namespace WordPress\DataLiberation\Importer;
4
5 use Exception;
6 use WordPress\Filesystem\Filesystem;
7 use WordPress\HttpClient\Client;
8 use WordPress\HttpClient\Request;
9
10 use function WordPress\Filesystem\wp_join_unix_paths;
11
12 class AttachmentDownloader {
13 private $client;
14 private $fps = array();
15 private $output_root;
16 private $output_paths = array();
17 /**
18 * @var Filesystem
19 */
20 private $source_from_filesystem;
21
22 private $pending_events = array();
23 private $enqueued_url;
24 private $progress = array();
25
26 public function __construct( $output_root, $options = array() ) {
27 $this->client = new Client();
28 $this->output_root = $output_root;
29 $this->source_from_filesystem = $options['source_from_filesystem'] ?? null;
30 }
31
32 public function get_progress() {
33 return $this->progress;
34 }
35
36 /**
37 * Whether any downloads are still in progress.
38 *
39 * Note that zero active requests does not mean all work is done.
40 * Even if all the response bytes are received, we still need to process
41 * them and emit the final success/failure events.
42 *
43 * @return bool
44 */
45 public function has_pending_requests() {
46 return count( $this->client->get_active_requests() ) > 0 || count( $this->pending_events ) > 0 || count( $this->progress ) > 0;
47 }
48
49 public function enqueue_if_not_exists( $url, $output_relative_path ) {
50 $this->enqueued_url = $url;
51
52 $output_path = wp_join_unix_paths( $this->output_root, $output_relative_path );
53 if ( file_exists( $output_path ) ) {
54 $this->pending_events[] = new AttachmentDownloaderEvent(
55 $this->enqueued_url,
56 AttachmentDownloaderEvent::ALREADY_EXISTS
57 );
58
59 return true;
60 }
61 if ( file_exists( $output_path . '.partial' ) ) {
62 $this->pending_events[] = new AttachmentDownloaderEvent(
63 $this->enqueued_url,
64 AttachmentDownloaderEvent::IN_PROGRESS
65 );
66
67 return true;
68 }
69
70 $output_dir = dirname( $output_path );
71 if ( ! file_exists( $output_dir ) ) {
72 // @TODO: think through the chmod of the created directory.
73 mkdir( $output_dir, 0777, true );
74 }
75
76 $protocol = parse_url( $url, PHP_URL_SCHEME );
77 if ( null === $protocol ) {
78 return false;
79 }
80
81 switch ( $protocol ) {
82 case 'file':
83 if ( ! $this->source_from_filesystem ) {
84 _doing_it_wrong(
85 __METHOD__,
86 'Cannot process file:// URLs without a source filesystem instance. Use the source_from_filesystem option to pass in a filesystem instance to WP_Attachment_Downloader.',
87 '1.0'
88 );
89
90 return false;
91 }
92 $source_path = parse_url( $url, PHP_URL_PATH );
93 if ( false === $source_path ) {
94 return false;
95 }
96
97 // Just copy the file over.
98 // @TODO: think through the chmod of the created file.
99 $stream = null;
100 try {
101 $stream = $this->source_from_filesystem->open_read_stream( $source_path );
102 $fp = fopen( $output_path, 'wb' );
103 while ( ! $stream->reached_end_of_data() ) {
104 $pulled = $stream->pull( 65536 );
105 $chunk = $stream->consume( $pulled );
106 fwrite( $fp, $chunk );
107 }
108 fclose( $fp );
109
110 $this->pending_events[] = new AttachmentDownloaderEvent(
111 $this->enqueued_url,
112 AttachmentDownloaderEvent::SUCCESS
113 );
114 } catch ( Exception $e ) {
115 $this->pending_events[] = new AttachmentDownloaderEvent(
116 $this->enqueued_url,
117 AttachmentDownloaderEvent::FAILURE,
118 'copy_failed'
119 );
120 } finally {
121 if ( $stream ) {
122 $stream->close_reading();
123 }
124 }
125
126 return true;
127 case 'http':
128 case 'https':
129 // Create a placeholder file to indicate that the download is in progress.
130 touch( $output_path . '.partial' );
131 $request = new Request( $url );
132 $this->output_paths[ $request->id ] = $output_path;
133 $this->progress[ $this->enqueued_url ] = array(
134 'received' => null,
135 'total' => null,
136 );
137 $this->client->enqueue( $request );
138
139 return true;
140 }
141
142 return false;
143 }
144
145 public function get_enqueued_url() {
146 return $this->enqueued_url;
147 }
148
149 public function queue_full() {
150 return count( $this->client->get_active_requests() ) >= 10;
151 }
152
153 /**
154 * Returns and clears all pending events.
155 *
156 * @return AttachmentDownloaderEvent[]
157 */
158 public function get_events() {
159 $events = $this->pending_events;
160 $this->pending_events = array();
161 return $events;
162 }
163
164 public function poll() {
165 while ( $this->client->await_next_event() ) {
166 $event = $this->client->get_event();
167 $request = $this->client->get_request();
168 if ( Client::EVENT_FAILED === $event ) {
169 $this->on_failure( $request->url, $request->id, $request->error );
170 return true;
171 }
172
173 // Only process responses this was the last request in the chain.
174 if ( $request->is_redirected() ) {
175 continue;
176 }
177
178 // The request object we get from the client may be a redirect
179 // Let's keep referring to the original request.
180 $original_url = $request->original_request()->url;
181 $original_request_id = $request->original_request()->id;
182
183 /**
184 * @TODO: Whenever we get a redirect to a URL we've already processed,
185 * stop and emit a success event.
186 */
187 switch ( $event ) {
188 case Client::EVENT_GOT_HEADERS:
189 if ( file_exists( $this->output_paths[ $original_request_id ] . '.partial' ) ) {
190 unlink( $this->output_paths[ $original_request_id ] . '.partial' );
191 }
192 echo $this->output_paths[ $original_request_id ] . "\n";
193 $fp = fopen( $this->output_paths[ $original_request_id ] . '.partial', 'wb' );
194 if ( false !== $fp ) {
195 $this->fps[ $original_request_id ] = $fp;
196 $this->progress[ $original_url ]['received'] = 0;
197 if ( $request->response->get_header( 'Content-Length' ) ) {
198 $this->progress[ $original_url ]['total'] = $request->response->get_header( 'Content-Length' );
199 }
200 }
201 break;
202 case Client::EVENT_BODY_CHUNK_AVAILABLE:
203 $chunk = $this->client->get_response_body_chunk();
204 if ( ! fwrite( $this->fps[ $original_request_id ], $chunk ) ) {
205 // @TODO: Don't echo the error message. Attach it to the import session instead for the user to review later on.
206 _doing_it_wrong(
207 __METHOD__,
208 sprintf( 'Failed to write to file: %s', $this->output_paths[ $original_request_id ] ),
209 '1.0'
210 );
211 }
212 $this->progress[ $original_url ]['received'] += strlen( $chunk );
213 break;
214 case Client::EVENT_FINISHED:
215 if ( $request->response->ok() ) {
216 $this->on_success( $original_url, $original_request_id );
217 } else {
218 $this->on_failure( $original_url, $original_request_id, 'http_error_' . $request->response->status_code );
219 }
220 break;
221 }
222
223 return true;
224 }
225
226 return false;
227 }
228
229 private function on_failure( $original_url, $original_request_id, $error = null ) {
230 if ( isset( $this->fps[ $original_request_id ] ) ) {
231 if ( is_resource( $this->fps[ $original_request_id ] ) ) {
232 fclose( $this->fps[ $original_request_id ] );
233 }
234 unset( $this->fps[ $original_request_id ] );
235 }
236 if ( isset( $this->output_paths[ $original_request_id ] ) ) {
237 $partial_file = $this->output_paths[ $original_request_id ] . '.partial';
238 if ( file_exists( $partial_file ) ) {
239 unlink( $partial_file );
240 }
241 }
242 $this->pending_events[] = new AttachmentDownloaderEvent(
243 $original_url,
244 AttachmentDownloaderEvent::FAILURE,
245 $error
246 );
247 unset( $this->progress[ $original_url ] );
248 unset( $this->output_paths[ $original_request_id ] );
249 }
250
251 private function on_success( $original_url, $original_request_id ) {
252 // Only clean up if this was the last request in the chain.
253 if ( isset( $this->fps[ $original_request_id ] ) ) {
254 if ( is_resource( $this->fps[ $original_request_id ] ) ) {
255 fclose( $this->fps[ $original_request_id ] );
256 }
257 unset( $this->fps[ $original_request_id ] );
258 }
259 if ( isset( $this->output_paths[ $original_request_id ] ) ) {
260 if ( false === rename(
261 $this->output_paths[ $original_request_id ] . '.partial',
262 $this->output_paths[ $original_request_id ]
263 ) ) {
264 // @TODO: Log an error.
265 }
266 }
267 $this->pending_events[] = new AttachmentDownloaderEvent(
268 $original_url,
269 AttachmentDownloaderEvent::SUCCESS
270 );
271 unset( $this->progress[ $original_url ] );
272 unset( $this->output_paths[ $original_request_id ] );
273 }
274
275 public function __destruct() {
276 // Ensure any remaining open file descriptors are closed.
277 foreach ( $this->fps as $request_id => $fp ) {
278 if ( is_resource( $fp ) ) {
279 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
280 @fclose( $fp );
281 }
282 unset( $this->fps[ $request_id ] );
283 }
284 }
285 }
286