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-dfrapi-image-uploader.php

class-dfrapi-image-uploader.php in Datafeedr API 1.4.3, at classes/class-dfrapi-image-uploader.php

298 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 defined( 'ABSPATH' ) || exit;
4
5
6 class Dfrapi_Image_Uploader {
7
8 /**
9 * @var Dfrapi_Image_Data $image_data
10 */
11 public $image_data;
12
13 /**
14 * @var int $timeout The amount of time in seconds to wait for an image to be downloaded.
15 */
16 public $timeout = 5;
17
18 /**
19 * @var string $mime_type The image's mime type.
20 */
21 public $mime_type;
22
23 /**
24 * @var string $mime_type The image's extension.
25 */
26 public $extension;
27
28 /**
29 * Dfrapi_Image_Uploader constructor.
30 *
31 * @param Dfrapi_Image_Data $image_data
32 */
33 public function __construct( Dfrapi_Image_Data $image_data ) {
34 $this->image_data = $image_data;
35 }
36
37 /**
38 * Uploads an image from the given $this->image_url and return its Attachment ID on success
39 * or a WP_Error on failure.
40 *
41 * @return int|WP_Error The ID of the attachment or a WP_Error on failure.
42 */
43 public function upload() {
44
45 require_once( ABSPATH . 'wp-includes/pluggable.php' );
46 require_once( ABSPATH . 'wp-admin/includes/media.php' );
47 require_once( ABSPATH . 'wp-admin/includes/file.php' );
48 require_once( ABSPATH . 'wp-admin/includes/image.php' );
49
50 $attachment_id = $this->sideload_image();
51
52 // If sideload returned a WP_Error, add some extra data to the error object and return it.
53 if ( is_wp_error( $attachment_id ) ) {
54 $attachment_id->add_data( $this->image_data->get_image_url(), 'image_url' );
55 $attachment_id->add_data( $this->image_data->get_post_parent_id(), 'post_parent' );
56 $attachment_id->add_data( $this->image_data->get_title(), 'post_title' );
57
58 return $attachment_id;
59 }
60
61 // Maybe set as post thumbnail.
62 if ( $this->image_data->is_post_thumbnail() ) {
63 set_post_thumbnail( $this->image_data->get_post_parent_id(), $attachment_id );
64 }
65
66 // Maybe update alternative text field.
67 if ( $this->image_data->get_alternative_text() ) {
68 update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $this->image_data->get_alternative_text() ) );
69 }
70
71 return $attachment_id;
72 }
73
74 /**
75 * Upload the image and if successful, import into Media Library.
76 *
77 * This method will make 2 or 3 attempts to download the image.
78 *
79 * First Attempt: Try to download the image with the standard HTTP requests args.
80 * Second Attempt: Try to download the image with the modified HTTP requests args.
81 * Third Attempt: If Jetpack is activated, try to download the image using a Jetpack Photon URL.
82 *
83 * If all 3 attempts fail, return a WP_Error.
84 *
85 * @return int|WP_Error The ID of the attachment or a WP_Error on failure.
86 */
87 private function sideload_image() {
88
89 // This is our first attempt to download the image (without modified HTTP request args).
90 $tmp_name = $this->download_url( $this->image_data->get_image_url() );
91
92 // If our first attempt to download the image URL fails, try again with $with_request_filters set to true.
93 if ( is_wp_error( $tmp_name ) ) {
94 $tmp_name = $this->download_url( $this->image_data->get_image_url(), true );
95 }
96
97 // This is our last attempt to download image using a Jetpack Photon URL (if Jetpack is active).
98 if ( is_wp_error( $tmp_name ) ) {
99 $photon_image_url = dfrapi_jetpack_photon_url( $this->image_data->get_image_url() );
100
101 // Make sure the photon image URL is different than the URL of the image we are attempting to download.
102 if ( $photon_image_url !== $this->image_data->get_image_url() ) {
103 $tmp_name = $this->download_url( $photon_image_url );
104 }
105 }
106
107 // If $tmp_name is still a WP_Error then bail, for some reason we can't download this image.
108 if ( is_wp_error( $tmp_name ) ) {
109 return $tmp_name;
110 }
111
112 $mime_type = wp_get_image_mime( $tmp_name );
113
114 if ( ! $mime_type ) {
115 $this->unlink_tmp_file( $tmp_name );
116
117 return new WP_Error(
118 'mime_type_indeterminable',
119 __( 'The true mime type cannot be determined for this image.', 'datafeedr-api' ),
120 [
121 '$image_url' => $this->image_data->get_image_url(),
122 '$post_parent' => $this->image_data->get_post_parent_id(),
123 ]
124 );
125 }
126
127 if ( ! array_key_exists( $mime_type, $this->mime_types_and_extensions() ) ) {
128 $this->unlink_tmp_file( $tmp_name );
129
130 return new WP_Error(
131 'mime_type_invalid',
132 sprintf( __( 'The mime type "%s" is not a valid mime type for an image.', 'datafeedr-api' ), esc_html( $mime_type ) ),
133 [
134 '$image_url' => $this->image_data->get_image_url(),
135 '$post_parent' => $this->image_data->get_post_parent_id(),
136 ]
137 );
138 }
139
140 $this->mime_type = $mime_type;
141 $this->extension = $this->mime_types_and_extensions()[ $mime_type ];
142
143 add_filter( 'wp_check_filetype_and_ext', [ $this, 'set_missing_extension_or_mime_type' ] );
144
145 $attachment_id = media_handle_sideload(
146 [
147 'name' => rtrim( substr( $this->image_data->get_filename(), 0, 150 ), '.' ) . '.' . $this->extension,
148 'tmp_name' => $tmp_name,
149 ],
150 $this->image_data->get_post_parent_id(),
151 $this->image_data->get_title(),
152 $this->image_data->get_post_array()
153 );
154
155 $this->unlink_tmp_file( $tmp_name );
156
157 remove_filter( 'wp_check_filetype_and_ext', [ $this, 'set_missing_extension_or_mime_type' ] );
158
159 return $attachment_id;
160 }
161
162 /**
163 * This is a wrapper for the download_url() function. If $with_request_filters is enabled, that
164 * means we will add filters to the "http_request_args" in order to change the request
165 * with the hopes of bypassing any access controls (403) permission issues.
166 *
167 * @param string $image_url The image URL to download.
168 * @param false $with_request_filters Set to true to modify HTTP request args before request.
169 *
170 * @return string|WP_Error The name of the temporary file or WP_Error on failure.
171 */
172 public function download_url( string $image_url, $with_request_filters = false ) {
173
174 add_filter( 'wp_unique_filename', [ $this, 'force_filename_length_limit' ] );
175
176 if ( $with_request_filters ) {
177 add_filter( 'http_request_args', [ $this, 'modify_http_request_args' ], 10, 2 );
178 }
179
180 $tmp_name = download_url( $image_url, $this->timeout );
181
182 if ( $with_request_filters ) {
183 remove_filter( 'http_request_args', [ $this, 'modify_http_request_args' ] );
184 }
185
186 remove_filter( 'wp_unique_filename', [ $this, 'force_filename_length_limit' ] );
187
188 return $tmp_name;
189 }
190
191 /**
192 * Modify the arguments sent in the HTTP request to download the image.
193 *
194 * These are used in the WP_Http::request() method which is called by the download_url() function.
195 *
196 * @param array $default_args
197 * @param string $url
198 *
199 * @return array
200 */
201 public function modify_http_request_args( array $default_args, string $url ) {
202
203 $custom_args = [
204 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0',
205 'stream' => true,
206 ];
207
208 $custom_args = apply_filters(
209 'dfrapi_image_uploader_custom_http_request_args',
210 $custom_args,
211 $default_args,
212 $url,
213 $this
214 );
215
216 return array_merge( $default_args, $custom_args );
217 }
218
219 /**
220 * Sets the image's extension or mime type if either are missing.
221 *
222 * This is required for processing image URLs which aren't formatted with a traditional
223 * extension such as .jpg or .png. For example, if the image URL uses a query string or is
224 * just a slug. In these cases, WordPress's wp_check_filetype() function will fail to
225 * get the uploaded image's extension and mime type which forces the _wp_handle_upload()
226 * (which is ultimately called by media_handle_sideload()) function to fail and return
227 * a 'Sorry, this file type is not permitted for security reasons.' WP_Error.
228 *
229 * @param array $params
230 *
231 * @return array
232 */
233 public function set_missing_extension_or_mime_type( $params ) {
234
235 if ( ! empty( $params['ext'] ) && ! empty( $params['type'] ) ) {
236 return $params;
237 }
238
239 $params['ext'] = empty( $params['ext'] ) ? $this->extension : $params['ext'];
240 $params['type'] = empty( $params['type'] ) ? $this->mime_type : $params['type'];
241
242 return $params;
243 }
244
245 /**
246 * Set the amount of time to process download_url() function.
247 *
248 * @param int $timeout
249 */
250 public function set_timeout( $timeout = 5 ) {
251 $this->timeout = absint( $timeout );
252 }
253
254 /**
255 * An array of image mime types and their respective extensions.
256 *
257 * @return array
258 */
259 private function mime_types_and_extensions() {
260 return apply_filters( 'dfrapi_image_uploader_mime_types_and_extensions', [
261 'image/jpeg' => 'jpg',
262 'image/jpg' => 'jpg',
263 'image/jpe' => 'jpg',
264 'image/png' => 'png',
265 'image/gif' => 'gif',
266 'image/bmp' => 'bmp',
267 'image/tiff' => 'tif',
268 'image/webp' => 'webp',
269 'image/x-icon' => 'ico',
270 'image/heic' => 'heic',
271 ], $this );
272 }
273
274 /**
275 * Safely unlink a temporary file.
276 *
277 * @param string $tmp_name
278 */
279 public function unlink_tmp_file( string $tmp_name ) {
280 @unlink( $tmp_name );
281 }
282
283 /**
284 * When filenames are longer than 255 characters, this causes a "failed to open stream: File name too long" error.
285 * This filter function shortens the unique name generated by WordPress before the file is stored in the
286 * temporary directory.
287 *
288 * @param string $filename
289 *
290 * @return string
291 */
292 public function force_filename_length_limit( string $filename ) {
293 $length = absint( apply_filters( 'dfrapi_image_uploader_filename_max_length', 200, $this ) );
294
295 return substr( $filename, 0, $length );
296 }
297 }
298