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
← All changes | inc/classes/class-imagify-db.php +272 -40 1.10 → trunk View file →
@@ -1,6 +1,5 @@
1 1 <?php
2 -defined( 'ABSPATH' ) || die( 'Cheatin’ uh?' );
3 2
4 3 /**
5 4 * Imagify DB class. It reunites tools to work with the DB.
6 5 *
@@ -60,13 +59,72 @@
60 59 * @return string A comma separated list of values.
61 60 */
62 61 public static function prepare_values_list( $values ) {
63 62 $values = esc_sql( (array) $values );
64 - $values = array_map( array( __CLASS__, 'quote_string' ), $values );
63 + $values = array_map( [ __CLASS__, 'quote_string' ], $values );
65 64 return implode( ',', $values );
66 65 }
67 66
68 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 + /**
69 127 * Wrap a value in quotes, unless it's an integer.
70 128 *
71 129 * @since 1.6.13
72 130 * @access public
@@ -158,20 +216,22 @@
158 216 * Get the SQL JOIN clause to use to get only attachments that have the required WP metadata.
159 217 * It returns an empty string if the database has no attachments without the required metadada.
160 218 * It also triggers Imagify_DB::unlimit_joins().
161 219 *
162 - * @since 1.7
163 - * @access public
164 - * @author Grégory Viguier
165 - *
166 - * @param string $id_field An ID field to match the metadata ID against in the JOIN clause.
220 + * @param string $id_field An ID field to match the metadata ID against in the JOIN clause.
167 221 * Default is the posts table `ID` field, using the `p` alias: `p.ID`.
168 222 * In case of "false" value or PEBKAC, fallback to the same field without alias.
169 - * @param bool $matching Set to false to get a query to fetch metas NOT matching the file extensions.
170 - * @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.
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 + *
171 227 * @return string
228 + * @author Grégory Viguier
229 + *
230 + * @since 1.7
231 + * @access public
172 232 */
173 - public static function get_required_wp_metadata_join_clause( $id_field = 'p.ID', $matching = true, $test = true ) {
233 + public static function get_required_wp_metadata_join_clause( $id_field = 'p.ID', $matching = true, $test = true, $special_join_conditions = '' ) {
174 234 global $wpdb;
175 235
176 236 if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
177 237 return '';
@@ -185,9 +245,18 @@
185 245 }
186 246
187 247 $join = $matching ? 'INNER' : 'LEFT';
188 248
249 + $first = true;
250 +
189 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 + }
190 259 $clause .= "
191 260 $join JOIN $wpdb->postmeta AS $alias
192 261 ON ( $id_field = $alias.post_id AND $alias.meta_key = '$meta_name' )";
193 262 }
@@ -194,9 +263,64 @@
194 263
195 264 return $clause;
196 265 }
197 266
267 +
198 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 + /**
199 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.
200 324 * It returns an empty string if the database has no attachments without the required metadada.
201 325 *
202 326 * @since 1.7
@@ -213,17 +337,20 @@
213 337 * bool $prepared Set to true if the query will be prepared with using $wpdb->prepare().
214 338 * }.
215 339 * @return string A query.
216 340 */
217 - public static function get_required_wp_metadata_where_clause( $args = array() ) {
218 - static $query = array();
341 + public static function get_required_wp_metadata_where_clause( $args = [] ) {
342 + static $query = [];
219 343
220 - $args = imagify_merge_intersect( $args, array(
221 - 'aliases' => array(),
222 - 'matching' => true,
223 - 'test' => true,
224 - 'prepared' => false,
225 - ) );
344 + $args = imagify_merge_intersect(
345 + $args,
346 + [
347 + 'aliases' => [],
348 + 'matching' => true,
349 + 'test' => true,
350 + 'prepared' => false,
351 + ]
352 + );
226 353
227 354 list( $aliases, $matching, $test, $prepared ) = array_values( $args );
228 355
229 356 if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
@@ -230,13 +357,13 @@
230 357 return '';
231 358 }
232 359
233 360 if ( $aliases && is_string( $aliases ) ) {
234 - $aliases = array(
361 + $aliases = [
235 362 '_wp_attached_file' => $aliases,
236 - );
363 + ];
237 364 } elseif ( ! is_array( $aliases ) ) {
238 - $aliases = array();
365 + $aliases = [];
239 366 }
240 367
241 368 $aliases = imagify_merge_intersect( $aliases, self::get_required_wp_metadata_aliases() );
242 369 $key = implode( '|', $aliases ) . '|' . (int) $matching;
@@ -250,11 +377,11 @@
250 377 $alias_2 = $aliases['_wp_attachment_metadata'];
251 378 $extensions = self::get_extensions_where_clause( $args );
252 379
253 380 if ( $matching ) {
254 - $query[ $key ] = "AND $alias_1.meta_value NOT LIKE '%://%' AND $alias_1.meta_value NOT LIKE '_:\\\\\%' $extensions";
381 + $query[ $key ] = "AND $alias_1.meta_value NOT LIKE '%://%' AND $alias_1.meta_value NOT LIKE '_:\\\\\%' AND $extensions";
255 382 } else {
256 - $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 '_:\\\\\%' $extensions )";
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 )";
257 384 }
258 385
259 386 return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
260 387 }
@@ -259,8 +386,112 @@
259 386 return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
260 387 }
261 388
262 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 + /**
263 494 * Get the SQL part to be used in a WHERE clause, to get only attachments that have a valid file extensions.
264 495 * It returns an empty string if the database has no attachments without the required metadada.
265 496 *
266 497 * @since 1.7
@@ -279,17 +510,14 @@
279 510 * @return string A query.
280 511 */
281 512 public static function get_extensions_where_clause( $args = false ) {
282 513 static $extensions;
283 - static $query = array();
514 + static $query = [];
284 515
285 - $args = imagify_merge_intersect( $args, array(
286 - 'alias' => array(),
287 - 'matching' => true,
288 - 'test' => true,
289 - 'prepared' => false,
290 - ) );
516 + $instance = new self();
291 517
518 + $args = $instance->prepare_query_args( $args );
519 +
292 520 list( $alias, $matching, $test, $prepared ) = array_values( $args );
293 521
294 522 if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
295 523 return '';
@@ -298,8 +526,14 @@
298 526 if ( ! isset( $extensions ) ) {
299 527 $extensions = array_keys( imagify_get_mime_types() );
300 528 $extensions = implode( '|', $extensions );
301 529 $extensions = explode( '|', $extensions );
530 + $extensions = array_map(
531 + function ( $ex ) {
532 + return strrev( $ex );
533 + },
534 + $extensions
535 + );
302 536 }
303 537
304 538 if ( ! $alias ) {
305 539 $alias = self::get_required_wp_metadata_aliases();
@@ -311,14 +545,12 @@
311 545 if ( isset( $query[ $key ] ) ) {
312 546 return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
313 547 }
314 548
315 - if ( $matching ) {
316 - $query[ $key ] = "AND ( LOWER( $alias.meta_value ) LIKE '%." . implode( "' OR LOWER( $alias.meta_value ) LIKE '%.", $extensions ) . "' )";
317 - } else {
318 - $query[ $key ] = "OR ( LOWER( $alias.meta_value ) NOT LIKE '%." . implode( "' AND LOWER( $alias.meta_value ) NOT LIKE '%.", $extensions ) . "' )";
319 - }
549 + $regex = '^' . implode( '\..*|^', $extensions ) . '\..*';
320 550
551 + $query[ $key ] = $instance->generate_query( $matching, $alias, $regex );
552 +
321 553 return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
322 554 }
323 555
324 556 /**
@@ -330,12 +562,12 @@
330 562 *
331 563 * @return array An array with the meta name as key and its alias as value.
332 564 */
333 565 public static function get_required_wp_metadata_aliases() {
334 - return array(
566 + return [
335 567 '_wp_attached_file' => 'imrwpmt1',
336 568 '_wp_attachment_metadata' => 'imrwpmt2',
337 - );
569 + ];
338 570 }
339 571
340 572 /**
341 573 * Combine two arrays with some specific keys.
@@ -351,12 +583,12 @@
351 583 * @return array The combined arrays.
352 584 */
353 585 public static function combine_query_results( $keys, $values, $keep_keys_order = false ) {
354 586 if ( ! $keys || ! $values ) {
355 - return array();
587 + return [];
356 588 }
357 589
358 - $result = array();
590 + $result = [];
359 591 $keys = array_flip( $keys );
360 592
361 593 foreach ( $values as $v ) {
362 594 if ( isset( $keys[ $v['id'] ] ) ) {
@@ -398,9 +630,9 @@
398 630 public static function get_metas( $metas, $ids ) {
399 631 global $wpdb;
400 632
401 633 if ( ! $ids ) {
402 - return array_fill_keys( array_keys( $metas ), array() );
634 + return array_fill_keys( array_keys( $metas ), [] );
403 635 }
404 636
405 637 $sql_ids = implode( ',', $ids );
406 638