PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 1.9.4
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v1.9.4
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / classes / Optimization / File.php

File.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF 1.9.4, at classes/Optimization/File.php

847 lines 21.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Imagify\Optimization;
3
4 defined( 'ABSPATH' ) || die( 'Cheatin’ uh?' );
5
6 /**
7 * A generic optimization class focussed on the file itself.
8 *
9 * @since 1.9
10 * @author Grégory Viguier
11 */
12 class File {
13
14 /**
15 * Absolute path to the file.
16 *
17 * @var string
18 * @since 1.9
19 * @access protected
20 * @author Grégory Viguier
21 */
22 protected $path;
23
24 /**
25 * Tell if the file is an image.
26 *
27 * @var bool
28 * @since 1.9
29 * @access protected
30 * @see $this->is_image()
31 * @author Grégory Viguier
32 */
33 protected $is_image;
34
35 /**
36 * Store the file mime type + file extension (if the file is supported).
37 *
38 * @var array
39 * @since 1.9
40 * @access protected
41 * @see $this->get_file_type()
42 * @author Grégory Viguier
43 */
44 protected $file_type;
45
46 /**
47 * Filesystem object.
48 *
49 * @var \Imagify_Filesystem
50 * @since 1.9
51 * @access protected
52 * @author Grégory Viguier
53 */
54 protected $filesystem;
55
56 /**
57 * The editor instance used to resize the file.
58 *
59 * @var \WP_Image_Editor_Imagick|\WP_Image_Editor_GD|WP_Error.
60 * @since 1.9
61 * @access protected
62 * @author Grégory Viguier
63 */
64 protected $editor;
65
66 /**
67 * Used to cache the plugin’s options.
68 *
69 * @var array
70 * @since 1.9
71 * @access protected
72 * @author Grégory Viguier
73 */
74 protected $options = [];
75
76 /**
77 * The constructor.
78 *
79 * @since 1.9
80 * @access public
81 * @author Grégory Viguier
82 *
83 * @param string $file_path Absolute path to the file.
84 */
85 public function __construct( $file_path ) {
86 $this->path = $file_path;
87 $this->filesystem = \Imagify_Filesystem::get_instance();
88 }
89
90 /**
91 * Tell if the file is valid.
92 *
93 * @since 1.9
94 * @access public
95 * @author Grégory Viguier
96 *
97 * @return bool
98 */
99 public function is_valid() {
100 return (bool) $this->path;
101 }
102
103 /**
104 * Tell if the file can be processed.
105 *
106 * @since 1.9
107 * @access public
108 * @author Grégory Viguier
109 *
110 * @return bool|WP_Error
111 */
112 public function can_be_processed() {
113 if ( ! $this->path ) {
114 return new \WP_Error( 'empty_path', __( 'File path is empty.', 'imagify' ) );
115 }
116
117 if ( ! empty( $this->filesystem->errors->errors ) ) {
118 return new \WP_Error( 'filesystem_error', __( 'Filesystem error.', 'imagify' ), $this->filesystem->errors );
119 }
120
121 if ( ! $this->filesystem->exists( $this->path ) ) {
122 return new \WP_Error(
123 'not_exists',
124 sprintf(
125 /* translators: %s is a file path. */
126 __( 'The file %s does not seem to exist.', 'imagify' ),
127 '<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
128 )
129 );
130 }
131
132 if ( ! $this->filesystem->is_file( $this->path ) ) {
133 return new \WP_Error(
134 'not_a_file',
135 sprintf(
136 /* translators: %s is a file path. */
137 __( 'This does not seem to be a file: %s.', 'imagify' ),
138 '<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
139 )
140 );
141 }
142
143 if ( ! $this->filesystem->is_writable( $this->path ) ) {
144 return new \WP_Error(
145 'not_writable',
146 sprintf(
147 /* translators: %s is a file path. */
148 __( 'The file %s does not seem to be writable.', 'imagify' ),
149 '<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
150 )
151 );
152 }
153
154 $parent_folder = $this->filesystem->dir_path( $this->path );
155
156 if ( ! $this->filesystem->is_writable( $parent_folder ) ) {
157 return new \WP_Error(
158 'folder_not_writable',
159 sprintf(
160 /* translators: %s is a file path. */
161 __( 'The folder %s does not seem to be writable.', 'imagify' ),
162 '<code>' . esc_html( $this->filesystem->make_path_relative( $parent_folder ) ) . '</code>'
163 )
164 );
165 }
166
167 return true;
168 }
169
170
171 /** ----------------------------------------------------------------------------------------- */
172 /** EDITION ================================================================================= */
173 /** ----------------------------------------------------------------------------------------- */
174
175 /**
176 * Resize an image if it is bigger than the maximum width defined in the settings.
177 *
178 * @since 1.9
179 * @access public
180 * @author Grégory Viguier
181 * @author Remy Perona
182 *
183 * @param array $dimensions {
184 * Array of image dimensions.
185 *
186 * @type int $width The image width.
187 * @type int $height The image height.
188 * }
189 * @param int $max_width Maximum width defined in the settings.
190 * @return string|WP_Error Path the the resized image. A WP_Error object on failure.
191 */
192 public function resize( $dimensions = [], $max_width = 0 ) {
193 $can_be_processed = $this->can_be_processed();
194
195 if ( is_wp_error( $can_be_processed ) ) {
196 return $can_be_processed;
197 }
198
199 if ( ! $this->is_image() ) {
200 return new \WP_Error(
201 'not_an_image',
202 sprintf(
203 /* translators: %s is a file path. */
204 __( 'The file %s does not seem to be an image, and cannot be resized.', 'imagify' ),
205 '<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
206 )
207 );
208 }
209
210 $editor = $this->get_editor();
211
212 if ( is_wp_error( $editor ) ) {
213 return $editor;
214 }
215
216 // Try to correct the auto-rotation if the info is available.
217 if ( $this->filesystem->can_get_exif() && 'image/jpeg' === $this->get_mime_type() ) {
218 $exif = $this->filesystem->get_image_exif( $this->path );
219 $orientation = isset( $exif['Orientation'] ) ? (int) $exif['Orientation'] : 1;
220
221 switch ( $orientation ) {
222 case 3:
223 $editor->rotate( 180 );
224 break;
225 case 6:
226 $editor->rotate( -90 );
227 break;
228 case 8:
229 $editor->rotate( 90 );
230 }
231 }
232
233 if ( ! $dimensions ) {
234 $dimensions = $this->get_dimensions();
235 }
236
237 if ( ! $max_width ) {
238 $max_width = $this->get_option( 'resize_larger_w' );
239 }
240
241 // Prevent removal of the exif data when resizing (only works with Imagick).
242 add_filter( 'image_strip_meta', '__return_false', 789 );
243
244 // Resize.
245 $new_sizes = wp_constrain_dimensions( $dimensions['width'], $dimensions['height'], $max_width );
246 $resized = $editor->resize( $new_sizes[0], $new_sizes[1], false );
247
248 // Remove the filter when we're done to prevent any conflict.
249 remove_filter( 'image_strip_meta', '__return_false', 789 );
250
251 if ( is_wp_error( $resized ) ) {
252 return $resized;
253 }
254
255 $resized_image_path = $editor->generate_filename( 'imagifyresized' );
256 $resized_image_saved = $editor->save( $resized_image_path );
257
258 if ( is_wp_error( $resized_image_saved ) ) {
259 return $resized_image_saved;
260 }
261
262 return $resized_image_path;
263 }
264
265 /**
266 * Create a thumbnail.
267 * Warning: If the destination file already exists, it will be overwritten.
268 *
269 * @since 1.9
270 * @access protected
271 * @author Grégory Viguier
272 *
273 * @param array $destination {
274 * The thumbnail data.
275 *
276 * @type string $path Path to the destination file.
277 * @type int $width The image width.
278 * @type int $height The image height.
279 * @type bool $crop True to crop, false to resize.
280 * @type bool $adjust_filename True to adjust the file name like what `$editor->multi_resize()` returns, like WP default behavior (default). False to prevent it, and use the file name from $path instead.
281 * }
282 * @return bool|array|WP_Error {
283 * A WP_Error object on error. True if the file exists.
284 * An array of thumbnail data if the file has just been created:
285 *
286 * @type string $file File name.
287 * @type int $width The image width.
288 * @type int $height The image height.
289 * @type string $mime-type The mime type.
290 * }
291 */
292 public function create_thumbnail( $destination ) {
293 $can_be_processed = $this->can_be_processed();
294
295 if ( is_wp_error( $can_be_processed ) ) {
296 return $can_be_processed;
297 }
298
299 if ( ! $this->is_image() ) {
300 return new \WP_Error(
301 'not_an_image',
302 sprintf(
303 /* translators: %s is a file path. */
304 __( 'The file %s does not seem to be an image, and cannot be resized.', 'imagify' ),
305 '<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
306 )
307 );
308 }
309
310 $editor = $this->get_editor();
311
312 if ( is_wp_error( $editor ) ) {
313 return $editor;
314 }
315
316 // Create the file.
317 $result = $editor->multi_resize( [ $destination ] );
318
319 if ( ! $result ) {
320 return new \WP_Error( 'image_resize_error', __( 'The thumbnail could not be created.', 'imagify' ) );
321 }
322
323 $result = reset( $result );
324
325 $filename = $result['file'];
326 $source_thumb_path = $this->filesystem->dir_path( $this->path ) . $filename;
327
328 if ( ! isset( $destination['adjust_filename'] ) || $destination['adjust_filename'] ) {
329 // The file name can change from what we expected (1px wider, etc), let's use the resulting data to move the file to the right place.
330 $destination_thumb_path = $this->filesystem->dir_path( $destination['path'] ) . $filename;
331 } else {
332 // Respect what is set in $path.
333 $destination_thumb_path = $destination['path'];
334 $result['file'] = $this->filesystem->file_name( $destination['path'] );
335 }
336
337 if ( $source_thumb_path === $destination_thumb_path ) {
338 return $result;
339 }
340
341 $moved = $this->filesystem->move( $source_thumb_path, $destination_thumb_path, true );
342
343 if ( ! $moved ) {
344 return new \WP_Error( 'move_error', __( 'The file could not be moved to its final destination.', 'imagify' ) );
345 }
346
347 return $result;
348 }
349
350 /**
351 * Backup a file.
352 *
353 * @since 1.9
354 * @access public
355 * @author Grégory Viguier
356 *
357 * @param string $backup_path The backup path.
358 * @return bool|WP_Error True on success. False if the backup option is disabled. A WP_Error object on failure.
359 */
360 public function backup( $backup_path = null ) {
361 $can_be_processed = $this->can_be_processed();
362
363 if ( is_wp_error( $can_be_processed ) ) {
364 return $can_be_processed;
365 }
366
367 if ( ! isset( $backup_path ) ) {
368 $backup_path = get_imagify_attachment_backup_path( $this->path );
369 }
370
371 // Make sure the backups directory has no errors.
372 if ( ! $backup_path ) {
373 return new \WP_Error( 'wp_upload_error', __( 'Error while retrieving the backups directory path.', 'imagify' ) );
374 }
375
376 // Create sub-directories.
377 $created = $this->filesystem->make_dir( $this->filesystem->dir_path( $backup_path ) );
378
379 if ( ! $created ) {
380 return new \WP_Error( 'backup_dir_not_writable', __( 'The backup directory is not writable.', 'imagify' ) );
381 }
382
383 /**
384 * Allow to overwrite the backup file if it already exists.
385 *
386 * @since 1.6.9
387 * @author Grégory Viguier
388 *
389 * @param bool $overwrite Whether to overwrite the backup file.
390 * @param string $path The file path.
391 * @param string $backup_path The backup path.
392 */
393 $overwrite = apply_filters( 'imagify_backup_overwrite_backup', false, $this->path, $backup_path );
394
395 // Copy the file.
396 $this->filesystem->copy( $this->path, $backup_path, $overwrite, FS_CHMOD_FILE );
397
398 // Make sure the backup copy exists.
399 if ( ! $this->filesystem->exists( $backup_path ) ) {
400 return new \WP_Error( 'backup_doesnt_exist', __( 'The file could not be saved.', 'imagify' ), array(
401 'file_path' => $this->filesystem->make_path_relative( $this->path ),
402 'backup_path' => $this->filesystem->make_path_relative( $backup_path ),
403 ) );
404 }
405
406 return true;
407 }
408
409 /**
410 * Optimize a file with Imagify.
411 *
412 * @since 1.9
413 * @access public
414 * @author Grégory Viguier
415 *
416 * @param array $args {
417 * Optional. An array of arguments.
418 *
419 * @type bool $backup False to prevent backup. True to follow the user's setting. A backup can't be forced.
420 * @type string $backup_path If a backup must be done, this is the path to use. Default is the backup path used for the WP Media Library.
421 * @type int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
422 * @type bool $keep_exif To keep exif data or not.
423 * @type string $convert Set to 'webp' to convert the image to webp.
424 * @type string $context The context.
425 * @type int $original_size The file size, sent to the API.
426 * }
427 * @return \sdtClass|\WP_Error Optimized image data. A \WP_Error object on error.
428 */
429 public function optimize( $args = [] ) {
430 $args = array_merge( [
431 'backup' => true,
432 'backup_path' => null,
433 'optimization_level' => 0,
434 'keep_exif' => true,
435 'convert' => '',
436 'context' => 'wp',
437 'original_size' => 0,
438 ], $args );
439
440 $can_be_processed = $this->can_be_processed();
441
442 if ( is_wp_error( $can_be_processed ) ) {
443 return $can_be_processed;
444 }
445
446 // Check if external HTTP requests are blocked.
447 if ( \Imagify_Requirements::is_imagify_blocked() ) {
448 return new \WP_Error( 'http_block_external', __( 'External HTTP requests are blocked.', 'imagify' ) );
449 }
450
451 /**
452 * Fires before a media file optimization.
453 *
454 * @since 1.9
455 * @author Grégory Viguier
456 *
457 * @param string $path Absolute path to the media file.
458 * @param array $args Arguments passed to the method.
459 */
460 do_action( 'imagify_before_optimize_file', $this->path, $args );
461
462 /**
463 * Fires before to optimize the Image with Imagify.
464 *
465 * @since 1.0
466 * @deprecated
467 *
468 * @param string $path Absolute path to the image file.
469 * @param bool $backup True if a backup will be make.
470 */
471 do_action_deprecated( 'before_do_imagify', [ $this->path, $args['backup'] ], '1.9', 'imagify_before_optimize_file' );
472
473 if ( $args['backup'] ) {
474 $backup_result = $this->backup( $args['backup_path'] );
475
476 if ( is_wp_error( $backup_result ) ) {
477 // Stop the process if we can't backup the file.
478 return $backup_result;
479 }
480 }
481
482 // Send file for optimization and fetch the response.
483 $data = [
484 'normal' => 0 === $args['optimization_level'],
485 'aggressive' => 1 === $args['optimization_level'],
486 'ultra' => 2 === $args['optimization_level'],
487 'keep_exif' => $args['keep_exif'],
488 'original_size' => $args['original_size'],
489 'context' => $args['context'],
490 ];
491
492 if ( $args['convert'] ) {
493 $data['convert'] = $args['convert'];
494 }
495
496 $response = upload_imagify_image( [
497 'image' => $this->path,
498 'data' => wp_json_encode( $data ),
499 ] );
500
501 if ( is_wp_error( $response ) ) {
502 return new \WP_Error( 'api_error', $response->get_error_message() );
503 }
504
505 if ( ! function_exists( 'download_url' ) ) {
506 require_once ABSPATH . 'wp-admin/includes/file.php';
507 }
508
509 $temp_file = download_url( $response->image );
510
511 if ( is_wp_error( $temp_file ) ) {
512 return new \WP_Error( 'temp_file_not_found', $temp_file->get_error_message() );
513 }
514
515 if ( 'webp' === $args['convert'] ) {
516 $destination_path = $this->get_path_to_webp();
517 $this->path = $destination_path;
518 $this->file_type = null;
519 $this->editor = null;
520 } else {
521 $destination_path = $this->path;
522 }
523
524 $moved = $this->filesystem->move( $temp_file, $destination_path, true );
525
526 if ( ! $moved ) {
527 return new \WP_Error( 'move_error', __( 'The file could not be moved to its final destination.', 'imagify' ) );
528 }
529
530 /**
531 * Fires after to optimize the Image with Imagify.
532 *
533 * @since 1.0
534 * @deprecated
535 *
536 * @param string $path Absolute path to the image file.
537 * @param bool $backup True if a backup has been made.
538 */
539 do_action_deprecated( 'after_do_imagify', [ $this->path, $args['backup'] ], '1.9', 'imagify_before_optimize_file' );
540
541 /**
542 * Fires after a media file optimization.
543 *
544 * @since 1.9
545 * @author Grégory Viguier
546 *
547 * @param string $path Absolute path to the media file.
548 * @param array $args Arguments passed to the method.
549 */
550 do_action( 'imagify_after_optimize_file', $this->path, $args );
551
552 return $response;
553 }
554
555
556 /** ----------------------------------------------------------------------------------------- */
557 /** IMAGE EDITOR (GD/IMAGEMAGICK) =========================================================== */
558 /** ----------------------------------------------------------------------------------------- */
559
560 /**
561 * Get an image editor instance (WP_Image_Editor_Imagick, WP_Image_Editor_GD).
562 *
563 * @since 1.9
564 * @access protected
565 * @author Grégory Viguier
566 *
567 * @return WP_Image_Editor_Imagick|WP_Image_Editor_GD|WP_Error
568 */
569 protected function get_editor() {
570 if ( isset( $this->editor ) ) {
571 return $this->editor;
572 }
573
574 $this->editor = wp_get_image_editor( $this->path, [
575 'methods' => $this->get_editor_methods(),
576 ] );
577
578 if ( ! is_wp_error( $this->editor ) ) {
579 return $this->editor;
580 }
581
582 $this->editor = new \WP_Error(
583 'image_editor',
584 sprintf(
585 /* translators: %1$s is an error message, %2$s is a "More info?" link. */
586 __( 'No php extensions are available to edit images on the server. ImageMagick or GD is required. The internal error is: %1$s. %2$s', 'imagify' ),
587 $this->editor->get_error_message(),
588 '<a href="' . esc_url( imagify_get_external_url( 'documentation-imagick-gd' ) ) . '" target="_blank">' . __( 'More info?', 'imagify' ) . '</a>'
589 )
590 );
591
592 return $this->editor;
593 }
594
595 /**
596 * Get the image editor methods we will use.
597 *
598 * @since 1.9
599 * @access protected
600 * @author Grégory Viguier
601 *
602 * @return array
603 */
604 protected function get_editor_methods() {
605 static $methods;
606
607 if ( isset( $methods ) ) {
608 return $methods;
609 }
610
611 $methods = [
612 'resize',
613 'multi_resize',
614 'generate_filename',
615 'save',
616 ];
617
618 if ( $this->filesystem->can_get_exif() ) {
619 $methods[] = 'rotate';
620 }
621
622 return $methods;
623 }
624
625
626 /** ----------------------------------------------------------------------------------------- */
627 /** VARIOUS TOOLS =========================================================================== */
628 /** ----------------------------------------------------------------------------------------- */
629
630 /**
631 * Check if a file exceeds the weight limit (> 5mo).
632 *
633 * @since 1.9
634 * @access public
635 * @author Grégory Viguier
636 *
637 * @return bool
638 */
639 public function is_exceeded() {
640 if ( ! $this->is_valid() ) {
641 return false;
642 }
643
644 $size = $this->filesystem->size( $this->path );
645
646 return $size > IMAGIFY_MAX_BYTES;
647 }
648
649 /**
650 * Tell if the current file is supported for a given context.
651 *
652 * @since 1.9
653 * @access public
654 * @see imagify_get_mime_types()
655 * @author Grégory Viguier
656 *
657 * @param array $allowed_mime_types A list of allowed mime types.
658 * @return bool
659 */
660 public function is_supported( $allowed_mime_types ) {
661 return in_array( $this->get_mime_type(), $allowed_mime_types, true );
662 }
663
664 /**
665 * Tell if the file is an image.
666 *
667 * @since 1.9
668 * @access public
669 * @author Grégory Viguier
670 *
671 * @return bool
672 */
673 public function is_image() {
674 if ( isset( $this->is_image ) ) {
675 return $this->is_image;
676 }
677
678 $this->is_image = strpos( $this->get_mime_type(), 'image/' ) === 0;
679
680 return $this->is_image;
681 }
682
683 /**
684 * Tell if the file is a pdf.
685 *
686 * @since 1.9
687 * @access public
688 * @author Grégory Viguier
689 *
690 * @return bool
691 */
692 public function is_pdf() {
693 return 'application/pdf' === $this->get_mime_type();
694 }
695
696 /**
697 * Get the file mime type.
698 *
699 * @since 1.9
700 * @access public
701 * @author Grégory Viguier
702 *
703 * @return string
704 */
705 public function get_mime_type() {
706 return $this->get_file_type()->type;
707 }
708
709 /**
710 * Get the file extension.
711 *
712 * @since 1.9
713 * @access public
714 * @author Grégory Viguier
715 *
716 * @return string|null
717 */
718 public function get_extension() {
719 return $this->get_file_type()->ext;
720 }
721
722 /**
723 * Get the file path.
724 *
725 * @since 1.9
726 * @access public
727 * @author Grégory Viguier
728 *
729 * @return string
730 */
731 public function get_path() {
732 return $this->path;
733 }
734
735 /**
736 * Replace the file extension by webp.
737 *
738 * @since 1.9
739 * @access public
740 * @author Grégory Viguier
741 *
742 * @return string|bool The file path on success. False if not an image or on failure.
743 */
744 public function get_path_to_webp() {
745 if ( ! $this->is_image() ) {
746 return false;
747 }
748
749 if ( $this->is_webp() ) {
750 return false;
751 }
752
753 return imagify_path_to_webp( $this->path );
754 }
755
756 /**
757 * Tell if the file is a webp image.
758 * Rejects "path/to/.webp" files.
759 *
760 * @since 1.9
761 * @access public
762 * @author Grégory Viguier
763 *
764 * @return bool
765 */
766 public function is_webp() {
767 return preg_match( '@(?!^|/|\\\)\.webp$@i', $this->path );
768 }
769
770 /**
771 * Get the file mime type + file extension.
772 *
773 * @since 1.9
774 * @access protected
775 * @see wp_check_filetype()
776 * @author Grégory Viguier
777 *
778 * @return object {
779 * @type string $ext The file extension.
780 * @type string $type The mime type.
781 * }
782 */
783 protected function get_file_type() {
784 if ( isset( $this->file_type ) ) {
785 return $this->file_type;
786 }
787
788 $this->file_type = (object) [
789 'ext' => '',
790 'type' => '',
791 ];
792
793 if ( ! $this->is_valid() ) {
794 return $this->file_type;
795 }
796
797 $this->file_type = (object) wp_check_filetype( $this->path );
798
799 return $this->file_type;
800 }
801
802 /**
803 * If the media is an image, get its width and height.
804 *
805 * @since 1.9
806 * @access public
807 * @author Grégory Viguier
808 *
809 * @return array
810 */
811 public function get_dimensions() {
812 if ( ! $this->is_image() ) {
813 return [
814 'width' => 0,
815 'height' => 0,
816 ];
817 }
818
819 $values = $this->filesystem->get_image_size( $this->path );
820
821 return [
822 'width' => $values['width'],
823 'height' => $values['height'],
824 ];
825 }
826
827 /**
828 * Get a plugin’s option.
829 *
830 * @since 1.9
831 * @access protected
832 * @author Grégory Viguier
833 *
834 * @param string $option_name The option nme.
835 * @return mixed
836 */
837 protected function get_option( $option_name ) {
838 if ( isset( $this->options[ $option_name ] ) ) {
839 return $this->options[ $option_name ];
840 }
841
842 $this->options[ $option_name ] = get_imagify_option( $option_name );
843
844 return $this->options[ $option_name ];
845 }
846 }
847