PluginProbe ʕ •ᴥ•ʔ
LiteSpeed Cache / 7.8.1
LiteSpeed Cache v7.8.1
trunk 1.0.15 1.9.1.1 2.9.9.2 3.6.4 4.6 5.7.0.1 6.5.4 7.0.0.1 7.0.1 7.1 7.2 7.3 7.3.0.1 7.4 7.5 7.5.0.1 7.6 7.6.1 7.6.2 7.7 7.8 7.8.0.1 7.8.1
litespeed-cache / src / img-optm-send.trait.php
litespeed-cache / src Last commit date
cdn 2 months ago data_structure 2 months ago activation.cls.php 2 months ago admin-display.cls.php 2 months ago admin-settings.cls.php 2 months ago admin.cls.php 2 months ago api.cls.php 2 months ago avatar.cls.php 2 months ago base.cls.php 2 months ago cdn.cls.php 2 months ago cloud-auth-callback.trait.php 2 months ago cloud-auth-ip.trait.php 2 months ago cloud-auth.trait.php 2 months ago cloud-misc.trait.php 2 months ago cloud-node.trait.php 2 months ago cloud-request.trait.php 2 months ago cloud.cls.php 2 months ago conf.cls.php 2 months ago control.cls.php 2 months ago core.cls.php 2 months ago crawler-map.cls.php 2 months ago crawler.cls.php 2 months ago css.cls.php 2 months ago data.cls.php 2 months ago data.upgrade.func.php 2 months ago db-optm.cls.php 2 months ago debug2.cls.php 2 months ago doc.cls.php 2 months ago error.cls.php 2 months ago esi.cls.php 2 months ago file.cls.php 2 months ago guest.cls.php 2 months ago gui.cls.php 2 months ago health.cls.php 2 months ago htaccess.cls.php 2 months ago img-optm-manage.trait.php 2 months ago img-optm-pull.trait.php 2 months ago img-optm-send.trait.php 2 months ago img-optm.cls.php 2 months ago import.cls.php 2 months ago import.preset.cls.php 2 months ago lang.cls.php 2 months ago localization.cls.php 2 months ago media.cls.php 2 months ago metabox.cls.php 2 months ago object-cache-wp.cls.php 2 months ago object-cache.cls.php 2 months ago object.lib.php 2 months ago optimize.cls.php 2 months ago optimizer.cls.php 2 months ago placeholder.cls.php 2 months ago purge.cls.php 2 months ago report.cls.php 2 months ago rest.cls.php 2 months ago root.cls.php 2 months ago router.cls.php 2 months ago str.cls.php 2 months ago tag.cls.php 2 months ago task.cls.php 2 months ago tool.cls.php 2 months ago ucss.cls.php 2 months ago utility.cls.php 2 months ago vary.cls.php 2 months ago vpi.cls.php 2 months ago
img-optm-send.trait.php
704 lines
1 <?php
2 /**
3 * Image optimization send trait
4 *
5 * @package LiteSpeed
6 * @since 7.8
7 */
8
9 namespace LiteSpeed;
10
11 defined( 'WPINC' ) || exit();
12
13 /**
14 * Trait Img_Optm_Send
15 *
16 * Handles image optimization request sending.
17 */
18 trait Img_Optm_Send {
19
20 /**
21 * Gather images auto when update attachment meta
22 * This is to optimize new uploaded images first. Stored in img_optm table.
23 * Later normal process will auto remove these records when trying to optimize these images again
24 *
25 * @since 4.0
26 * @param array $meta_value The meta value array.
27 * @param int $post_id The post ID.
28 */
29 public function wp_update_attachment_metadata( $meta_value, $post_id ) {
30 global $wpdb;
31
32 self::debug2( '🖌️ Auto update attachment meta [id] ' . $post_id );
33 if ( empty( $meta_value['file'] ) ) {
34 return;
35 }
36
37 // Load gathered images
38 if ( ! $this->_existed_src_list ) {
39 // To aavoid extra query when recalling this function
40 self::debug( 'SELECT src from img_optm table' );
41 if ( $this->__data->tb_exist( 'img_optm' ) ) {
42 $q = "SELECT src FROM `$this->_table_img_optm` WHERE post_id = %d";
43 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
44 $list = $wpdb->get_results( $wpdb->prepare( $q, $post_id ) );
45 foreach ( $list as $v ) {
46 $this->_existed_src_list[] = $post_id . '.' . $v->src;
47 }
48 }
49 if ( $this->__data->tb_exist( 'img_optming' ) ) {
50 $q = "SELECT src FROM `$this->_table_img_optming` WHERE post_id = %d";
51 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
52 $list = $wpdb->get_results( $wpdb->prepare( $q, $post_id ) );
53 foreach ( $list as $v ) {
54 $this->_existed_src_list[] = $post_id . '.' . $v->src;
55 }
56 } else {
57 $this->__data->tb_create( 'img_optming' );
58 }
59 }
60
61 // Prepare images
62 $this->tmp_pid = $post_id;
63 $this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/';
64 $this->_append_img_queue( $meta_value, true );
65 if ( ! empty( $meta_value['sizes'] ) ) {
66 foreach ( $meta_value['sizes'] as $img_size_name => $img_size ) {
67 $this->_append_img_queue( $img_size, false, $img_size_name );
68 }
69 }
70
71 if ( ! $this->_img_in_queue ) {
72 self::debug( 'auto update attachment meta 2 bypass: empty _img_in_queue' );
73 return;
74 }
75
76 // Save to DB
77 $this->_save_raw();
78
79 // $this->_send_request();
80 }
81
82 /**
83 * Auto send optm request
84 *
85 * @since 2.4.1
86 * @access public
87 */
88 public static function cron_auto_request() {
89 if ( ! wp_doing_cron() ) {
90 return false;
91 }
92
93 $instance = self::cls();
94 $instance->new_req();
95 }
96
97 /**
98 * Calculate wet run allowance
99 *
100 * @since 3.0
101 * @return int|false The wet limit or false if no limit.
102 */
103 public function wet_limit() {
104 $wet_limit = 1;
105 if ( ! empty( $this->_summary['img_taken'] ) ) {
106 $wet_limit = pow( $this->_summary['img_taken'], 2 );
107 }
108
109 if ( 1 === $wet_limit && ! empty( $this->_summary[ 'img_status.' . self::STATUS_ERR_OPTM ] ) ) {
110 $wet_limit = pow( $this->_summary[ 'img_status.' . self::STATUS_ERR_OPTM ], 2 );
111 }
112
113 if ( $wet_limit < Cloud::IMG_OPTM_DEFAULT_GROUP ) {
114 return $wet_limit;
115 }
116
117 // No limit
118 return false;
119 }
120
121 /**
122 * Push raw img to image optm server
123 *
124 * @since 1.6
125 * @access public
126 */
127 public function new_req() {
128 global $wpdb;
129
130 // check if is running
131 if ( ! empty( $this->_summary['is_running'] ) && time() - $this->_summary['is_running'] < apply_filters( 'litespeed_imgoptm_new_req_interval', 3600 ) ) {
132 self::debug( 'The previous req was in 3600s.' );
133 return;
134 }
135 $this->_summary['is_running'] = time();
136 self::save_summary();
137
138 // Check if has credit to push
139 $err = false;
140 $allowance = Cloud::cls()->allowance( Cloud::SVC_IMG_OPTM, $err );
141
142 $wet_limit = $this->wet_limit();
143
144 self::debug( "allowance_max $allowance wet_limit $wet_limit" );
145 if ( $wet_limit && $wet_limit < $allowance ) {
146 $allowance = $wet_limit;
147 }
148
149 if ( ! $allowance ) {
150 self::debug( '❌ No credit' );
151 Admin_Display::error( Error::msg( $err ) );
152 $this->_finished_running();
153 return;
154 }
155
156 self::debug( 'preparing images to push' );
157
158 $this->__data->tb_create( 'img_optming' );
159
160 $q = "SELECT COUNT(1) FROM `$this->_table_img_optming` WHERE optm_status = %d";
161 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
162 $q = $wpdb->prepare( $q, [ self::STATUS_REQUESTED ] );
163 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
164 $total_requested = $wpdb->get_var( $q );
165 $max_requested = $allowance * 1;
166
167 if ( $total_requested > $max_requested ) {
168 self::debug( '❌ Too many queued images (' . $total_requested . ' > ' . $max_requested . ')' );
169 Admin_Display::error( Error::msg( 'too_many_requested' ) );
170 $this->_finished_running();
171 return;
172 }
173
174 $allowance -= $total_requested;
175
176 if ( $allowance < 1 ) {
177 self::debug( '❌ Too many requested images ' . $total_requested );
178 Admin_Display::error( Error::msg( 'too_many_requested' ) );
179 $this->_finished_running();
180 return;
181 }
182
183 // Limit maximum number of items waiting to be pulled
184 $q = "SELECT COUNT(1) FROM `$this->_table_img_optming` WHERE optm_status = %d";
185 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
186 $q = $wpdb->prepare( $q, [ self::STATUS_NOTIFIED ] );
187 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
188 $total_notified = $wpdb->get_var( $q );
189 if ( $total_notified > 0 ) {
190 self::debug( '❌ Too many notified images (' . $total_notified . ')' );
191 Admin_Display::error( Error::msg( 'too_many_notified' ) );
192 $this->_finished_running();
193 return;
194 }
195
196 $q = "SELECT COUNT(1) FROM `$this->_table_img_optming` WHERE optm_status IN (%d, %d)";
197 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
198 $q = $wpdb->prepare( $q, [ self::STATUS_NEW, self::STATUS_RAW ] );
199 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
200 $total_new = $wpdb->get_var( $q );
201 // $allowance -= $total_new;
202
203 // May need to get more images
204 $list = [];
205 $more = $allowance - $total_new;
206 if ( $more > 0 ) {
207 $q = "SELECT b.post_id, b.meta_value
208 FROM `$wpdb->posts` a
209 LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID
210 WHERE b.meta_key = '_wp_attachment_metadata'
211 AND a.post_type = 'attachment'
212 AND a.post_status = 'inherit'
213 AND a.ID>%d
214 AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif')
215 ORDER BY a.ID
216 LIMIT %d
217 ";
218 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
219 $q = $wpdb->prepare( $q, [ $this->_summary['next_post_id'], $more ] );
220 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
221 $list = $wpdb->get_results( $q );
222 foreach ( $list as $v ) {
223 if ( ! $v->post_id ) {
224 continue;
225 }
226
227 $this->_summary['next_post_id'] = $v->post_id;
228
229 $meta_value = $this->_parse_wp_meta_value( $v );
230 if ( ! $meta_value ) {
231 continue;
232 }
233 $meta_value['file'] = wp_normalize_path( $meta_value['file'] );
234 $basedir = $this->wp_upload_dir['basedir'] . '/';
235 if ( strpos( $meta_value['file'], $basedir ) === 0 ) {
236 $meta_value['file'] = substr( $meta_value['file'], strlen( $basedir ) );
237 }
238
239 $this->tmp_pid = $v->post_id;
240 $this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/';
241 $this->_append_img_queue( $meta_value, true );
242 if ( ! empty( $meta_value['sizes'] ) ) {
243 foreach ( $meta_value['sizes'] as $img_size_name => $img_size ) {
244 $this->_append_img_queue( $img_size, false, $img_size_name );
245 }
246 }
247 }
248
249 self::save_summary();
250
251 $num_a = count( $this->_img_in_queue );
252 self::debug( 'Images found: ' . $num_a );
253 $this->_filter_duplicated_src();
254 self::debug( 'Images after duplicated: ' . count( $this->_img_in_queue ) );
255 $this->_filter_invalid_src();
256 self::debug( 'Images after invalid: ' . count( $this->_img_in_queue ) );
257 // Check w/ legacy imgoptm table, bypass finished images
258 $this->_filter_legacy_src();
259
260 $num_b = count( $this->_img_in_queue );
261 if ( $num_b !== $num_a ) {
262 self::debug( 'Images after filtered duplicated/invalid/legacy src: ' . $num_b );
263 }
264
265 // Save to DB
266 $this->_save_raw();
267 }
268
269 // Push to Cloud server
270 $accepted_imgs = $this->_send_request( $allowance );
271
272 $this->_finished_running();
273 if ( ! $accepted_imgs ) {
274 return;
275 }
276
277 $placeholder1 = Admin_Display::print_plural( $accepted_imgs[0], 'image' );
278 $placeholder2 = Admin_Display::print_plural( $accepted_imgs[1], 'image' );
279 $msg = sprintf( __( 'Pushed %1$s to Cloud server, accepted %2$s.', 'litespeed-cache' ), $placeholder1, $placeholder2 );
280 Admin_Display::success( $msg );
281 }
282
283 /**
284 * Set running to done
285 *
286 * @since 3.0
287 * @access private
288 */
289 private function _finished_running() {
290 $this->_summary['is_running'] = 0;
291 self::save_summary();
292 }
293
294 /**
295 * Add a new img to queue which will be pushed to request
296 *
297 * @since 1.6
298 * @since 7.5 Allow to choose which image sizes should be optimized + added parameter $img_size_name.
299 * @access private
300 * @param array $meta_value The meta value array.
301 * @param bool $is_ori_file Whether this is the original file.
302 * @param string|bool $img_size_name The image size name or false.
303 */
304 private function _append_img_queue( $meta_value, $is_ori_file = false, $img_size_name = false ) {
305 if ( empty( $meta_value['file'] ) || empty( $meta_value['width'] ) || empty( $meta_value['height'] ) ) {
306 self::debug2( 'bypass image due to lack of file/w/h: pid ' . $this->tmp_pid, $meta_value );
307 return;
308 }
309
310 $short_file_path = $meta_value['file'];
311
312 // Test if need to skip image size.
313 if ( ! $is_ori_file ) {
314 $short_file_path = $this->tmp_path . $short_file_path;
315 $skip = false !== array_search( $img_size_name, $this->_sizes_skipped, true );
316 if ( $skip ) {
317 self::debug2( 'bypass image ' . $short_file_path . ' due to skipped size: ' . $img_size_name );
318 return;
319 }
320 }
321
322 // Check if src is gathered already or not
323 if ( in_array( $this->tmp_pid . '.' . $short_file_path, $this->_existed_src_list, true ) ) {
324 // Debug2::debug2( '[Img_Optm] bypass image due to gathered: pid ' . $this->tmp_pid . ' ' . $short_file_path );
325 return;
326 } else {
327 // Append handled images
328 $this->_existed_src_list[] = $this->tmp_pid . '.' . $short_file_path;
329 }
330
331 // check file exists or not
332 $_img_info = $this->__media->info( $short_file_path, $this->tmp_pid );
333
334 $extension = pathinfo( $short_file_path, PATHINFO_EXTENSION );
335 if ( ! $_img_info || ! in_array( $extension, [ 'jpg', 'jpeg', 'png', 'gif' ], true ) ) {
336 self::debug2( 'bypass image due to file not exist: pid ' . $this->tmp_pid . ' ' . $short_file_path );
337 return;
338 }
339
340 // Check if optimized file exists or not
341 $target_needed = false;
342 if ( $this->_format ) {
343 $target_file_path = $short_file_path . '.' . $this->_format;
344 if ( ! $this->__media->info( $target_file_path, $this->tmp_pid ) ) {
345 $target_needed = true;
346 }
347 }
348 if ( $this->conf( self::O_IMG_OPTM_ORI ) ) {
349 $target_file_path = substr( $short_file_path, 0, -strlen( $extension ) ) . 'bk.' . $extension;
350 if ( ! $this->__media->info( $target_file_path, $this->tmp_pid ) ) {
351 $target_needed = true;
352 }
353 }
354 if ( ! $target_needed ) {
355 self::debug2( 'bypass image due to optimized file exists: pid ' . $this->tmp_pid . ' ' . $short_file_path );
356 return;
357 }
358
359 // Debug2::debug2( '[Img_Optm] adding image: pid ' . $this->tmp_pid );
360
361 $this->_img_in_queue[] = [
362 'pid' => $this->tmp_pid,
363 'md5' => $_img_info['md5'],
364 'url' => $_img_info['url'],
365 'src' => $short_file_path, // not needed in LiteSpeed IAPI, just leave for local storage after post
366 'mime_type' => ! empty( $meta_value['mime-type'] ) ? $meta_value['mime-type'] : '',
367 ];
368 }
369
370 /**
371 * Save gathered image raw data
372 *
373 * @since 3.0
374 * @access private
375 */
376 private function _save_raw() {
377 if ( empty( $this->_img_in_queue ) ) {
378 return;
379 }
380 $data = [];
381 $pid_list = [];
382 foreach ( $this->_img_in_queue as $k => $v ) {
383 $_img_info = $this->__media->info( $v['src'], $v['pid'] );
384
385 // attachment doesn't exist, delete the record
386 if ( empty( $_img_info['url'] ) || empty( $_img_info['md5'] ) ) {
387 unset( $this->_img_in_queue[ $k ] );
388 continue;
389 }
390 $pid_list[] = (int) $v['pid'];
391
392 $data[] = $v['pid'];
393 $data[] = self::STATUS_RAW;
394 $data[] = $v['src'];
395 }
396
397 global $wpdb;
398 $fields = 'post_id, optm_status, src';
399 $q = "INSERT INTO `$this->_table_img_optming` ( $fields ) VALUES ";
400
401 // Add placeholder
402 $q .= Utility::chunk_placeholder( $data, $fields );
403
404 // Store data
405 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
406 $wpdb->query( $wpdb->prepare( $q, $data ) );
407
408 $count = count( $this->_img_in_queue );
409 self::debug( 'Added raw images [total] ' . $count );
410
411 $this->_img_in_queue = [];
412
413 // Save thumbnail groups for future rescan index
414 $this->_gen_thumbnail_set();
415
416 $pid_list = array_unique( $pid_list );
417 self::debug( 'pid list to append to postmeta', $pid_list );
418 $pid_list = array_diff( $pid_list, $this->_pids_set );
419 $this->_pids_set = array_merge( $this->_pids_set, $pid_list );
420
421 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
422 $existed_meta = $wpdb->get_results( "SELECT * FROM `$wpdb->postmeta` WHERE post_id IN ('" . implode( "','", $pid_list ) . "') AND meta_key='" . self::DB_SET . "'" );
423 $existed_pid = [];
424 if ( $existed_meta ) {
425 foreach ( $existed_meta as $v ) {
426 $existed_pid[] = $v->post_id;
427 }
428 self::debug( 'pid list to update postmeta', $existed_pid );
429 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
430 $wpdb->query(
431 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $existed_pid is array of sanitized IDs
432 $wpdb->prepare( "UPDATE `$wpdb->postmeta` SET meta_value=%s WHERE post_id IN (" . implode( ',', $existed_pid ) . ') AND meta_key=%s', [
433 $this->_thumbnail_set,
434 self::DB_SET,
435 ] )
436 );
437 }
438
439 // Add new meta
440 $new_pids = $existed_pid ? array_diff( $pid_list, $existed_pid ) : $pid_list;
441 if ( $new_pids ) {
442 self::debug( 'pid list to update postmeta', $new_pids );
443 foreach ( $new_pids as $v ) {
444 self::debug( 'New group set info [pid] ' . $v );
445 $q = "INSERT INTO `$wpdb->postmeta` (post_id, meta_key, meta_value) VALUES (%d, %s, %s)";
446 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
447 $wpdb->query( $wpdb->prepare( $q, [ $v, self::DB_SET, $this->_thumbnail_set ] ) );
448 }
449 }
450 }
451
452 /**
453 * Generate thumbnail sets of current image group
454 *
455 * @since 5.4
456 * @access private
457 */
458 private function _gen_thumbnail_set() {
459 if ( $this->_thumbnail_set ) {
460 return;
461 }
462 $set = [];
463 foreach ( Media::cls()->get_image_sizes() as $size ) {
464 $curr_size = $size['width'] . 'x' . $size['height'];
465 if ( in_array( $curr_size, $set, true ) ) {
466 continue;
467 }
468 $set[] = $curr_size;
469 }
470 $this->_thumbnail_set = implode( PHP_EOL, $set );
471 }
472
473 /**
474 * Filter duplicated src in work table and $this->_img_in_queue, then mark them as duplicated
475 *
476 * @since 2.0
477 * @access private
478 */
479 private function _filter_duplicated_src() {
480 global $wpdb;
481
482 $srcpath_list = [];
483
484 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
485 $list = $wpdb->get_results( "SELECT src FROM `$this->_table_img_optming`" );
486 foreach ( $list as $v ) {
487 $srcpath_list[] = $v->src;
488 }
489
490 foreach ( $this->_img_in_queue as $k => $v ) {
491 if ( in_array( $v['src'], $srcpath_list, true ) ) {
492 unset( $this->_img_in_queue[ $k ] );
493 continue;
494 }
495
496 $srcpath_list[] = $v['src'];
497 }
498 }
499
500 /**
501 * Filter legacy finished ones
502 *
503 * @since 5.4
504 * @access private
505 */
506 private function _filter_legacy_src() {
507 global $wpdb;
508
509 if ( ! $this->__data->tb_exist( 'img_optm' ) ) {
510 return;
511 }
512
513 if ( ! $this->_img_in_queue ) {
514 return;
515 }
516
517 $finished_ids = [];
518
519 Utility::compatibility();
520 $post_ids = array_unique( array_column( $this->_img_in_queue, 'pid' ) );
521 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
522 $list = $wpdb->get_results( "SELECT post_id FROM `$this->_table_img_optm` WHERE post_id in (" . implode( ',', $post_ids ) . ') GROUP BY post_id' );
523 foreach ( $list as $v ) {
524 $finished_ids[] = $v->post_id;
525 }
526
527 foreach ( $this->_img_in_queue as $k => $v ) {
528 if ( in_array( $v['pid'], $finished_ids, true ) ) {
529 self::debug( 'Legacy image optimized [pid] ' . $v['pid'] );
530 unset( $this->_img_in_queue[ $k ] );
531 continue;
532 }
533 }
534
535 // Drop all existing legacy records
536 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
537 $wpdb->query( "DELETE FROM `$this->_table_img_optm` WHERE post_id in (" . implode( ',', $post_ids ) . ')' );
538 }
539
540 /**
541 * Filter the invalid src before sending
542 *
543 * @since 3.0.8.3
544 * @access private
545 */
546 private function _filter_invalid_src() {
547 $img_in_queue_invalid = [];
548 foreach ( $this->_img_in_queue as $k => $v ) {
549 if ( $v['src'] ) {
550 $extension = pathinfo( $v['src'], PATHINFO_EXTENSION );
551 }
552 if ( ! $v['src'] || empty( $extension ) || ! in_array( $extension, [ 'jpg', 'jpeg', 'png', 'gif' ], true ) ) {
553 $img_in_queue_invalid[] = $v['id'];
554 unset( $this->_img_in_queue[ $k ] );
555 continue;
556 }
557 }
558
559 if ( ! $img_in_queue_invalid ) {
560 return;
561 }
562
563 $count = count( $img_in_queue_invalid );
564 $msg = sprintf( __( 'Cleared %1$s invalid images.', 'litespeed-cache' ), $count );
565 Admin_Display::success( $msg );
566
567 self::debug( 'Found invalid src [total] ' . $count );
568 }
569
570 /**
571 * Push img request to Cloud server
572 *
573 * @since 1.6.7
574 * @access private
575 * @param int $allowance The allowance limit.
576 * @return array|void Array with pushed and accepted counts.
577 */
578 private function _send_request( $allowance ) {
579 global $wpdb;
580
581 $q = "SELECT id, src, post_id FROM `$this->_table_img_optming` WHERE optm_status=%d LIMIT %d";
582 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
583 $q = $wpdb->prepare( $q, [ self::STATUS_RAW, $allowance ] );
584 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
585 $_img_in_queue = $wpdb->get_results( $q );
586 if ( ! $_img_in_queue ) {
587 return;
588 }
589
590 self::debug( 'Load img in queue [total] ' . count( $_img_in_queue ) );
591
592 $list = [];
593 foreach ( $_img_in_queue as $v ) {
594 $_img_info = $this->__media->info( $v->src, $v->post_id );
595 // If record is invalid, remove from img_optming table
596 if ( empty( $_img_info['url'] ) || empty( $_img_info['md5'] ) ) {
597 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
598 $wpdb->query( $wpdb->prepare( "DELETE FROM `$this->_table_img_optming` WHERE id=%d", $v->id ) );
599 continue;
600 }
601
602 $img = [
603 'id' => $v->id,
604 'url' => $_img_info['url'],
605 'md5' => $_img_info['md5'],
606 ];
607 // Build the needed image types for request as we now support soft reset counter
608 if ( $this->_format ) {
609 $target_file_path = $v->src . '.' . $this->_format;
610 if ( $this->__media->info( $target_file_path, $v->post_id ) ) {
611 $img[ 'optm_' . $this->_format ] = 0;
612 }
613 }
614 if ( $this->conf( self::O_IMG_OPTM_ORI ) ) {
615 $extension = pathinfo( $v->src, PATHINFO_EXTENSION );
616 $target_file_path = substr( $v->src, 0, -strlen( $extension ) ) . 'bk.' . $extension;
617 if ( $this->__media->info( $target_file_path, $v->post_id ) ) {
618 $img['optm_ori'] = 0;
619 }
620 }
621
622 $list[] = $img;
623 }
624
625 if ( ! $list ) {
626 $msg = __( 'No valid image found in the current request.', 'litespeed-cache' );
627 Admin_Display::error( $msg );
628 return;
629 }
630
631 $data = [
632 'action' => self::CLOUD_ACTION_NEW_REQ,
633 'list' => wp_json_encode( $list ),
634 'optm_ori' => $this->conf( self::O_IMG_OPTM_ORI ) ? 1 : 0,
635 'optm_lossless' => $this->conf( self::O_IMG_OPTM_LOSSLESS ) ? 1 : 0,
636 'keep_exif' => $this->conf( self::O_IMG_OPTM_EXIF ) ? 1 : 0,
637 ];
638 if ( $this->_format ) {
639 $data[ 'optm_' . $this->_format ] = 1;
640 }
641
642 // Push to Cloud server
643 $json = Cloud::post( Cloud::SVC_IMG_OPTM, $data );
644 if ( ! $json ) {
645 return;
646 }
647
648 // Check data format
649 if ( empty( $json['ids'] ) ) {
650 self::debug( 'Failed to parse response data from Cloud server ', $json );
651 $msg = __( 'No valid image found by Cloud server in the current request.', 'litespeed-cache' );
652 Admin_Display::error( $msg );
653 return;
654 }
655
656 self::debug( 'Returned data from Cloud server count: ' . count( $json['ids'] ) );
657
658 $ids = implode( ',', array_map( 'intval', $json['ids'] ) );
659 // Update img table
660 $q = "UPDATE `$this->_table_img_optming` SET optm_status = '" . self::STATUS_REQUESTED . "' WHERE id IN ( $ids )";
661 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
662 $wpdb->query( $q );
663
664 $this->_summary['last_requested'] = time();
665 self::save_summary();
666
667 return [ count( $list ), count( $json['ids'] ) ];
668 }
669
670 /**
671 * Parse wp's meta value
672 *
673 * @since 1.6.7
674 * @access private
675 * @param object $v The database row object.
676 * @return array|false The parsed meta value or false on failure.
677 */
678 private function _parse_wp_meta_value( $v ) {
679 if ( empty( $v ) ) {
680 self::debug( 'bypassed parsing meta due to null value' );
681 return false;
682 }
683
684 if ( ! $v->meta_value ) {
685 self::debug( 'bypassed parsing meta due to no meta_value: pid ' . $v->post_id );
686 return false;
687 }
688
689 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Suppress warnings from corrupted metadata
690 $meta_value = @maybe_unserialize( $v->meta_value );
691 if ( ! is_array( $meta_value ) ) {
692 self::debug( 'bypassed parsing meta due to meta_value not json: pid ' . $v->post_id );
693 return false;
694 }
695
696 if ( empty( $meta_value['file'] ) ) {
697 self::debug( 'bypassed parsing meta due to no ori file: pid ' . $v->post_id );
698 return false;
699 }
700
701 return $meta_value;
702 }
703 }
704