PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / trunk
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF vtrunk
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 / inc / 3rd-party / nextgen-gallery / classes / Media / NGG.php

NGG.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF trunk, at inc/3rd-party/nextgen-gallery/classes/Media/NGG.php

569 lines 15.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Imagify\ThirdParty\NGG\Media;
3
4 use Imagify\Deprecated\Traits\Media\NGGDeprecatedTrait;
5
6 /**
7 * Media class for the medias from NextGen Gallery.
8 *
9 * @since 1.9
10 * @author Grégory Viguier
11 */
12 class NGG extends \Imagify\Media\AbstractMedia {
13 use NGGDeprecatedTrait;
14
15 /**
16 * Context (where the media "comes from").
17 *
18 * @var string
19 * @since 1.9
20 * @access protected
21 * @author Grégory Viguier
22 */
23 protected $context = 'ngg';
24
25 /**
26 * The image object.
27 *
28 * @var object A \nggImage object.
29 * @since 1.9
30 * @access protected
31 * @author Grégory Viguier
32 */
33 protected $image;
34
35 /**
36 * The storage object used by NGG.
37 *
38 * @var object A \C_Gallery_Storage object (by default).
39 * @since 1.8.
40 * @access protected
41 */
42 protected $storage;
43
44 /**
45 * The constructor.
46 *
47 * @since 1.9
48 * @access public
49 * @author Grégory Viguier
50 *
51 * @param int|\nggImage|\nggdb|\StdClass $id The NGG image ID, \nggImage object, \nggdb object, or an anonym object containing a pid property.
52 */
53 public function __construct( $id ) {
54 if ( ! static::constructor_accepts( $id ) ) {
55 parent::__construct( 0 );
56 return;
57 }
58
59 if ( is_numeric( $id ) ) {
60 $this->image = \nggdb::find_image( (int) $id );
61 $id = ! empty( $this->image->pid ) ? (int) $this->image->pid : 0;
62 } elseif ( $id instanceof \nggImage ) {
63 $this->image = $id;
64 $id = (int) $id->pid;
65 } elseif ( is_object( $id ) ) {
66 $this->image = \nggdb::find_image( (int) $id->pid );
67 $id = ! empty( $this->image->pid ) ? (int) $this->image->pid : 0;
68 } else {
69 $id = 0;
70 }
71
72 if ( ! $id ) {
73 $this->image = null;
74
75 parent::__construct( 0 );
76 return;
77 }
78
79 parent::__construct( $id );
80
81 // NGG storage.
82 if ( ! empty( $this->image->_ngiw ) ) {
83 $this->storage = $this->image->_ngiw->get_storage()->object;
84 // @codeCoverageIgnoreStart — requires live NGG v3 POPE classes or NGGFOLDER constant; not available in unit tests.
85 } elseif ( imagify_ngg_has_pope_storage() ) {
86 // C_Gallery_Storage is part of the NGG v3 POPE framework; absent on v4.
87 $this->storage = \C_Gallery_Storage::get_instance()->object;
88 }
89 // else: $this->storage remains null on NGG v4 (no _ngiw and no POPE framework).
90
91 // Load nggAdmin class (NGGFOLDER may be undefined on NGG v4).
92 if ( defined( 'NGGFOLDER' ) && ! class_exists( 'nggAdmin' ) ) {
93 $ngg_admin_functions_path = WP_PLUGIN_DIR . '/' . NGGFOLDER . '/products/photocrati_nextgen/modules/ngglegacy/admin/functions.php';
94
95 if ( $this->filesystem->exists( $ngg_admin_functions_path ) ) {
96 require_once $ngg_admin_functions_path;
97 }
98 }
99 // @codeCoverageIgnoreEnd
100 }
101
102 /**
103 * Tell if the given entry can be accepted in the constructor.
104 *
105 * @since 1.9
106 * @access public
107 * @author Grégory Viguier
108 *
109 * @param mixed $id Whatever.
110 * @return bool
111 */
112 public static function constructor_accepts( $id ) {
113 if ( ! $id ) {
114 return false;
115 }
116
117 if ( is_numeric( $id ) || $id instanceof \nggImage ) {
118 return true;
119 }
120
121 return is_object( $id ) && ! empty( $id->pid ) && is_numeric( $id->pid );
122 }
123
124
125 /** ----------------------------------------------------------------------------------------- */
126 /** NGG SPECIFICS =========================================================================== */
127 /** ----------------------------------------------------------------------------------------- */
128
129 /**
130 * Get the NGG image object.
131 *
132 * @since 1.9
133 * @access public
134 * @author Grégory Viguier
135 *
136 * @return \nggImage
137 */
138 public function get_ngg_image() {
139 return $this->image;
140 }
141
142 /**
143 * Get the NGG storage object.
144 *
145 * @since 1.9
146 * @access public
147 * @author Grégory Viguier
148 *
149 * @return \C_Gallery_Storage
150 */
151 public function get_ngg_storage() {
152 return $this->storage;
153 }
154
155
156 /** ----------------------------------------------------------------------------------------- */
157 /** ORIGINAL FILE =========================================================================== */
158 /** ----------------------------------------------------------------------------------------- */
159
160 /**
161 * Get the original file path, even if the file doesn't exist.
162 *
163 * @since 1.9
164 * @access public
165 * @author Grégory Viguier
166 *
167 * @return string|bool The file path. False on failure.
168 */
169 public function get_raw_original_path() {
170 if ( ! $this->is_valid() ) {
171 return false;
172 }
173
174 if ( $this->get_cdn() ) {
175 return $this->get_cdn()->get_file_path( 'original' );
176 }
177
178 return ! empty( $this->image->imagePath ) ? $this->image->imagePath : false;
179 }
180
181
182 /** ----------------------------------------------------------------------------------------- */
183 /** FULL SIZE FILE ========================================================================== */
184 /** ----------------------------------------------------------------------------------------- */
185
186 /**
187 * Get the URL of the media's full size file.
188 *
189 * @since 1.9.8
190 * @access public
191 * @author Grégory Viguier
192 *
193 * @return string|bool The file URL. False on failure.
194 */
195 public function get_fullsize_url() {
196 if ( ! $this->is_valid() ) {
197 return false;
198 }
199
200 if ( $this->get_cdn() ) {
201 return $this->get_cdn()->get_file_url();
202 }
203
204 return ! empty( $this->image->imageURL ) ? $this->image->imageURL : false;
205 }
206
207 /**
208 * Get the path to the media's full size file, even if the file doesn't exist.
209 *
210 * @since 1.9.8
211 * @access public
212 * @author Grégory Viguier
213 *
214 * @return string|bool The file path. False on failure.
215 */
216 public function get_raw_fullsize_path() {
217 if ( ! $this->is_valid() ) {
218 return false;
219 }
220
221 if ( $this->get_cdn() ) {
222 return $this->get_cdn()->get_file_path();
223 }
224
225 return ! empty( $this->image->imagePath ) ? $this->image->imagePath : false;
226 }
227
228
229 /** ----------------------------------------------------------------------------------------- */
230 /** BACKUP FILE ============================================================================= */
231 /** ----------------------------------------------------------------------------------------- */
232
233 /**
234 * Get the backup URL, even if the file doesn't exist.
235 *
236 * @since 1.9
237 * @access public
238 * @author Grégory Viguier
239 *
240 * @return string|bool The file URL. False on failure.
241 */
242 public function get_backup_url() {
243 if ( ! $this->is_valid() ) {
244 return false;
245 }
246
247 return site_url( '/' . $this->filesystem->make_path_relative( $this->get_raw_backup_path() ) );
248 }
249
250 /**
251 * Get the backup file path, even if the file doesn't exist.
252 *
253 * @since 1.9
254 * @access public
255 * @author Grégory Viguier
256 *
257 * @return string|bool The file path. False on failure.
258 */
259 public function get_raw_backup_path() {
260 if ( ! $this->is_valid() ) {
261 return false;
262 }
263
264 return get_imagify_ngg_attachment_backup_path( $this->get_raw_original_path() );
265 }
266
267
268 /** ----------------------------------------------------------------------------------------- */
269 /** THUMBNAILS ============================================================================== */
270 /** ----------------------------------------------------------------------------------------- */
271
272 /**
273 * Create the media thumbnails.
274 *
275 * @since 1.9
276 * @access public
277 * @author Grégory Viguier
278 *
279 * @return bool|WP_Error True on success. A \WP_Error instance on failure.
280 */
281 public function generate_thumbnails() {
282 if ( ! $this->is_valid() ) {
283 return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
284 }
285
286 // @codeCoverageIgnoreStart — requires live NGG storage; not available in unit tests.
287 if ( null === $this->storage ) {
288 return new \WP_Error( 'no_ngg_storage', __( 'NextGen Gallery storage is not available.', 'imagify' ) );
289 }
290 // @codeCoverageIgnoreEnd
291
292 $image_data = $this->storage->_image_mapper->find( $this->get_id() );
293
294 if ( ! $image_data ) {
295 return new \WP_Error( 'no_ngg_image', __( 'Image not found in NextGen Gallery data.', 'imagify' ) );
296 }
297
298 if ( empty( $image_data->meta_data['backup'] ) || ! is_array( $image_data->meta_data['backup'] ) ) {
299 $full_path = $this->storage->get_image_abspath( $image_data );
300
301 $image_data->meta_data['backup'] = [
302 'filename' => $this->filesystem->file_name( $full_path ), // Yes, $full_path.
303 'width' => $image_data->meta_data['width'], // Original image width.
304 'height' => $image_data->meta_data['height'], // Original image height.
305 'generated' => microtime(),
306 ];
307 }
308
309 $backup_path = $this->storage->get_image_abspath( $image_data, 'backup' );
310 $failed = [];
311
312 foreach ( $this->get_media_files() as $size_name => $size_data ) {
313 if ( 'full' === $size_name ) {
314 continue;
315 }
316
317 $params = $this->storage->get_image_size_params( $image_data, $size_name );
318 $thumbnail = @$this->storage->generate_image_clone( // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
319 $backup_path,
320 $this->storage->get_image_abspath( $image_data, $size_name ),
321 $params
322 );
323
324 if ( ! $thumbnail ) {
325 // Failed.
326 $failed[] = $size_name;
327 unset( $image_data->meta_data[ $size_name ] );
328 continue;
329 }
330
331 $size_meta = [
332 'width' => 0,
333 'height' => 0,
334 'filename' => \M_I18n::mb_basename( $thumbnail->fileName ),
335 'generated' => microtime(),
336 ];
337
338 $dimensions = $this->filesystem->get_image_size( $thumbnail->fileName );
339
340 if ( $dimensions ) {
341 $size_meta['width'] = $dimensions['width'];
342 $size_meta['height'] = $dimensions['height'];
343 }
344
345 if ( isset( $params['crop_frame'] ) ) {
346 $size_meta['crop_frame'] = $params['crop_frame'];
347 }
348
349 $image_data->meta_data[ $size_name ] = $size_meta;
350 }
351
352 // Keep our property up to date (defensively guard _ngiw in case it is absent).
353 // @codeCoverageIgnoreStart — requires live NGG _ngiw storage object; not available in unit tests.
354 if ( isset( $this->image->_ngiw ) ) {
355 $this->image->_ngiw->_cache['meta_data'] = $image_data->meta_data;
356 $this->image->_ngiw->_orig_image = $image_data;
357 }
358 // @codeCoverageIgnoreEnd
359
360 $post_id = $this->storage->_image_mapper->save( $image_data );
361
362 if ( ! $post_id ) {
363 return new \WP_Error( 'meta_data_not_saved', __( 'Related NextGen Gallery data could not be saved.', 'imagify' ) );
364 }
365
366 if ( $failed ) {
367 return new \WP_Error(
368 'thumbnail_restore_failed',
369 sprintf( _n( '%n thumbnail could not be restored.', '%n thumbnails could not be restored.', count( $failed ), 'imagify' ), count( $failed ) ),
370 [ 'failed_thumbnails' => $failed ]
371 );
372 }
373
374 return true;
375 }
376
377
378 /** ----------------------------------------------------------------------------------------- */
379 /** MEDIA DATA ============================================================================== */
380 /** ----------------------------------------------------------------------------------------- */
381
382 /**
383 * Tell if the current media has the required data (the data containing the file paths and thumbnails).
384 *
385 * @since 1.9
386 * @access public
387 * @author Grégory Viguier
388 *
389 * @return bool
390 */
391 public function has_required_media_data() {
392 static $sizes;
393
394 if ( ! $this->is_valid() ) {
395 return false;
396 }
397
398 if ( ! isset( $sizes ) ) {
399 $sizes = $this->get_media_files();
400 }
401
402 return $sizes && ! empty( $this->image->imagePath );
403 }
404
405 /**
406 * Get the list of the files of this media, including the full size file.
407 *
408 * @since 1.9
409 * @access public
410 * @author Grégory Viguier
411 *
412 * @return array {
413 * An array with the size names as keys ('full' is used for the full size file), and arrays of data as values:
414 *
415 * @type string $size The size name.
416 * @type string $path Absolute path to the file.
417 * @type int $width The file width.
418 * @type int $height The file height.
419 * @type string $mime-type The file mime type.
420 * @type bool $disabled True if the size is disabled in the plugin's settings.
421 * }
422 */
423 public function get_media_files() {
424 if ( ! $this->is_valid() ) {
425 return [];
426 }
427
428 $fullsize_path = $this->get_raw_fullsize_path();
429
430 if ( ! $fullsize_path ) {
431 return [];
432 }
433
434 $dimensions = $this->get_dimensions();
435 $all_sizes = [
436 'full' => [
437 'size' => 'full',
438 'path' => $fullsize_path,
439 'width' => $dimensions['width'],
440 'height' => $dimensions['height'],
441 'mime-type' => $this->get_mime_type(),
442 'disabled' => false,
443 ],
444 ];
445
446 if ( ! $this->is_image() ) {
447 return $this->filter_media_files( $all_sizes );
448 }
449
450 // Remove common values (that have no value for us here, lol). Also remove 'full' and 'backup'.
451 $image_data = array_diff_key(
452 is_array( $this->image->meta_data ) ? $this->image->meta_data : [],
453 [
454 'full' => 1,
455 'backup' => 1,
456 'width' => 1,
457 'height' => 1,
458 'md5' => 1,
459 'aperture' => 1,
460 'credit' => 1,
461 'camera' => 1,
462 'caption' => 1,
463 'created_timestamp' => 1,
464 'copyright' => 1,
465 'focal_length' => 1,
466 'iso' => 1,
467 'shutter_speed' => 1,
468 'flash' => 1,
469 'title' => 1,
470 'keywords' => 1,
471 'saved' => 1,
472 ]
473 );
474
475 if ( ! $image_data ) {
476 return $this->filter_media_files( $all_sizes );
477 }
478
479 // @codeCoverageIgnoreStart — requires live NGG storage; not available in unit tests.
480 if ( null === $this->storage ) {
481 return $this->filter_media_files( $all_sizes );
482 }
483 // @codeCoverageIgnoreEnd
484
485 $ngg_data = $this->storage->_image_mapper->find( $this->get_id() );
486
487 foreach ( $image_data as $size => $size_data ) {
488 if ( ! isset( $size_data['width'], $size_data['height'], $size_data['filename'], $size_data['generated'] ) ) {
489 continue;
490 }
491
492 $file_type = (object) wp_check_filetype( $size_data['filename'], $this->get_allowed_mime_types() );
493
494 if ( ! $file_type->type ) {
495 continue;
496 }
497
498 $all_sizes[ $size ] = [
499 'size' => $size,
500 'path' => $this->storage->get_image_abspath( $ngg_data, $size ),
501 'width' => (int) $size_data['width'],
502 'height' => (int) $size_data['height'],
503 'mime-type' => $file_type->type,
504 'disabled' => false,
505 ];
506 }
507
508 return $this->filter_media_files( $all_sizes );
509 }
510
511 /**
512 * If the media is an image, get its width and height.
513 *
514 * @since 1.9
515 * @access public
516 * @author Grégory Viguier
517 *
518 * @return array
519 */
520 public function get_dimensions() {
521 if ( ! $this->is_image() ) {
522 return [
523 'width' => 0,
524 'height' => 0,
525 ];
526 }
527
528 return [
529 'width' => ! empty( $this->image->meta_data['width'] ) ? (int) $this->image->meta_data['width'] : 0,
530 'height' => ! empty( $this->image->meta_data['height'] ) ? (int) $this->image->meta_data['height'] : 0,
531 ];
532 }
533
534 /**
535 * Update the media data dimensions.
536 *
537 * @since 1.9
538 * @access public
539 * @author Grégory Viguier
540 *
541 * @param array $dimensions {
542 * An array containing width and height.
543 *
544 * @type int $width The image width.
545 * @type int $height The image height.
546 * }
547 */
548 protected function update_media_data_dimensions( $dimensions ) {
549 $changed = false;
550 $data = [
551 'width' => $dimensions['width'],
552 'height' => $dimensions['height'],
553 'md5' => md5_file( $this->get_raw_fullsize_path() ),
554 ];
555
556 foreach ( $data as $k => $v ) {
557 if ( ! isset( $this->image->meta_data[ $k ] ) || $this->image->meta_data[ $k ] !== $v ) {
558 $this->image->meta_data[ $k ] = $v;
559
560 $changed = true;
561 }
562 }
563
564 if ( $changed ) {
565 \nggdb::update_image_meta( $this->id, $this->image->meta_data );
566 }
567 }
568 }
569