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 / classes / class-imagify-db.php

class-imagify-db.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF trunk, at inc/classes/class-imagify-db.php

797 lines 24.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Imagify DB class. It reunites tools to work with the DB.
5 *
6 * @since 1.6.13
7 * @author Grégory Viguier
8 */
9 class Imagify_DB {
10
11 /**
12 * Class version.
13 *
14 * @var string
15 */
16 const VERSION = '1.0.1';
17
18 /**
19 * Some hosts limit the number of JOINs in SQL queries, but we need them.
20 *
21 * @since 1.6.13
22 * @access public
23 * @author Grégory Viguier
24 */
25 public static function unlimit_joins() {
26 global $wpdb;
27 static $done = false;
28
29 if ( $done ) {
30 return;
31 }
32
33 $done = true;
34 $query = 'SET SQL_BIG_SELECTS=1';
35
36 /**
37 * Filter the SQL query allowing to remove the limit on JOINs.
38 *
39 * @since 1.6.13
40 * @author Grégory Viguier
41 *
42 * @param string|bool $query The query. False to prevent any query.
43 */
44 $query = apply_filters( 'imagify_db_unlimit_joins_query', $query );
45
46 if ( $query && is_string( $query ) ) {
47 $wpdb->query( $query ); // WPCS: unprepared SQL ok.
48 }
49 }
50
51 /**
52 * Change an array of values into a comma separated list, ready to be used in a `IN ()` clause.
53 *
54 * @since 1.6.13
55 * @access public
56 * @author Grégory Viguier
57 *
58 * @param array $values An array of values.
59 * @return string A comma separated list of values.
60 */
61 public static function prepare_values_list( $values ) {
62 $values = esc_sql( (array) $values );
63 $values = array_map( [ __CLASS__, 'quote_string' ], $values );
64 return implode( ',', $values );
65 }
66
67 /**
68 * Split a list of values into chunks whose rendered `IN ()` comma separated list
69 * stays under a character budget. This prevents hosts (e.g. WP Engine) that kill
70 * overly long SQL queries from failing on unbounded `IN ()` lists.
71 *
72 * @since 2.4
73 *
74 * @param array $values An array of values (integers or strings).
75 * @param int $sql_budget Maximum character length (rendered, comma separated) allowed per chunk.
76 * @return array An array of chunks. Each chunk is an array of values (same type as input).
77 */
78 public static function chunk_in_values( array $values, int $sql_budget = 8000 ): array {
79 if ( ! $values ) {
80 return [];
81 }
82
83 /**
84 * Filter the SQL character budget used to chunk `IN ()` value lists.
85 *
86 * @since 2.4
87 *
88 * @param int $sql_budget The character budget.
89 */
90 $given_sql_budget = $sql_budget;
91 $sql_budget = (int) apply_filters( 'imagify_db_in_clause_sql_budget', $sql_budget );
92
93 if ( $sql_budget < 1 ) {
94 // Fall back to the caller's own value if it was valid, otherwise the hardcoded default.
95 $sql_budget = ( $given_sql_budget >= 1 ) ? $given_sql_budget : 8000;
96 }
97
98 $chunks = [];
99 $current_chunk = [];
100 $current_len = 0;
101
102 foreach ( array_values( $values ) as $value ) {
103 $rendered_len = strlen( self::quote_string( esc_sql( $value ) ) );
104 // +1 for the comma separator, except for the first value in a chunk.
105 $added_len = $current_chunk ? $rendered_len + 1 : $rendered_len;
106
107 if ( $current_chunk && ( $current_len + $added_len ) > $sql_budget ) {
108 // Start a new chunk.
109 $chunks[] = $current_chunk;
110 $current_chunk = [];
111 $current_len = 0;
112 $added_len = $rendered_len;
113 }
114
115 $current_chunk[] = $value;
116 $current_len += $added_len;
117 }
118
119 if ( $current_chunk ) {
120 $chunks[] = $current_chunk;
121 }
122
123 return $chunks;
124 }
125
126 /**
127 * Wrap a value in quotes, unless it's an integer.
128 *
129 * @since 1.6.13
130 * @access public
131 * @author Grégory Viguier
132 *
133 * @param int|string $value A value.
134 * @return int|string
135 */
136 public static function quote_string( $value ) {
137 return is_numeric( $value ) ? $value : "'" . addcslashes( $value, "'" ) . "'";
138 }
139
140 /**
141 * First half of escaping for LIKE special characters % and _ before preparing for MySQL.
142 * Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security.
143 *
144 * Example Prepared Statement:
145 * $wild = '%';
146 * $find = 'only 43% of planets';
147 * $like = $wild . $wpdb->esc_like( $find ) . $wild;
148 * $sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like );
149 *
150 * Example Escape Chain:
151 * $sql = esc_sql( $wpdb->esc_like( $input ) );
152 *
153 * @since 1.7
154 * @access public
155 * @author Grégory Viguier
156 *
157 * @param string $text The raw text to be escaped. The input typed by the user should have no extra or deleted slashes.
158 * @return string Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare() or real_escape next.
159 */
160 public static function esc_like( $text ) {
161 global $wpdb;
162
163 if ( method_exists( $wpdb, 'esc_like' ) ) {
164 // Introduced in WP 4.0.0.
165 return $wpdb->esc_like( $text );
166 }
167
168 return addcslashes( $text, '_%\\' );
169 }
170
171 /**
172 * Get Imagify mime types, ready to be used in a `IN ()` clause.
173 *
174 * @since 1.6.13
175 * @since 1.9 Added $type parameter.
176 * @access public
177 * @author Grégory Viguier
178 *
179 * @param string $type One of 'image', 'not-image'. Any other value will return all mime types.
180 * @return string A comma separated list of mime types.
181 */
182 public static function get_mime_types( $type = null ) {
183 static $mime_types = [];
184
185 if ( empty( $type ) ) {
186 $type = 'all';
187 }
188
189 if ( ! isset( $mime_types[ $type ] ) ) {
190 $mime_types[ $type ] = self::prepare_values_list( imagify_get_mime_types( $type ) );
191 }
192
193 return $mime_types[ $type ];
194 }
195
196 /**
197 * Get post statuses related to attachments, ready to be used in a `IN ()` clause.
198 *
199 * @since 1.7
200 * @access public
201 * @author Grégory Viguier
202 *
203 * @return string A comma separated list of post statuses.
204 */
205 public static function get_post_statuses() {
206 static $statuses;
207
208 if ( ! isset( $statuses ) ) {
209 $statuses = self::prepare_values_list( imagify_get_post_statuses() );
210 }
211
212 return $statuses;
213 }
214
215 /**
216 * Get the SQL JOIN clause to use to get only attachments that have the required WP metadata.
217 * It returns an empty string if the database has no attachments without the required metadada.
218 * It also triggers Imagify_DB::unlimit_joins().
219 *
220 * @param string $id_field An ID field to match the metadata ID against in the JOIN clause.
221 * Default is the posts table `ID` field, using the `p` alias: `p.ID`.
222 * In case of "false" value or PEBKAC, fallback to the same field without alias.
223 * @param bool $matching Set to false to get a query to fetch metas NOT matching the file extensions.
224 * @param bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway.
225 * @param string $special_join_conditions Special conditions to apply on the join.
226 *
227 * @return string
228 * @author Grégory Viguier
229 *
230 * @since 1.7
231 * @access public
232 */
233 public static function get_required_wp_metadata_join_clause( $id_field = 'p.ID', $matching = true, $test = true, $special_join_conditions = '' ) {
234 global $wpdb;
235
236 if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
237 return '';
238 }
239
240 self::unlimit_joins();
241 $clause = '';
242
243 if ( ! $id_field || ! is_string( $id_field ) ) {
244 $id_field = "$wpdb->posts.ID";
245 }
246
247 $join = $matching ? 'INNER' : 'LEFT';
248
249 $first = true;
250
251 foreach ( self::get_required_wp_metadata_aliases() as $meta_name => $alias ) {
252 if ( $first ) {
253 $first = false;
254 $clause .= "
255 $join JOIN $wpdb->postmeta AS $alias
256 ON ( $id_field = $alias.post_id AND $alias.meta_key = '$meta_name' $special_join_conditions )";
257 continue;
258 }
259 $clause .= "
260 $join JOIN $wpdb->postmeta AS $alias
261 ON ( $id_field = $alias.post_id AND $alias.meta_key = '$meta_name' )";
262 }
263
264 return $clause;
265 }
266
267
268 /**
269 * Get the Sub query(exists) clause to use to get only attachments that have the required WP metadata.
270 * It returns an empty string if the database has no attachments without the required metadada.
271 *
272 * @param string $id_field An ID field to match the metadata ID against in the WHERE clause.
273 * Default is the posts table `ID` field, using the `p` alias: `p.ID`.
274 * @param bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway.
275 *
276 * @return string
277 */
278 public static function get_required_wp_metadata_exist_clause( $id_field = 'p.ID', $test = true ) {
279 global $wpdb;
280
281 if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
282 return '';
283 }
284
285 self::unlimit_joins();
286 $clause = '';
287
288 if ( ! $id_field || ! is_string( $id_field ) ) {
289 $id_field = "$wpdb->posts.ID";
290 }
291 $additional_clause = self::get_required_exist_wp_metadata_where_clause(
292 [
293 'matching' => false,
294 'test' => false,
295 ]
296 );
297
298 $first = true;
299
300 foreach ( self::get_required_wp_metadata_aliases() as $meta_name => $alias ) {
301 if ( $first ) {
302 $first = false;
303 $clause .= "
304 EXISTS(
305 SELECT 1 FROM $wpdb->postmeta AS $alias WHERE
306 $alias.post_id = $id_field AND $alias.meta_key = '$meta_name'
307 $additional_clause
308 )
309 ";
310 continue;
311 }
312
313 $clause .= "
314 OR NOT EXISTS (
315 SELECT 1 FROM $wpdb->postmeta AS $alias WHERE
316 $alias.post_id = $id_field AND $alias.meta_key = '$meta_name')";
317 }
318
319 return "AND( $clause )";
320 }
321
322 /**
323 * Get the SQL part to be used in a WHERE clause, to get only attachments that have (in)valid '_wp_attached_file' and '_wp_attachment_metadata' metadatas.
324 * It returns an empty string if the database has no attachments without the required metadada.
325 *
326 * @since 1.7
327 * @since 1.7.1.2 Use a single $arg parameter instead of 3. New $prepared parameter.
328 * @access public
329 * @author Grégory Viguier
330 *
331 * @param array $args {
332 * Optional. An array of arguments.
333 *
334 * string $aliases The aliases to use for the meta values.
335 * bool $matching Set to false to get a query to fetch invalid metas.
336 * bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway.
337 * bool $prepared Set to true if the query will be prepared with using $wpdb->prepare().
338 * }.
339 * @return string A query.
340 */
341 public static function get_required_wp_metadata_where_clause( $args = [] ) {
342 static $query = [];
343
344 $args = imagify_merge_intersect(
345 $args,
346 [
347 'aliases' => [],
348 'matching' => true,
349 'test' => true,
350 'prepared' => false,
351 ]
352 );
353
354 list( $aliases, $matching, $test, $prepared ) = array_values( $args );
355
356 if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
357 return '';
358 }
359
360 if ( $aliases && is_string( $aliases ) ) {
361 $aliases = [
362 '_wp_attached_file' => $aliases,
363 ];
364 } elseif ( ! is_array( $aliases ) ) {
365 $aliases = [];
366 }
367
368 $aliases = imagify_merge_intersect( $aliases, self::get_required_wp_metadata_aliases() );
369 $key = implode( '|', $aliases ) . '|' . (int) $matching;
370
371 if ( isset( $query[ $key ] ) ) {
372 return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
373 }
374
375 unset( $args['prepared'] );
376 $alias_1 = $aliases['_wp_attached_file'];
377 $alias_2 = $aliases['_wp_attachment_metadata'];
378 $extensions = self::get_extensions_where_clause( $args );
379
380 if ( $matching ) {
381 $query[ $key ] = "AND $alias_1.meta_value NOT LIKE '%://%' AND $alias_1.meta_value NOT LIKE '_:\\\\\%' AND $extensions";
382 } else {
383 $query[ $key ] = "AND ( $alias_2.meta_value IS NULL OR $alias_1.meta_value IS NULL OR $alias_1.meta_value LIKE '%://%' OR $alias_1.meta_value LIKE '_:\\\\\%' AND $extensions )";
384 }
385
386 return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
387 }
388
389 /**
390 * Get the SQL part to be used in a WHERE clause, to get only attachments that have (in)valid '_wp_attached_file' and '_wp_attachment_metadata' metadatas.
391 * It returns an empty string if the database has no attachments without the required metadada.
392 *
393 * @param array $args {
394 * Optional. An array of arguments.
395 *
396 * string $aliases The aliases to use for the meta values.
397 * bool $matching Set to false to get a query to fetch invalid metas.
398 * bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway.
399 * bool $prepared Set to true if the query will be prepared with using $wpdb->prepare().
400 * }.
401 * @return string A query.
402 */
403 public static function get_required_exist_wp_metadata_where_clause( $args = [] ) {
404 static $query = [];
405
406 $args = imagify_merge_intersect(
407 $args,
408 [
409 'aliases' => [],
410 'matching' => true,
411 'test' => true,
412 'prepared' => false,
413 ]
414 );
415
416 list( $aliases, $matching, $test, $prepared ) = array_values( $args );
417
418 if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
419 return '';
420 }
421
422 if ( $aliases && is_string( $aliases ) ) {
423 $aliases = [
424 '_wp_attached_file' => $aliases,
425 ];
426 } elseif ( ! is_array( $aliases ) ) {
427 $aliases = [];
428 }
429
430 $aliases = imagify_merge_intersect( $aliases, self::get_required_wp_metadata_aliases() );
431 $key = implode( '|', $aliases ) . '|' . (int) $matching;
432
433 if ( isset( $query[ $key ] ) ) {
434 return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
435 }
436
437 unset( $args['prepared'] );
438 $alias_1 = $aliases['_wp_attached_file'];
439 $extensions = self::get_extensions_where_clause( $args );
440
441 if ( $matching ) {
442 $query[ $key ] = "AND $alias_1.meta_value NOT LIKE '%://%' AND $alias_1.meta_value NOT LIKE '_:\\\\\%' OR NOT ( $extensions )";
443 } else {
444 $query[ $key ] = "AND ( $alias_1.meta_value LIKE '%://%' OR $alias_1.meta_value LIKE '_:\\\\\%' OR NOT ( $extensions ) )";
445 }
446
447 return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
448 }
449
450 /**
451 * Prepare query arguments.
452 *
453 * @param array $args {
454 * Optional. An array of arguments.
455 *
456 * string $aliases The aliases to use for the meta values.
457 * bool $matching Set to false to get a query to fetch invalid metas.
458 * bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway.
459 * bool $prepared Set to true if the query will be prepared with using $wpdb->prepare().
460 * }.
461 *
462 * @return array
463 */
464 private function prepare_query_args( $args ) {
465 return imagify_merge_intersect(
466 $args,
467 [
468 'aliases' => [],
469 'matching' => true,
470 'test' => true,
471 'prepared' => false,
472 ]
473 );
474 }
475
476 /**
477 * Generate query.
478 *
479 * @param bool $matching Matching.
480 * @param string $alias Query alias.
481 * @param string $regex Query Regex.
482 *
483 * @return string
484 */
485 private function generate_query( $matching, $alias, $regex ) {
486 if ( $matching ) {
487 return "REVERSE (LOWER( $alias.meta_value )) REGEXP '$regex'";
488 }
489
490 return "REVERSE (LOWER( $alias.meta_value )) NOT REGEXP '$regex'";
491 }
492
493 /**
494 * Get the SQL part to be used in a WHERE clause, to get only attachments that have a valid file extensions.
495 * It returns an empty string if the database has no attachments without the required metadada.
496 *
497 * @since 1.7
498 * @since 1.7.1.2 Use a single $arg parameter instead of 3. New $prepared parameter.
499 * @access public
500 * @author Grégory Viguier
501 *
502 * @param array $args {
503 * Optional. An array of arguments.
504 *
505 * string $alias The alias to use for the meta value.
506 * bool $matching Set to false to get a query to fetch metas NOT matching the file extensions.
507 * bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway.
508 * bool $prepared Set to true if the query will be prepared with using $wpdb->prepare().
509 * }.
510 * @return string A query.
511 */
512 public static function get_extensions_where_clause( $args = false ) {
513 static $extensions;
514 static $query = [];
515
516 $instance = new self();
517
518 $args = $instance->prepare_query_args( $args );
519
520 list( $alias, $matching, $test, $prepared ) = array_values( $args );
521
522 if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
523 return '';
524 }
525
526 if ( ! isset( $extensions ) ) {
527 $extensions = array_keys( imagify_get_mime_types() );
528 $extensions = implode( '|', $extensions );
529 $extensions = explode( '|', $extensions );
530 $extensions = array_map(
531 function ( $ex ) {
532 return strrev( $ex );
533 },
534 $extensions
535 );
536 }
537
538 if ( ! $alias ) {
539 $alias = self::get_required_wp_metadata_aliases();
540 $alias = $alias['_wp_attached_file'];
541 }
542
543 $key = $alias . '|' . (int) $matching;
544
545 if ( isset( $query[ $key ] ) ) {
546 return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
547 }
548
549 $regex = '^' . implode( '\..*|^', $extensions ) . '\..*';
550
551 $query[ $key ] = $instance->generate_query( $matching, $alias, $regex );
552
553 return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
554 }
555
556 /**
557 * Get the aliases used for the metas in self::get_required_wp_metadata_join_clause(), self::get_required_wp_metadata_where_clause(), and self::get_extensions_where_clause().
558 *
559 * @since 1.7
560 * @access public
561 * @author Grégory Viguier
562 *
563 * @return array An array with the meta name as key and its alias as value.
564 */
565 public static function get_required_wp_metadata_aliases() {
566 return [
567 '_wp_attached_file' => 'imrwpmt1',
568 '_wp_attachment_metadata' => 'imrwpmt2',
569 ];
570 }
571
572 /**
573 * Combine two arrays with some specific keys.
574 * We use this function to combine the result of 2 SQL queries.
575 *
576 * @since 1.6.13
577 * @access public
578 * @author Grégory Viguier
579 *
580 * @param array $keys An array of keys.
581 * @param array $values An array of arrays like array( 'id' => id, 'value' => value ).
582 * @param int $keep_keys_order Set to true to return an array ordered like $keys instead of $values.
583 * @return array The combined arrays.
584 */
585 public static function combine_query_results( $keys, $values, $keep_keys_order = false ) {
586 if ( ! $keys || ! $values ) {
587 return [];
588 }
589
590 $result = [];
591 $keys = array_flip( $keys );
592
593 foreach ( $values as $v ) {
594 if ( isset( $keys[ $v['id'] ] ) ) {
595 $result[ $v['id'] ] = $v['value'];
596 }
597 }
598
599 if ( $keep_keys_order ) {
600 $keys = array_intersect_key( $keys, $result );
601 return array_replace( $keys, $result );
602 }
603
604 return $result;
605 }
606
607 /**
608 * A helper to retrieve all values from one or several post metas, given a list of post IDs.
609 * The $wpdb cache is flushed to save memory.
610 *
611 * @since 1.6.13
612 * @access public
613 * @author Grégory Viguier
614 *
615 * @param array $metas An array of meta names like:
616 * array(
617 * 'key1' => 'meta_name_1',
618 * 'key2' => 'meta_name_2',
619 * 'key3' => 'meta_name_3',
620 * )
621 * If a key contains 'data', the results will be unserialized.
622 * @param array $ids An array of post IDs.
623 * @return array An array of arrays of results like:
624 * array(
625 * 'key1' => array( post_id_1 => 'result_1', post_id_2 => 'result_2', post_id_3 => 'result_3' ),
626 * 'key2' => array( post_id_1 => 'result_4', post_id_3 => 'result_5' ),
627 * 'key3' => array( post_id_1 => 'result_6', post_id_2 => 'result_7' ),
628 * )
629 */
630 public static function get_metas( $metas, $ids ) {
631 global $wpdb;
632
633 if ( ! $ids ) {
634 return array_fill_keys( array_keys( $metas ), [] );
635 }
636
637 $sql_ids = implode( ',', $ids );
638
639 foreach ( $metas as $result_name => $meta_name ) {
640 $metas[ $result_name ] = $wpdb->get_results( // WPCS: unprepared SQL ok.
641 "SELECT pm.post_id as id, pm.meta_value as value
642 FROM $wpdb->postmeta as pm
643 WHERE pm.meta_key = '$meta_name'
644 AND pm.post_id IN ( $sql_ids )
645 ORDER BY pm.post_id DESC",
646 ARRAY_A
647 );
648
649 $wpdb->flush();
650 $metas[ $result_name ] = self::combine_query_results( $ids, $metas[ $result_name ], true );
651
652 if ( strpos( $result_name, 'data' ) !== false ) {
653 $metas[ $result_name ] = array_map( 'maybe_unserialize', $metas[ $result_name ] );
654 }
655 }
656
657 return $metas;
658 }
659
660 /**
661 * Create/Upgrade the table in the database.
662 *
663 * @since 1.7
664 * @access public
665 * @author Grégory Viguier
666 *
667 * @param string $table_name The (prefixed) table name.
668 * @param string $schema_query Query representing the table schema.
669 * @return bool True on success. False otherwise.
670 */
671 public static function create_table( $table_name, $schema_query ) {
672 global $wpdb;
673
674 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
675
676 $wpdb->hide_errors();
677
678 $schema_query = trim( $schema_query );
679 $charset_collate = $wpdb->get_charset_collate();
680
681 dbDelta( "CREATE TABLE $table_name ($schema_query) $charset_collate;" );
682
683 return empty( $wpdb->last_error ) && self::table_exists( $table_name );
684 }
685
686 /**
687 * Tell if the given table exists.
688 *
689 * @since 1.7
690 * @access public
691 * @author Grégory Viguier
692 *
693 * @param string $table_name Full name of the table (with DB prefix).
694 * @return bool
695 */
696 public static function table_exists( $table_name ) {
697 global $wpdb;
698
699 $escaped_table = self::esc_like( $table_name );
700 $result = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $escaped_table ) );
701
702 return $result === $table_name;
703 }
704
705 /**
706 * Cache transients used for optimization process locks.
707 *
708 * @since 1.9
709 * @access public
710 * @author Grégory Viguier
711 *
712 * @param string $context The context.
713 * @param array $media_ids The media IDs.
714 */
715 public static function cache_process_locks( $context, $media_ids ) {
716 global $wpdb;
717
718 if ( ! $context || ! $media_ids || wp_using_ext_object_cache() ) {
719 return;
720 }
721
722 // Sanitize the IDs.
723 $media_ids = array_filter( $media_ids );
724 $media_ids = array_unique( $media_ids );
725
726 if ( ! $media_ids ) {
727 return;
728 }
729
730 $context_instance = imagify_get_context( $context );
731 $context = $context_instance->get_name();
732 $process_class_name = imagify_get_optimization_process_class_name( $context );
733 $transient_name = sprintf( $process_class_name::LOCK_NAME, $context, '%' );
734 $is_network_wide = $context_instance->is_network_wide();
735
736 // Do 1 DB query per context (and cache results) before doing 1 get_transient() (2 DB queries) per media ID.
737 $prefix = $is_network_wide ? '_site_transient_' : '_transient_';
738
739 if ( $is_network_wide && is_multisite() ) {
740 $network_id = function_exists( 'get_current_network_id' ) ? get_current_network_id() : (int) $wpdb->siteid;
741 $cache_prefix = "$network_id:";
742 $notoptions_key = "$network_id:notoptions";
743 $cache_group = 'site-options';
744 $results = $wpdb->get_results(
745 $wpdb->prepare(
746 "SELECT meta_key as name, meta_value as value FROM $wpdb->sitemeta WHERE ( meta_key LIKE %s OR meta_key LIKE %s ) AND site_id = %d",
747 $prefix . $transient_name,
748 $prefix . 'timeout_' . $transient_name,
749 $network_id
750 ),
751 OBJECT_K
752 ); // WPCS: unprepared SQL ok.
753 } else {
754 $cache_prefix = '';
755 $notoptions_key = 'notoptions';
756 $cache_group = 'options';
757 $results = $wpdb->get_results(
758 $wpdb->prepare(
759 "SELECT option_name as name, option_value as value FROM $wpdb->options WHERE ( option_name LIKE %s OR option_name LIKE %s )",
760 $prefix . $transient_name,
761 $prefix . 'timeout_' . $transient_name
762 ),
763 OBJECT_K
764 ); // WPCS: unprepared SQL ok.
765 }
766
767 $not_exist = [];
768
769 foreach ( [ '', 'timeout_' ] as $maybe_timeout ) {
770 foreach ( $media_ids as $id ) {
771 $option_name = $prefix . $maybe_timeout . str_replace( '%', $id, $transient_name );
772
773 if ( isset( $results[ $option_name ] ) ) {
774 // Cache the value.
775 $value = $results[ $option_name ]->value;
776 $value = maybe_unserialize( $value );
777 wp_cache_set( "$cache_prefix$option_name", $value, $cache_group );
778 } else {
779 // No value.
780 $not_exist[ $option_name ] = true;
781 }
782 }
783 }
784
785 if ( ! $not_exist ) {
786 return;
787 }
788
789 // Cache the options that don't exist in the DB.
790 $notoptions = wp_cache_get( $notoptions_key, $cache_group );
791 $notoptions = is_array( $notoptions ) ? $notoptions : [];
792 $notoptions = array_merge( $notoptions, $not_exist );
793
794 wp_cache_set( $notoptions_key, $notoptions, $cache_group );
795 }
796 }
797