PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.4.14
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.4.14
1.4.14 1.4.13 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.54 1.3.53 1.3.52 1.3.51 1.3.50 1.3.49 1.3.48 1.3.47 1.3.46 1.3.45 All 177 releases
disco / app / Upload / ImageUpload.php

ImageUpload.php in Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO 1.4.14, at app/Upload/ImageUpload.php

364 lines 9.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php //phpcs:ignore
2
3 /**
4 * Image Upload
5 *
6 * Business logic for uploading, retrieving, and deleting images
7 * used by Product Badges and Text Highlights.
8 *
9 * @package Disco
10 * @subpackage Disco\App\Upload
11 * @since 1.3.21
12 * @category App
13 */
14
15 namespace Disco\App\Upload;
16
17 /**
18 * Class ImageUpload
19 *
20 * Provides methods to upload images to the WordPress media library
21 * under type-specific directories, retrieve uploaded images by type,
22 * and delete images from the media library.
23 *
24 * Supported upload types:
25 * - product-badge → stored in wp-content/uploads/disco-product-badges/
26 * - text-highlight → stored in wp-content/uploads/disco-text-highlight/
27 *
28 * @package Disco
29 * @subpackage Disco\App\Upload
30 * @author Ohidul Islam <wahid0003@gmail.com>
31 * @link https://webappick.com
32 * @license https://opensource.org/licenses/gpl-license.php GNU Public License
33 * @category App
34 */
35 class ImageUpload {
36
37 /**
38 * Mapping of upload types to their target directory names.
39 *
40 * Each type stores images in a dedicated subdirectory
41 * under the WordPress uploads folder.
42 *
43 * @since 1.0.0
44 */
45 public const UPLOAD_DIRS = array( // phpcs:ignore SlevomatCodingStandard.Classes.DisallowMultiConstantDefinition.DisallowedMultiConstantDefinition
46 'product-badge' => 'disco-product-badges',
47 'text-highlight' => 'disco-text-highlight',
48 );
49
50 /**
51 * Allowed MIME types for uploaded images.
52 *
53 * Only PNG, JPEG, and WebP formats are permitted.
54 *
55 * @since 1.0.0
56 */
57 private const ALLOWED_MIME_TYPES = array( // phpcs:ignore SlevomatCodingStandard.Classes.DisallowMultiConstantDefinition.DisallowedMultiConstantDefinition
58 'image/png',
59 'image/jpeg',
60 'image/jpg',
61 'image/webp',
62 );
63
64 /**
65 * Maximum allowed file size in bytes.
66 *
67 * Set to 100 KB (102400 bytes).
68 *
69 * @since 1.0.0
70 */
71 private const MAX_FILE_SIZE = 102400;
72
73 /**
74 * Upload an image to the WordPress media library.
75 *
76 * Validates the file MIME type and size, moves it to a type-specific
77 * upload directory, and creates a WordPress media library attachment.
78 *
79 * @since 1.0.0
80 * @param array $file Uploaded file data from $_FILES (name, type, tmp_name, error, size).
81 * @param string $type Upload type key. Must be one of: 'product-badge', 'text-highlight'.
82 * @return array{id: int, url: string, type: string}|\WP_Error Attachment data on success, WP_Error on failure.
83 */
84 public function upload( $file, $type ) {
85 if ( empty( $file ) ) {
86 return new \WP_Error(
87 'no_image',
88 __( 'No image file provided.', 'disco' ),
89 array( 'status' => 400 )
90 );
91 }
92
93 if ( ! $type || ! isset( self::UPLOAD_DIRS[ $type ] ) ) {
94 return new \WP_Error(
95 'invalid_type',
96 __( 'Invalid upload type. Use product-badge or text-highlight.', 'disco' ),
97 array( 'status' => 400 )
98 );
99 }
100
101 $file = $this->validate_file( $file );
102
103 if ( is_wp_error( $file ) ) {
104 return $file;
105 }
106
107 return $this->process_upload( $file['file'], $file['mime_type'], $type );
108 }
109
110 /**
111 * Retrieve all uploaded images for a given upload type.
112 *
113 * Queries WordPress media library attachments and filters them
114 * by the type-specific upload directory path.
115 *
116 * @since 1.0.0
117 * @param string $type Upload type key. Must be one of: 'product-badge', 'text-highlight'.
118 * @return array<int, array{id: int, name: string, url: string}>|\WP_Error Array of image data on success, WP_Error on failure.
119 */
120 public function get_images( $type ) {
121 if ( ! isset( self::UPLOAD_DIRS[ $type ] ) ) {
122 return new \WP_Error(
123 'invalid_type',
124 __( 'Invalid type. Use product-badge or text-highlight.', 'disco' ),
125 array( 'status' => 400 )
126 );
127 }
128
129 $dir_name = self::UPLOAD_DIRS[ $type ];
130 $attachments = get_posts(
131 array(
132 'post_type' => 'attachment',
133 'post_status' => 'inherit',
134 'posts_per_page' => -1,
135 'orderby' => 'date',
136 'order' => 'DESC',
137 'meta_query' => array(), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
138 )
139 );
140
141 $images = array();
142
143 foreach ( $attachments as $attachment ) {
144 $file_path = get_attached_file( $attachment->ID );
145
146 if ( ! $file_path || ! str_contains( $file_path, $dir_name . '/' ) ) {
147 continue;
148 }
149
150 $url = wp_get_attachment_url( $attachment->ID );
151
152 if ( ! $url ) {
153 continue;
154 }
155
156 $images[] = array(
157 'id' => $attachment->ID,
158 'name' => basename( $file_path ),
159 'url' => $url,
160 );
161 }
162
163 return $images;
164 }
165
166 /**
167 * Delete an uploaded image from the WordPress media library.
168 *
169 * Permanently removes the attachment post and its associated
170 * file from disk using wp_delete_attachment with force delete.
171 *
172 * @since 1.0.0
173 * @param int $id WordPress attachment post ID.
174 * @return array{id: int, deleted: bool}|\WP_Error Delete confirmation on success, WP_Error on failure.
175 */
176 public function delete( $id ) {
177 $attachment = get_post( $id );
178
179 if ( ! $attachment || 'attachment' !== $attachment->post_type ) {
180 return new \WP_Error(
181 'not_found',
182 __( 'Image not found.', 'disco' ),
183 array( 'status' => 404 )
184 );
185 }
186
187 $result = wp_delete_attachment( $id, true );
188
189 if ( ! $result ) {
190 return new \WP_Error(
191 'delete_error',
192 __( 'Failed to delete image.', 'disco' ),
193 array( 'status' => 500 )
194 );
195 }
196
197 return array(
198 'id' => $id,
199 'deleted' => true,
200 );
201 }
202
203 /**
204 * Validate the uploaded file's MIME type and size.
205 *
206 * Uses PHP's finfo extension for reliable MIME type detection
207 * rather than relying on the client-provided Content-Type.
208 *
209 * @since 1.0.0
210 * @param array $file Uploaded file data from $_FILES.
211 * @return array{file: array, mime_type: string}|\WP_Error Validated file data or WP_Error.
212 */
213 private function validate_file( $file ) {
214 // Detect MIME type from file contents, not the client-provided header.
215 $finfo = finfo_open( FILEINFO_MIME_TYPE );
216
217 if ( false === $finfo ) {
218 return new \WP_Error(
219 'finfo_error',
220 __( 'Unable to detect file type.', 'disco' ),
221 array( 'status' => 500 )
222 );
223 }
224
225 $mime_type = finfo_file( $finfo, $file['tmp_name'] );
226 finfo_close( $finfo );
227
228 if ( false === $mime_type || ! in_array( $mime_type, self::ALLOWED_MIME_TYPES, true ) ) {
229 return new \WP_Error(
230 'invalid_format',
231 __( 'Only PNG, JPG, JPEG, and WebP formats are allowed.', 'disco' ),
232 array( 'status' => 400 )
233 );
234 }
235
236 if ( $file['size'] > self::MAX_FILE_SIZE ) {
237 return new \WP_Error(
238 'file_too_large',
239 __( 'File size must be under 100 KB.', 'disco' ),
240 array( 'status' => 400 )
241 );
242 }
243
244 return array(
245 'file' => $file,
246 'mime_type' => $mime_type,
247 );
248 }
249
250 /**
251 * Process the validated file upload.
252 *
253 * Moves the file to the type-specific directory and creates a
254 * WordPress media library attachment.
255 *
256 * @since 1.0.0
257 * @param array $file Validated file data from $_FILES.
258 * @param string $mime_type Detected MIME type of the file.
259 * @param string $type Upload type key (product-badge or text-highlight).
260 * @return array{id: int, url: string, type: string}|\WP_Error Attachment data on success, WP_Error on failure.
261 */
262 private function process_upload( $file, $mime_type, $type ) {
263 $dir_name = self::UPLOAD_DIRS[ $type ];
264 $upload_filter = function ( $uploads ) use ( $dir_name ) {
265 $uploads['subdir'] = '/' . $dir_name;
266 $uploads['path'] = $uploads['basedir'] . '/' . $dir_name;
267 $uploads['url'] = $uploads['baseurl'] . '/' . $dir_name;
268
269 return $uploads;
270 };
271
272 add_filter( 'upload_dir', $upload_filter ); // @phpstan-ignore arguments.count
273 $this->ensure_upload_directory();
274
275 require_once ABSPATH . 'wp-admin/includes/file.php';
276 require_once ABSPATH . 'wp-admin/includes/media.php';
277 require_once ABSPATH . 'wp-admin/includes/image.php';
278
279 $file_array = array(
280 'name' => sanitize_file_name( $file['name'] ),
281 'type' => $mime_type,
282 'tmp_name' => $file['tmp_name'],
283 'error' => $file['error'],
284 'size' => $file['size'],
285 );
286
287 $uploaded = wp_handle_upload(
288 $file_array,
289 array(
290 'test_form' => false,
291 'test_type' => true,
292 'mimes' => array(
293 'jpg|jpeg' => 'image/jpeg',
294 'png' => 'image/png',
295 'webp' => 'image/webp',
296 ),
297 )
298 );
299
300 remove_filter( 'upload_dir', $upload_filter );
301
302 if ( isset( $uploaded['error'] ) ) {
303 return new \WP_Error( 'upload_error', $uploaded['error'], array( 'status' => 500 ) );
304 }
305
306 return $this->create_attachment( $uploaded, $file, $type );
307 }
308
309 /**
310 * Ensure the upload target directory exists.
311 *
312 * @since 1.0.0
313 * @return void
314 */
315 private function ensure_upload_directory() {
316 $upload_dir = wp_upload_dir();
317
318 if ( file_exists( $upload_dir['path'] ) ) {
319 return;
320 }
321
322 wp_mkdir_p( $upload_dir['path'] );
323 }
324
325 /**
326 * Create a media library attachment for the uploaded file.
327 *
328 * @since 1.0.0
329 * @param array $uploaded Upload result from wp_handle_upload.
330 * @param array $file Original file data.
331 * @param string $type Upload type key.
332 * @return array{id: int, url: string, type: string}|\WP_Error Attachment data on success, WP_Error on failure.
333 */
334 private function create_attachment( $uploaded, $file, $type ) {
335 $attachment_id = wp_insert_attachment(
336 array(
337 'post_mime_type' => $uploaded['type'],
338 'post_title' => preg_replace( '/\.[^.]+$/', '', sanitize_file_name( $file['name'] ) ),
339 'post_content' => '',
340 'post_status' => 'inherit',
341 ),
342 $uploaded['file']
343 );
344
345 if ( 0 === $attachment_id ) {
346 return new \WP_Error(
347 'attachment_error',
348 __( 'Failed to create media library entry.', 'disco' ),
349 array( 'status' => 500 )
350 );
351 }
352
353 $metadata = wp_generate_attachment_metadata( $attachment_id, $uploaded['file'] );
354 wp_update_attachment_metadata( $attachment_id, $metadata );
355
356 return array(
357 'id' => $attachment_id,
358 'url' => $uploaded['url'],
359 'type' => $type,
360 );
361 }
362
363 }
364