PluginProbe
Datafeedr API / 1.4.3
Datafeedr API v1.4.3
1.4.3 1.4.2 1.0.125 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 1.0.22 1.0.23 1.0.24 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.3 1.0.30 1.0.31 1.0.32 All 182 releases
datafeedr-api / classes / class-datafeedr-image-importer.php

class-datafeedr-image-importer.php in Datafeedr API 1.4.3, at classes/class-datafeedr-image-importer.php

779 lines 18.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Class Datafeedr_Image_Importer.
5 *
6 * Handles importing images from URLs and assigning image data.
7 *
8 * Usage:
9 *
10 * $url = 'http://via.placeholder.com/350x150';
11 *
12 * $args = array(
13 * 'title' => 'My Image Name',
14 * 'file_name' => 'my-image-name',
15 * 'post_id' => 123,
16 * 'description' => 'Description of this image.',
17 * 'caption' => 'Caption for this image.',
18 * 'alt_text' => 'Alt text for this image.',
19 * 'user_id' => 1,
20 * 'is_post_thumbnail' => true,
21 * 'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0',
22 * 'timeout' => 30,
23 * );
24 *
25 * $image = new Datafeedr_Image_Importer( $url, $args );
26 *
27 * $attachment_id = $image->import();
28 *
29 * if ( is_wp_error( $attachment_id ) ) {
30 * // Handle error.
31 * } else {
32 * // Success! Do something with $attachment_id.
33 * }
34 *
35 * @since 1.0.71
36 */
37 class Datafeedr_Image_Importer {
38
39 use Datafeedr_Timer;
40
41 /**
42 * Image URL.
43 *
44 * @since 1.0.71
45 * @access protected
46 * @var string $url
47 */
48 protected $url;
49
50 /**
51 * An array of arguments for storing image in database.
52 *
53 * @see $this->default_args()
54 * @since 1.0.71
55 * @access protected
56 * @var array $args
57 */
58 protected $args;
59
60 /**
61 * The response of wp_remote_get( $this->url ) or WP_Error on failure.
62 *
63 * @since 1.0.71
64 * @access protected
65 * @var WP_Error|array $response
66 */
67 protected $response;
68
69 /**
70 * The ID created for the new attachment.
71 *
72 * @since 1.0.71
73 * @access protected
74 * @var integer $attachment_id
75 */
76 protected $attachment_id;
77
78 /**
79 * A copy of the WP_Error encountered during import.
80 *
81 * @since 1.0.74
82 * @access protected
83 * @var WP_Error $wp_error
84 */
85 protected $wp_error = null;
86
87 /**
88 * Datafeedr_Image_Importer constructor.
89 *
90 * Sets the $this->url and $this->args properties.
91 *
92 * @param string $url Image URL.
93 * @param array $args Optional. See $this->default_args().
94 *
95 * @since 1.0.71
96 */
97 public function __construct( $url, $args = array() ) {
98 $this->set_url( $url );
99 $this->set_args( $args );
100 }
101
102 /**
103 * Sets the $this->url property.
104 *
105 * @param string $url Image URL.
106 *
107 * @since 1.0.71
108 */
109 protected function set_url( $url ) {
110 $url = ( "//" === substr( $url, 0, 2 ) ) ? 'http:' . $url : $url;
111 $this->url = apply_filters( 'datafeedr_image_importer/url', $url, $this );
112 }
113
114 /**
115 * Sets the $this->args property.
116 *
117 * This merges the $defaults with the values passed into the constructor.
118 *
119 * @param array $args
120 *
121 * @since 1.0.71
122 */
123 protected function set_args( $args ) {
124 $args = wp_parse_args( $args, $this->default_args() );
125 $this->args = apply_filters( 'datafeedr_image_importer/args', $args, $this );
126 }
127
128 /**
129 * Default args to use for importing image.
130 *
131 * @return array
132 * @since 1.0.71
133 */
134 protected function default_args() {
135
136 $args = array(
137
138 /**
139 * This is the ID of the post we want to attach this image to. If we do not
140 * want this image to be attached to a post, leave this set to 0.
141 */
142 'post_id' => 0,
143
144 /**
145 * This is name of the file name the image will have once it is stored on
146 * on the server in the WordPress uploads directory.
147 */
148 'file_name' => preg_replace( '/\.[^.]+$/', '', basename( $this->url ) ),
149
150 /**
151 * This is the ID of the User this image will be associated with.
152 */
153 'user_id' => $this->get_admin_user_id(),
154
155 /**
156 * This is the title of the image (which is different than the file name).
157 */
158 'title' => preg_replace( '/\.[^.]+$/', '', basename( $this->url ) ),
159
160 /**
161 * The description of the image.
162 */
163 'description' => '',
164
165 /**
166 * The caption for the image.
167 */
168 'caption' => '',
169
170 /**
171 * The alt text. Text to display if image cannot be loaded.
172 */
173 'alt_text' => '',
174
175 /**
176 * Whether this image should be set as the post's thumbnail. If the post_id
177 * is 0, this setting will be ignored.
178 */
179 'is_post_thumbnail' => false,
180
181 /**
182 * The user-agent to use when making requests to the external URL.
183 */
184 'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0',
185
186 /**
187 * The number of seconds to spend attempting to download the image.
188 */
189 'timeout' => 10,
190 );
191
192 return $args;
193 }
194
195 /**
196 * Returns the image URL.
197 *
198 * @return string
199 * @since 1.0.71
200 */
201 public function url() {
202 return $this->url;
203 }
204
205 /**
206 * Returns the image's attachment ID.
207 *
208 * @return integer
209 * @since 1.0.71
210 */
211 public function attachment_id() {
212 return absint( $this->attachment_id );
213 }
214
215 /**
216 * Returns the response of wp_remote_get($this->url) or WP_Error on failure.
217 *
218 * @return array|WP_Error
219 * @since 1.0.71
220 */
221 public function response() {
222 return $this->response;
223 }
224
225 /**
226 * Returns a WP_Error encountered during the import() process.
227 *
228 * @return WP_Error|null
229 * @since 1.0.74
230 */
231 public function wp_error() {
232 return $this->wp_error;
233 }
234
235 /**
236 * Returns all args or a value of a specific arg.
237 *
238 * @param bool|string $key
239 * @param string $cast Possible values: boolean, integer, float, string, array, object or null. See settype().
240 *
241 * @return array|mixed Return all $args if $key doesn't exists or a single arg if $key isset.
242 * @since 1.0.74
243 */
244 public function args( $key = false, $cast = 'string' ) {
245
246 $args = $this->args;
247
248 if ( $key && isset( $args[ $key ] ) ) {
249 $value = $args[ $key ];
250 settype( $value, $cast );
251
252 return $value;
253 }
254
255 return $args;
256 }
257
258 /**
259 * Returns true if errors were encountered. Otherwise returns false.
260 *
261 * @return bool
262 * @since 1.0.74
263 */
264 public function has_error() {
265 return ( is_wp_error( $this->wp_error() ) ) ? true : false;
266 }
267
268 /**
269 * This handles the importing of an image into WordPress.
270 *
271 * This performs the following actions:
272 * - Requires necessary WordPress media.php, $file.php and image.php files
273 * - Performs a wp_remote_get() request to the image URL.
274 * - Extracts the image extension (.jpg, .png, etc...) for the image.
275 * - Downloads the image file and stores a temporary copy.
276 * - Adds the image to the WordPress uploads directory and saves a record to the posts table.
277 * - Sets the $this->attachment_id property
278 * - Maybe sets the image as the post's featured thumbnail.
279 * - Maybe sets the image's alt text.
280 *
281 * If the import encounters an error, a WP_Error will be added to the $this->errors array.
282 *
283 * @return Datafeedr_Image_Importer
284 * @since 1.0.71
285 */
286 public function import() {
287
288 $this->start_timer();
289
290 $this->require_files();
291
292 $tmp_name = $this->tmp_name();
293
294 if ( is_wp_error( $tmp_name ) ) {
295
296 $this->wp_error = $tmp_name;
297
298 /**
299 * Do something when image import fails.
300 *
301 * @param WP_Error $tmp_name
302 * @param Datafeedr_Image_Importer $this
303 *
304 * @since 1.0.72
305 *
306 */
307 do_action( 'datafeedr_image_importer/import/fail', $tmp_name, $this );
308
309 $this->stop_timer();
310
311 return $this;
312 }
313
314 $content_type = $this->response_header( $this->response(), 'content-type' );
315
316 $extension = $this->content_type_to_extension( $content_type );
317
318 $file_array = array();
319 $file_array['name'] = $this->args( 'file_name' ) . '.' . $extension;
320 $file_array['tmp_name'] = $tmp_name;
321
322 if ( is_wp_error( $file_array['tmp_name'] ) ) {
323
324 $this->wp_error = $file_array['tmp_name'];
325
326 /**
327 * Do something when image import fails.
328 *
329 * @param WP_Error $file_array ['tmp_name']
330 * @param Datafeedr_Image_Importer $this
331 *
332 * @since 1.0.72
333 *
334 */
335 do_action( 'datafeedr_image_importer/import/fail', $file_array['tmp_name'], $this );
336
337 $this->stop_timer();
338
339 return $this;
340 }
341
342 $result = $this->media_sideload_image(
343 $file_array,
344 $this->args( 'post_id', 'integer' ),
345 $this->args( 'title' ),
346 $this->post_data()
347 );
348
349 if ( is_wp_error( $result ) ) {
350
351 $this->wp_error = $result;
352
353 $this->unlink_tmp_file( $file_array['tmp_name'] );
354
355 /**
356 * Do something when image import fails.
357 *
358 * @param WP_Error $result
359 * @param Datafeedr_Image_Importer $this
360 *
361 * @since 1.0.72
362 *
363 */
364 do_action( 'datafeedr_image_importer/import/fail', $result, $this );
365
366 $this->stop_timer();
367
368 return $this;
369 }
370
371 $this->set_attachment_id( $result );
372 $this->set_post_thumbnail();
373 $this->set_alt_text();
374 $this->set_owner_source();
375
376 /**
377 * Do something when image import succeeds.
378 *
379 * @param int $result Attachment ID.
380 * @param Datafeedr_Image_Importer $this
381 *
382 * @since 1.0.72
383 */
384 do_action( 'datafeedr_image_importer/import/success', $result, $this );
385
386 $this->stop_timer();
387
388 return $this;
389 }
390
391 /**
392 * Returns an array of Post data to use in the call to media_sideload_image().
393 *
394 * This sets the image description, caption and user_id.
395 *
396 * @return array Post data.
397 * @since 1.0.71
398 */
399 protected function post_data() {
400
401 $post_data = array(
402 'post_content' => $this->args( 'description' ),
403 'post_excerpt' => $this->args( 'caption' ),
404 'post_author' => $this->args( 'user_id', 'integer' ),
405 );
406
407 /**
408 * Allow user to override post data.
409 *
410 * @param array $post_data
411 * @param Datafeedr_Image_Importer $this
412 *
413 * @since 1.0.72
414 *
415 */
416 return apply_filters( 'datafeedr_image_importer/post_data', $post_data, $this );
417 }
418
419 /**
420 * Sets the image's alt text if alt text was provided.
421 *
422 * @since 1.0.71
423 */
424 protected function set_alt_text() {
425 $text = $this->args( 'alt_text' );
426 if ( ! empty( $text ) ) {
427 update_post_meta( $this->attachment_id(), '_wp_attachment_image_alt', $text );
428 }
429 }
430
431 /**
432 * Sets the image's owner and source. _owner_datafeedr : dfrps
433 *
434 * @since 1.0.101
435 */
436 protected function set_owner_source() {
437 $source = isset( $this->args['_source_plugin'] ) ? trim( $this->args['_source_plugin'] ) : '';
438 update_post_meta( $this->attachment_id(), '_owner_datafeedr', $source );
439 }
440
441 /**
442 * Sets the image as the post thumbnail if the is_post_thumbnail argument is set to
443 * true and the post_id is greater than 0.
444 *
445 * @since 1.0.71
446 */
447 protected function set_post_thumbnail() {
448
449 if ( ! $this->args( 'is_post_thumbnail', 'boolean' ) ) {
450 return;
451 }
452
453 if ( $this->args( 'post_id', 'integer' ) < 1 ) {
454 return;
455 }
456
457 set_post_thumbnail( $this->args( 'post_id', 'integer' ), $this->attachment_id() );
458 }
459
460 /**
461 * Requires the necessary files for importing an image into WordPress.
462 *
463 * @since 1.0.71
464 */
465 protected function require_files() {
466 require_once( ABSPATH . 'wp-admin/includes/media.php' );
467 require_once( ABSPATH . 'wp-admin/includes/file.php' );
468 require_once( ABSPATH . 'wp-admin/includes/image.php' );
469 }
470
471 /**
472 * A wrapper for the media_handle_sideload() function.
473 *
474 * @param array $file_array
475 * @param int $post_id
476 * @param string $title
477 * @param array $post_data
478 *
479 * @return int|WP_Error The ID of the attachment or a WP_Error on failure.
480 * @see media_handle_sideload()
481 *
482 * @since 1.0.71
483 */
484 protected function media_sideload_image( $file_array, $post_id = 0, $title = '', $post_data = array() ) {
485
486 $attachment_id = media_handle_sideload( $file_array, $post_id, $title, $post_data );
487
488 // If there was an error sideloading the image, return the returned WP_Error.
489 if ( is_wp_error( $attachment_id ) ) {
490 return $attachment_id;
491 }
492
493 $attachment_id = absint( $attachment_id );
494
495 if ( $attachment_id === 0 ) {
496 return new WP_Error( 'dfrapi_attachment_id_is_zero', __( 'Attachment ID is zero.', 'datafeedr' ) );
497 }
498
499 /**
500 * Get attachment metadata just to confirm an image
501 * was uploaded to the server properly.
502 */
503 $meta = wp_get_attachment_metadata( $attachment_id );
504
505 if ( ! $meta ) {
506 return new WP_Error( 'dfrapi_wp_get_attachment_metadata_returned_false', __( 'wp_get_attachment_metadata() returned false.', 'datafeedr' ) );
507 }
508
509 $width = isset( $meta['width'] ) ? absint( $meta['width'] ) : 0;
510 $height = isset( $meta['width'] ) ? absint( $meta['height'] ) : 0;
511
512 // If the image width is 0, return WP_Error.
513 if ( $width === 0 ) {
514 return new WP_Error( 'dfrapi_attachment_width_is_zero', __( 'Attachment width is zero.', 'datafeedr' ) );
515 }
516
517 // If the image height is 0, return WP_Error.
518 if ( $height === 0 ) {
519 return new WP_Error( 'dfrapi_attachment_height_is_zero', __( 'Attachment height is zero.', 'datafeedr' ) );
520 }
521
522 // If the image is missing a file, return WP_Error.
523 if ( ! isset( $meta['file'] ) || empty( $meta['file'] ) ) {
524 return new WP_Error( 'dfrapi_attachment_has_no_file', __( 'Attachment has no file.', 'datafeedr' ) );
525 }
526
527 // If we made it this far, the image is valid so return the image's attachment ID.
528 return $attachment_id;
529 }
530
531 /**
532 * This is a modified version of download_url() from /wp-admin/includes/file.php
533 *
534 * We had to modify so we could change the user-agent.
535 *
536 * Downloads a URL to a local temporary file using the WordPress HTTP Class.
537 * Please note, That the calling function must unlink() the file.
538 *
539 * This also sets the $this->response property.
540 *
541 * @return WP_Error|string WP_Error on failure, string temporary filename on success.
542 * @since 1.0.72
543 *
544 * @see /wp-admin/includes/file.php:965 function download_url( $url, $timeout = 300 )
545 */
546 protected function tmp_name() {
547
548 $url = $this->url();
549
550 if ( ! $url ) {
551 return new WP_Error( 'http_no_url', __( 'Invalid URL Provided.' ) );
552 }
553
554 $url_filename = basename( parse_url( $url, PHP_URL_PATH ) );
555
556 $url_filename = substr( $url_filename, 0, 100 );
557
558 $tmp_name = wp_tempnam( $url_filename );
559
560 if ( ! $tmp_name ) {
561 return new WP_Error( 'http_no_file', __( 'Could not create Temporary file.' ) );
562 }
563
564 $this->set_response( $tmp_name );
565
566 if ( is_wp_error( $this->response() ) ) {
567 $this->unlink_tmp_file( $tmp_name );
568
569 return $this->response();
570 }
571
572 $response_code = $this->response_code( $this->response() );
573
574 if ( 200 != $response_code ) {
575 $this->unlink_tmp_file( $tmp_name );
576
577 return new WP_Error(
578 'http_' . $response_code,
579 __( 'Response code was something other than 200.', 'datafeedr' )
580 );
581 }
582
583 $content_md5 = $this->response_header( $this->response(), 'content-md5' );
584
585 if ( $content_md5 ) {
586 $md5_check = verify_file_md5( $tmp_name, $content_md5 );
587 if ( is_wp_error( $md5_check ) ) {
588 $this->unlink_tmp_file( $tmp_name );
589
590 return $md5_check;
591 }
592 }
593
594 return $tmp_name;
595 }
596
597 /**
598 * Sets the $this->attachment_id property.
599 *
600 * @param integer $id The ID to set the attachment_id to.
601 *
602 * @since 1.0.71
603 */
604 protected function set_attachment_id( $id ) {
605 $this->attachment_id = absint( $id );
606 }
607
608 /**
609 * Safely unlink a temporary file.
610 *
611 * @param string $file_name Name of temporary file to unlink.
612 *
613 * @since 1.0.72
614 */
615 protected function unlink_tmp_file( $file_name ) {
616 @unlink( $file_name );
617 }
618
619 /**
620 * Sets the $this->response property.
621 *
622 * Makes a call to wp_remote_get() and sets the $this->response property to the
623 * returned value.
624 *
625 * @param string $tmp_name Temporary file name generated by wp_tempnam().
626 *
627 * @since 1.0.71
628 */
629 protected function set_response( $tmp_name ) {
630
631 $args = array(
632 'user-agent' => $this->args( 'user_agent' ),
633 'timeout' => $this->args( 'timeout', 'integer' ),
634 'filename' => $tmp_name,
635 'stream' => true,
636 );
637
638 /**
639 * Allow user to override $args.
640 *
641 * @param array $args
642 * @param Datafeedr_Image_Importer $this
643 *
644 * @since 1.0.72
645 *
646 */
647 $args = apply_filters( 'datafeedr_image_importer/set_response/args', $args, $this );
648
649 $this->response = wp_safe_remote_get( $this->url, $args );
650 }
651
652 /**
653 * Retrieve a single header by name from the raw response.
654 *
655 * @param $response WP_Error|array The response or WP_Error on failure.
656 * @param string $header Header name to retrieve value from.
657 *
658 * @return string The header value. Empty string on if incorrect parameter given, or if the header doesn't exist.
659 * @since 1.0.72
660 */
661 protected function response_header( $response, $header ) {
662 return wp_remote_retrieve_header( $response, $header );
663 }
664
665 /**
666 * Retrieve only the response message from the raw response.
667 *
668 * Will return an empty array if incorrect parameter value is given.
669 *
670 * @param $response WP_Error|array The response or WP_Error on failure.
671 *
672 * @return string The response message. Empty string on incorrect parameter given.
673 * @since 1.0.72
674 */
675 protected function response_message( $response ) {
676 return wp_remote_retrieve_response_message( $response );
677 }
678
679 /**
680 * Retrieve only the response code from the raw response.
681 *
682 * Will return an empty array if incorrect parameter value is given.
683 *
684 * @param $response WP_Error|array The response or WP_Error on failure.
685 *
686 * @return int|string The response code as an integer. Empty string on incorrect parameter given.
687 * @since 1.0.72
688 */
689 protected function response_code( $response ) {
690 return wp_remote_retrieve_response_code( $response );
691 }
692
693 /**
694 * This returns the proper image extension given the $content_type requested.
695 *
696 * Handle content types of values like these:
697 * "image/png"
698 * "image/jpeg;charset=UTF-8"
699 * "JPG"
700 *
701 * @param string $content_type Content type received from HTTP request.
702 *
703 * @return string Extension (ex. jpg, png, gif, etc...).
704 * @since 1.0.71
705 */
706 protected function content_type_to_extension( $content_type ) {
707
708 $type = explode( ";", $content_type );
709 $type = trim( strtolower( $type[0] ) );
710
711 $extensions = $this->extensions();
712
713 foreach ( $extensions as $mime => $ext ) {
714 if ( $mime == $type ) {
715 return $ext;
716 }
717 }
718
719 return 'jpg';
720 }
721
722 /**
723 * Returns an associative array of valid content types (or mime types) and
724 * their respective extensions.
725 *
726 * @return array
727 * @since 1.0.71
728 */
729 protected function extensions() {
730
731 $extensions = array(
732 'image/jpeg' => 'jpg',
733 'image/jpg' => 'jpg',
734 'image/jpe' => 'jpg',
735 'image/png' => 'png',
736 'image/gif' => 'gif',
737 'image/bmp' => 'bmp',
738 'image/tiff' => 'tif',
739 'image/webp' => 'webp',
740 'jpg' => 'jpg',
741 'png' => 'png',
742 'gif' => 'gif',
743 );
744
745 /**
746 * Allow user to override $extensions.
747 *
748 * @param array $extensions
749 * @param Datafeedr_Image_Importer $this
750 *
751 * @since 1.0.72
752 *
753 */
754 return apply_filters( 'datafeedr_image_importer/extensions', $extensions, $this );
755 }
756
757 /**
758 * Returns the first found ID of an admin user.
759 *
760 * @return int
761 * @since 1.0.71
762 */
763 protected function get_admin_user_id() {
764
765 $args = array(
766 'blog_id' => $GLOBALS['blog_id'],
767 'role__in' => array( 'administrator' ),
768 'orderby' => 'ID',
769 'order' => 'ASC',
770 'number' => 1,
771 );
772
773 $users = get_users( $args );
774 $user = $users[0];
775
776 return absint( $user->ID );
777 }
778 }
779