PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.2.9
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.2.9
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-files-list-table.php

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

1,075 lines 32.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! class_exists( 'WP_List_Table' ) ) {
4 require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
5 }
6
7 /**
8 * Class that display the "custom folders" files.
9 *
10 * @since 1.7
11 */
12 class Imagify_Files_List_Table extends WP_List_Table {
13
14 /**
15 * Class version.
16 *
17 * @var string
18 * @since 1.7
19 */
20 const VERSION = '1.1';
21
22 /**
23 * Class version.
24 *
25 * @var string
26 * @since 1.7
27 */
28 const PER_PAGE_OPTION = 'imagify_files_per_page';
29
30 /**
31 * List of the folders containing the listed files.
32 *
33 * @var array
34 * @since 1.7
35 */
36 protected $folders = [];
37
38 /**
39 * Filesystem object.
40 *
41 * @var Imagify_Filesystem
42 * @since 1.7.1
43 */
44 protected $filesystem;
45
46 /**
47 * Views object.
48 *
49 * @var Imagify_Views
50 * @since 1.9
51 */
52 protected $views;
53
54 /**
55 * Constructor.
56 *
57 * @since 1.7
58 *
59 * @param array $args An associative array of arguments.
60 */
61 public function __construct( $args = [] ) {
62 parent::__construct(
63 [
64 'plural' => 'imagify-files',
65 'screen' => isset( $args['screen'] ) ? convert_to_screen( $args['screen'] ) : null,
66 ]
67 );
68
69 $this->modes = [
70 'list' => __( 'List View', 'imagify' ),
71 ];
72
73 $this->filesystem = Imagify_Filesystem::get_instance();
74 $this->views = Imagify_Views::get_instance();
75 }
76
77 /**
78 * Prepares the list of items for displaying.
79 *
80 * @since 1.7
81 */
82 public function prepare_items() {
83 global $wpdb;
84
85 add_screen_option(
86 'per_page',
87 [
88 'label' => __( 'Number of files per page', 'imagify' ),
89 'default' => 20,
90 'option' => self::PER_PAGE_OPTION,
91 ]
92 );
93
94 $files_db = Imagify_Files_DB::get_instance();
95 $files_table = $files_db->get_table_name();
96 $files_key = $files_db->get_primary_key();
97 $files_key_esc = esc_sql( $files_key );
98 $per_page = $this->get_items_per_page( self::PER_PAGE_OPTION );
99
100 // Prepare the query to get items.
101 $page = $this->get_pagenum();
102 $offset = ( $page - 1 ) * $per_page;
103 $orderbys = $this->get_sortable_columns();
104 $orderby = 'path';
105 $order = 'ASC';
106 $folders = [];
107 $file_ids = [];
108 $where = '';
109
110 $sent_orderby = isset( $_GET['orderby'] ) ? sanitize_text_field( wp_unslash( $_GET['orderby'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
111 $sent_order = isset( $_GET['order'] ) ? sanitize_text_field( wp_unslash( $_GET['order'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
112 $folder_filter = self::get_folder_filter();
113 $status_filter = self::get_status_filter();
114
115 if ( ! empty( $sent_orderby ) && isset( $orderbys[ $sent_orderby ] ) ) {
116 $orderby = $sent_orderby;
117 $order = is_array( $orderbys[ $orderby ] ) ? 'DESC' : 'ASC';
118
119 if ( 'optimization' === $orderby ) {
120 $orderby = 'percent';
121 }
122 }
123
124 if ( $sent_order ) {
125 $order = 'ASC' === strtoupper( $sent_order ) ? 'ASC' : 'DESC';
126 }
127
128 if ( $folder_filter ) {
129 // Display only files from a specific custom folder.
130 $where = "WHERE folder_id = $folder_filter";
131 }
132
133 if ( $status_filter ) {
134 // Display files optimized, not optimized, or with error.
135 $where .= $where ? ' AND ' : 'WHERE ';
136
137 switch ( $status_filter ) {
138 case 'optimized':
139 $where .= "( status = 'success' OR status = 'already_optimized' )";
140 break;
141 case 'unoptimized':
142 $where .= 'status IS NULL';
143 break;
144 case 'errors':
145 $where .= "status = 'error'";
146 break;
147 }
148 }
149
150 // Pagination.
151 $this->set_pagination_args(
152 [
153 'total_items' => (int) $wpdb->get_var( "SELECT COUNT($files_key_esc) FROM $files_table $where" ), // WPCS: unprepared SQL ok.
154 'per_page' => $per_page,
155 ]
156 );
157
158 // Get items.
159 $this->items = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $files_table $where ORDER BY $orderby $order LIMIT %d, %d", $offset, $per_page ) ); // WPCS: unprepared SQL ok.
160
161 if ( ! $this->items ) {
162 return;
163 }
164
165 // Prepare items.
166 foreach ( $this->items as $i => $item ) {
167 // Cast values.
168 $item = $files_db->cast_row( $item );
169
170 // Store the folders used by the items to get their data later in 1 query.
171 $folders[ $item->folder_id ] = $item->folder_id;
172
173 // Store the item IDs to store transients later in 1 query.
174 $file_ids[ $item->$files_key ] = $item->$files_key;
175
176 // Use Imagify objects + add related folder ID and path (set later).
177 $this->items[ $i ] = (object) [
178 'process' => imagify_get_optimization_process( $item, 'custom-folders' ),
179 'folder_id' => $item->folder_id,
180 'folder_path' => false,
181 'is_folder_active' => true,
182 ];
183
184 if ( ! $this->items[ $i ]->process->is_valid() ) {
185 unset( $this->items[ $i ] );
186 }
187 }
188
189 $folders = array_filter( $folders );
190
191 // Cache transient values.
192 Imagify_DB::cache_process_locks( 'custom-folders', $file_ids );
193
194 if ( ! $folders ) {
195 return;
196 }
197
198 // Get folders data.
199 $folders_db = Imagify_Folders_DB::get_instance();
200 $folders_table = $folders_db->get_table_name();
201 $folders_key_esc = esc_sql( $folders_db->get_primary_key() );
202 $folders = Imagify_DB::prepare_values_list( $folders );
203 $folders = $wpdb->get_results( "SELECT * FROM $folders_table WHERE $folders_key_esc IN ( $folders )" ); // WPCS: unprepared SQL ok.
204
205 if ( ! $folders ) {
206 return;
207 }
208
209 // Cast folders data and store data into a property.
210 foreach ( $folders as $folder ) {
211 $folder = $folders_db->cast_row( $folder );
212
213 $this->folders[ $folder->folder_id ] = $folder;
214 }
215
216 // Set folders path to each item.
217 foreach ( $this->items as $i => $item ) {
218 if ( $item->folder_id && isset( $this->folders[ $item->folder_id ] ) ) {
219 $item->folder_path = $this->folders[ $item->folder_id ]->path;
220 $item->is_folder_active = (bool) $this->folders[ $item->folder_id ]->active;
221 }
222 }
223
224 // Button templates.
225 Imagify_Views::get_instance()->print_js_template_in_footer( 'button/processing' );
226 }
227
228 /**
229 * Message to be displayed when there are no items.
230 *
231 * @since 1.7
232 */
233 public function no_items() {
234 if ( self::get_status_filter() ) {
235 // Filter by status.
236 switch ( self::get_status_filter() ) {
237 case 'optimized':
238 /* translators: 1 is a link tag start, 2 is the link tag end. */
239 printf( esc_html__( 'No optimized files. Have you tried the %1$sbulk optimization%2$s yet?', 'imagify' ), '<a href="' . esc_url( get_imagify_admin_url( 'files-bulk-optimization' ) ) . '">', '</a>' );
240 return;
241
242 case 'unoptimized':
243 esc_html_e( 'No unoptimized files, hurray!', 'imagify' );
244 return;
245
246 case 'errors':
247 esc_html_e( 'No errors, hurray!', 'imagify' );
248 return;
249 }
250 }
251
252 $args = [
253 'action' => 'imagify_scan_custom_folders',
254 '_wpnonce' => wp_create_nonce( 'imagify_scan_custom_folders' ),
255 '_wp_http_referer' => get_imagify_admin_url( 'files-list' ),
256 ];
257
258 if ( self::get_folder_filter() ) {
259 // A specific custom folder (selected or not).
260 $args['folder'] = self::get_folder_filter();
261 $args['_wp_http_referer'] = rawurlencode( add_query_arg( 'folder-filter', self::get_folder_filter(), $args['_wp_http_referer'] ) );
262
263 printf(
264 /* translators: 1 and 2 are link tag starts, 3 is a link tag end. */
265 esc_html__( 'No files yet. Do you want to %1$sscan this folder%3$s for new files or launch a %2$sbulk optimization%3$s directly?', 'imagify' ),
266 '<a href="' . esc_url( add_query_arg( $args, admin_url( 'admin-post.php' ) ) ) . '">',
267 '<a href="' . esc_url( get_imagify_admin_url( 'files-bulk-optimization' ) ) . '">',
268 '</a>'
269 );
270 return;
271 }
272
273 if ( Imagify_Folders_DB::get_instance()->has_active_folders() ) {
274 // All selected custom folders.
275 $args['_wp_http_referer'] = rawurlencode( $args['_wp_http_referer'] );
276 printf(
277 /* translators: 1 and 2 are link tag starts, 3 is a link tag end. */
278 esc_html__( 'No files yet. Do you want to %1$sscan your selected folders%3$s for new files or launch a %2$sbulk optimization%3$s directly?', 'imagify' ),
279 '<a href="' . esc_url( add_query_arg( $args, admin_url( 'admin-post.php' ) ) ) . '">',
280 '<a href="' . esc_url( get_imagify_admin_url( 'files-bulk-optimization' ) ) . '">',
281 '</a>'
282 );
283 return;
284 }
285
286 // Nothing selected in the settings.
287 printf(
288 /* translators: 1 is a link tag start, 2 is the link tag end. */
289 esc_html__( 'To see things appear here, you must select folders in the settings page first :)', 'imagify' ),
290 '<a href="' . esc_url( get_imagify_admin_url() ) . '">',
291 '</a>'
292 );
293 }
294
295 /**
296 * Display views.
297 *
298 * @since 1.7
299 */
300 public function views() {
301 global $wpdb;
302
303 // Get all folders.
304 $folders_table = Imagify_Folders_DB::get_instance()->get_table_name();
305 $folders = $wpdb->get_results( "SELECT folder_id, path FROM $folders_table" ); // WPCS: unprepared SQL ok.
306
307 if ( ! $folders ) {
308 return;
309 }
310
311 $files_db = Imagify_Files_DB::get_instance();
312 $files_table = $files_db->get_table_name();
313 $files_key_esc = esc_sql( $files_db->get_primary_key() );
314
315 // Filter files by folder.
316 $folder_filters = [];
317 $root_id = 0;
318 $counts = $wpdb->get_results( "SELECT folder_id, COUNT( $files_key_esc ) AS count FROM $files_table GROUP BY folder_id", OBJECT_K ); // WPCS: unprepared SQL ok.
319
320 foreach ( $folders as $folder ) {
321 if ( '{{ROOT}}/' === $folder->path ) {
322 $root_id = $folder->folder_id;
323
324 $folder_filters[ $folder->folder_id ] = '/';
325 } else {
326 $folder_filters[ $folder->folder_id ] = '/' . trim( $this->filesystem->make_path_relative( Imagify_Files_Scan::remove_placeholder( $folder->path ) ), '/' );
327 }
328 }
329
330 natcasesort( $folder_filters );
331
332 if ( $root_id ) {
333 $folder_filters[ $root_id ] = __( 'Site\'s root', 'imagify' );
334 }
335
336 foreach ( $folder_filters as $folder_id => $label ) {
337 $folder_filters[ $folder_id ] .= ' (' . ( isset( $counts[ $folder_id ] ) ? (int) $counts[ $folder_id ]->count : 0 ) . ')';
338 }
339
340 // Filter files by status.
341 $counts = $wpdb->get_results( "SELECT status, COUNT( $files_key_esc ) AS count FROM $files_table GROUP BY status", OBJECT_K ); // WPCS: unprepared SQL ok.
342 $status_filters = [
343 'optimized' => 0,
344 'unoptimized' => 0,
345 'errors' => 0,
346 ];
347
348 if ( isset( $counts['success'] ) ) {
349 $status_filters['optimized'] += $counts['success']->count;
350 }
351
352 if ( isset( $counts['already_optimized'] ) ) {
353 $status_filters['optimized'] += $counts['already_optimized']->count;
354 }
355
356 if ( isset( $counts[''] ) ) {
357 $status_filters['unoptimized'] += $counts['']->count;
358 }
359
360 if ( isset( $counts['error'] ) ) {
361 $status_filters['errors'] += $counts['error']->count;
362 }
363
364 $status_filters = [
365 '' => __( 'All Media Files', 'imagify' ),
366 'optimized' => _x( 'Optimized', 'Media Files', 'imagify' ) . ' (' . $status_filters['optimized'] . ')',
367 'unoptimized' => _x( 'Unoptimized', 'Media Files', 'imagify' ) . ' (' . $status_filters['unoptimized'] . ')',
368 'errors' => _x( 'Errors', 'Media Files', 'imagify' ) . ' (' . $status_filters['errors'] . ')',
369 ];
370
371 // Get submitted values.
372 $folder_filter = self::get_folder_filter();
373 $status_filter = self::get_status_filter();
374
375 // Display the filters.
376 if ( method_exists( $this->screen, 'render_screen_reader_content' ) ) {
377 // Introduced in WP 4.4.
378 $this->screen->render_screen_reader_content( 'heading_views' );
379 }
380 ?>
381 <div class="wp-filter">
382 <div class="filter-items">
383
384 <label for="folder-filter" class="screen-reader-text"><?php esc_html_e( 'Filter by folder', 'imagify' ); ?></label>
385 <select class="folder-filters" name="folder-filter" id="folder-filter">
386 <?php
387 printf( '<option value="%s"%s>%s</option>', '', selected( $folder_filter, 0, false ), esc_html__( 'All Folders', 'imagify' ) );
388
389 foreach ( $folder_filters as $folder_id => $label ) {
390 printf( '<option value="%d"%s>%s</option>', esc_attr( $folder_id ), selected( $folder_filter, $folder_id, false ), esc_html( $label ) );
391 }
392 ?>
393 </select>
394
395 <label for="status-filter" class="screen-reader-text"><?php esc_html_e( 'Filter by status', 'imagify' ); ?></label>
396 <select class="folder-filters" name="status-filter" id="status-filter">
397 <?php
398 foreach ( $status_filters as $status => $label ) {
399 printf( '<option value="%s"%s>%s</option>', esc_attr( $status ), selected( $status_filter, $status, false ), esc_html( $label ) );
400 }
401 ?>
402 </select>
403
404 <?php submit_button( _x( 'Filter', 'verb', 'imagify' ), '', 'filter_action', false, [ 'id' => 'folders-query-submit' ] ); ?>
405
406 <?php $this->extra_tablenav( 'bar' ); ?>
407 </div>
408 </div>
409 <?php
410 }
411
412 /**
413 * Get an associative array ( option_name => option_title ) with the list of bulk actions available on this table.
414 *
415 * @since 1.7
416 *
417 * @return array
418 */
419 public function get_bulk_actions() {
420 return [
421 'imagify-bulk-refresh-status' => __( 'Refresh status', 'imagify' ),
422 ];
423 }
424
425 /**
426 * Get a list of columns. The format is:
427 * 'internal-name' => 'Title'
428 *
429 * @since 1.7
430 *
431 * @return array
432 */
433 public function get_columns() {
434 return [
435 'cb' => '<input type="checkbox" />',
436 'title' => __( 'File', 'imagify' ),
437 'folder' => __( 'Folder', 'imagify' ),
438 'optimization' => __( 'Optimization', 'imagify' ),
439 'status' => __( 'Status', 'imagify' ),
440 'optimization_level' => __( 'Optimization Level', 'imagify' ),
441 'actions' => __( 'Actions', 'imagify' ),
442 ];
443 }
444
445 /**
446 * Get a list of sortable columns. The format is:
447 * 'internal-name' => 'orderby'
448 * or
449 * 'internal-name' => array( 'orderby', true )
450 *
451 * The second format will make the initial sorting order be descending.
452 *
453 * @since 1.7
454 *
455 * @return array
456 */
457 public function get_sortable_columns() {
458 return [
459 'folder' => 'folder',
460 'optimization' => [ 'optimization', true ],
461 'status' => 'status',
462 'optimization_level' => [ 'optimization_level', true ],
463 ];
464 }
465
466 /**
467 * Get a column contents.
468 *
469 * @since 1.7
470 *
471 * @param string $column The column "name": "cb", "title", "optimization_level", etc.
472 * @param object $item The current item. It must contain at least a $process property.
473 * @return string HTML contents,
474 */
475 public function get_column( $column, $item ) {
476 if ( ! method_exists( $this, 'column_' . $column ) ) {
477 return '';
478 }
479
480 ob_start();
481 call_user_func( [ $this, 'column_' . $column ], $item );
482 return ob_get_clean();
483 }
484
485 /**
486 * Handles the checkbox column output.
487 *
488 * @since 1.7
489 *
490 * @param object $item The current item. It must contain at least a $process property.
491 */
492 public function column_cb( $item ) {
493 $media_id = $item->process->get_media()->get_id();
494 ?>
495 <label class="screen-reader-text" for="cb-select-<?php echo esc_attr( $media_id ); ?>"><?php echo esc_html_x( 'Select file', 'checkbox label', 'imagify' ); ?></label>
496 <input id="cb-select-<?php echo esc_attr( $media_id ); ?>" type="checkbox" name="bulk_select[]" value="<?php echo esc_attr( $media_id ); ?>" />
497 <?php
498 }
499
500 /**
501 * Handles the title column output.
502 *
503 * @since 1.7
504 *
505 * @param object $item The current item. It must contain at least a $process property.
506 */
507 public function column_title( $item ) {
508 $item = $this->maybe_set_item_folder( $item );
509 $media = $item->process->get_media();
510 $url = $media->get_fullsize_url();
511 $base = ! empty( $item->folder_path ) ? Imagify_Files_Scan::remove_placeholder( $item->folder_path ) : '';
512 $title = $this->filesystem->make_path_relative( $media->get_fullsize_path(), $base );
513
514 list( $mime ) = explode( '/', $media->get_mime_type() );
515
516 if ( $media->is_image() ) {
517 $dimensions = $media->get_dimensions();
518 $orientation = $dimensions['width'] > $dimensions['height'] ? ' landscape' : ' portrait';
519 $orientation = $dimensions['width'] && $dimensions['height'] ? $orientation : '';
520
521 if ( ! wp_doing_ajax() && $item->process->get_data()->get_optimized_size( false, 0, false ) > 100000 ) {
522 // LazyLoad.
523 $image_tag = '<img src="' . esc_url( IMAGIFY_ASSETS_IMG_URL . 'lazyload.png' ) . '" data-lazy-src="' . esc_url( $url ) . '" alt="" />';
524 $image_tag .= '<noscript><img src="' . esc_url( $url ) . '" alt="" /></noscript>';
525 } else {
526 $image_tag = '<img src="' . esc_url( $url ) . '" class="hide-if-no-js" alt="" />';
527 }
528 } else {
529 $orientation = '';
530 $image_tag = '<img src="' . esc_url( wp_mime_type_icon( $mime ) ) . '" class="hide-if-no-js" alt="" />';
531 }
532 ?>
533 <strong class="has-media-icon">
534 <a href="<?php echo esc_url( $url ); ?>" target="_blank">
535 <span class="media-icon <?php echo sanitize_html_class( $mime . '-icon' ); ?><?php echo esc_attr( $orientation ); ?>">
536 <span class="centered">
537 <?php echo $image_tag; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
538 </span>
539 </span>
540 <?php echo esc_html( $title ); ?>
541 </a>
542 </strong>
543 <p class="filename">
544 <?php $this->comparison_tool_button( $item ); ?>
545 </p>
546 <?php
547 }
548
549 /**
550 * Handles the parent folder column output.
551 *
552 * @since 1.7
553 *
554 * @param object $item The current item. It must contain at least a $process property.
555 */
556 public function column_folder( $item ) {
557 $item = $this->maybe_set_item_folder( $item );
558
559 if ( empty( $item->folder_path ) ) {
560 return;
561 }
562
563 $format = '%s';
564 $filter = self::get_folder_filter();
565
566 if ( $filter !== $item->folder_id ) {
567 $format = '<a href="' . esc_url( add_query_arg( 'folder-filter', $item->folder_id, get_imagify_admin_url( 'files-list' ) ) ) . '">%s</a>';
568 }
569
570 if ( '{{ROOT}}/' === $item->folder_path ) {
571 // It's the site's root.
572 printf( $format, esc_html__( 'Site\'s root', 'imagify' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
573 } else {
574 printf( $format, '<code>/' . trim( $this->filesystem->make_path_relative( Imagify_Files_Scan::remove_placeholder( $item->folder_path ) ), '/' ) . '</code>' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
575 }
576
577 if ( ! $item->is_folder_active ) {
578 echo '<br/>';
579 esc_html_e( 'This folder is not selected for bulk optimization.', 'imagify' );
580 }
581 }
582
583 /**
584 * Handles the optimization data column output.
585 *
586 * @since 1.7
587 *
588 * @param object $item The current item. It must contain at least a $process property.
589 */
590 public function column_optimization( $item ) {
591 $data = $item->process->get_data();
592 $media_id = $item->process->get_media()->get_id();
593 ?>
594 <ul class="imagify-datas-list">
595 <li class="imagify-data-item">
596 <span class="data"><?php esc_html_e( 'Original Filesize:', 'imagify' ); ?></span>
597 <strong class="data-value original"><?php echo esc_html( $data->get_original_size() ); ?></strong>
598 </li>
599 <?php if ( $data->is_optimized() ) { ?>
600 <li class="imagify-data-item">
601 <span class="data"><?php esc_html_e( 'New Filesize:', 'imagify' ); ?></span>
602 <strong class="data-value big optimized"><?php echo esc_html( $data->get_optimized_size() ); ?></strong>
603 </li>
604 <li class="imagify-data-item">
605 <span class="data"><?php esc_html_e( 'Original Saving:', 'imagify' ); ?></span>
606 <strong class="data-value">
607 <span class="imagify-chart">
608 <span class="imagify-chart-container">
609 <canvas class="imagify-consumption-chart imagify-consumption-chart-<?php echo esc_attr( $media_id ); ?>" width="15" height="15"></canvas>
610 <?php if ( wp_doing_ajax() ) { ?>
611 <script type="text/javascript">jQuery( window ).trigger( "canvasprinted.imagify", [ ".imagify-consumption-chart-<?php echo esc_attr( $media_id ); ?>" ] ); </script>
612 <?php } ?>
613 </span>
614 </span>
615 <span class="imagify-chart-value"><?php echo esc_html( $data->get_saving_percent() ); ?></span>%
616 </strong>
617 </li>
618 <?php
619 if ( $item->process->get_media()->is_image() ) {
620 $has_nextgen = $item->process->has_next_gen() ? __( 'Yes', 'imagify' ) : __( 'No', 'imagify' );
621 ?>
622 <li class="imagify-data-item">
623 <span class="data"><?php esc_html_e( 'Next-Gen generated:', 'imagify' ); ?></span>
624 <strong class="data-value"><?php echo esc_html( $has_nextgen ); ?></strong>
625 </li>
626 <?php
627 }
628 }
629 ?>
630 </ul>
631 <?php
632 }
633
634 /**
635 * Handles the status column output.
636 *
637 * @since 1.7
638 *
639 * @param object $item The current item. It must contain at least a $process property.
640 */
641 public function column_status( $item ) {
642 $data = $item->process->get_data();
643 $row = $data->get_row();
644 $status = $data->get_optimization_status();
645 $messages = [];
646
647 if ( ! $status ) {
648 // File is not optimized.
649 $messages[] = '<strong class="imagify-status-not-optimized">' . esc_html_x( 'Not optimized', 'Media File', 'imagify' ) . '</strong>';
650 } elseif ( ! empty( $row['error'] ) ) {
651 // Error or already optimized.
652 $messages[] = '<span class="imagify-status-' . $status . '">' . esc_html( imagify_translate_api_message( $row['error'] ) ) . '</span>';
653 }
654
655 if ( empty( $row['modified'] ) && ! $messages ) {
656 // No need to display this if we already have another message to display.
657 $messages[] = '<em class="imagify-status-no-changes">' . esc_html__( 'No changes found', 'imagify' ) . '</em>';
658 } elseif ( ! empty( $row['modified'] ) ) {
659 // The file has changed or is missing.
660 $messages[] = '<strong class="imagify-status-changed">' . esc_html__( 'The file has changed', 'imagify' ) . '</strong>';
661 }
662
663 echo implode( '<br/>', $messages ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
664
665 $this->refresh_status_button( $item );
666 }
667
668 /**
669 * Handles the optimization level column output.
670 *
671 * @since 1.7
672 *
673 * @param object $item The current item. It must contain at least a $process property.
674 */
675 public function column_optimization_level( $item ) {
676 $data = $item->process->get_data();
677
678 if ( ! $data->is_error() ) {
679 echo imagify_get_optimization_level_label( $data->get_optimization_level(), '%ICON% %s' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
680 }
681 }
682
683 /**
684 * Handles the actions column output.
685 *
686 * @since 1.7
687 *
688 * @param object $item The current item. It must contain at least a $process property.
689 */
690 public function column_actions( $item ) {
691 static $done = false;
692
693 if ( ! Imagify_Requirements::is_api_key_valid() ) {
694 // Stop the process if the API key isn't valid.
695 if ( ! $done ) {
696 // No need to display this on every row.
697 $done = true;
698 esc_html_e( 'Invalid API key', 'imagify' );
699 echo '<br/><a href="' . esc_url( get_imagify_admin_url() ) . '">' . esc_html__( 'Check your Settings', 'imagify' ) . '</a>';
700 }
701 return;
702 }
703
704 if ( $item->process->is_locked() ) {
705 Imagify_Views::get_instance()->print_template(
706 'button-processing',
707 [
708 'label' => __( 'Optimizing...', 'imagify' ),
709 ]
710 );
711 return;
712 }
713
714 $this->optimize_button( $item );
715 $this->retry_button( $item );
716 $this->reoptimize_buttons( $item );
717 $this->generate_nextgen_versions_button( $item );
718 $this->delete_nextgen_versions_button( $item );
719 $this->restore_button( $item );
720 }
721
722 /**
723 * Prints a button to optimize the file.
724 *
725 * @since 1.7
726 *
727 * @param object $item The current item. It must contain at least a $process property.
728 */
729 protected function optimize_button( $item ) {
730 if ( $item->process->get_data()->get_optimization_status() ) {
731 // Already optimized.
732 return;
733 }
734
735 $media = $item->process->get_media();
736 $class = $media->has_backup() ? ' file-has-backup' : '';
737 $url = get_imagify_admin_url(
738 'optimize-file',
739 [
740 'attachment_id' => $media->get_id(),
741 ]
742 );
743
744 echo $this->views->get_template( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
745 'button/optimize',
746 [
747 'url' => $url, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
748 'atts' => [
749 'class' => 'button-primary button-imagify-optimize' . esc_attr( $class ),
750 ],
751 ]
752 );
753 }
754
755 /**
756 * Prints a button to retry to optimize the file.
757 *
758 * @since 1.7
759 *
760 * @param object $item The current item. It must contain at least a $process property.
761 */
762 protected function retry_button( $item ) {
763 $data = $item->process->get_data();
764
765 if ( ! $data->is_already_optimized() && ! $data->is_error() ) {
766 // Not optimized or successfully optimized.
767 return;
768 }
769
770 $media = $item->process->get_media();
771 $class = $media->has_backup() ? ' file-has-backup' : '';
772 $url = get_imagify_admin_url(
773 'optimize-file',
774 [
775 'attachment_id' => $media->get_id(),
776 ]
777 );
778
779 echo $this->views->get_template( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
780 'button/retry-optimize',
781 [
782 'url' => $url, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
783 'atts' => [
784 'class' => 'button button-imagify-optimize' . esc_attr( $class ),
785 ],
786 ]
787 );
788 echo '<br/>';
789 }
790
791 /**
792 * Prints buttons to re-optimize the file to other levels.
793 *
794 * @since 1.7
795 *
796 * @param object $item The current item. It must contain at least a $process property.
797 */
798 protected function reoptimize_buttons( $item ) {
799 $data = $item->process->get_data();
800
801 if ( ! $data->get_optimization_status() ) {
802 // Not optimized yet.
803 return;
804 }
805
806 $is_already_optimized = $data->is_already_optimized();
807 $media = $item->process->get_media();
808 $can_reoptimize = $is_already_optimized || $media->has_backup();
809
810 // Don't display anything if there is no backup or the image has been optimized.
811 if ( ! $can_reoptimize ) {
812 return;
813 }
814
815 $media_level = $data->get_optimization_level();
816 $data = [];
817 $url_args = [ 'attachment_id' => $media->get_id() ];
818
819 if ( $media_level < 1 ) {
820 $url_args['optimization_level'] = 2;
821 $data['optimization_level'] = 2;
822 $data['url'] = get_imagify_admin_url( 'reoptimize-file', $url_args );
823
824 echo $this->views->get_template( 'button/re-optimize', $data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
825 } elseif ( $media_level > 0 ) {
826 $url_args['optimization_level'] = 0;
827 $data['optimization_level'] = 0;
828 $data['url'] = get_imagify_admin_url( 'reoptimize-file', $url_args );
829
830 echo $this->views->get_template( 'button/re-optimize', $data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
831 }
832 }
833
834 /**
835 * Prints a button to generate Next gen versions if they are missing.
836 *
837 * @since 1.7
838 *
839 * @param object $item The current item. It must contain at least a $process property.
840 */
841 protected function generate_nextgen_versions_button( $item ) {
842 $button = get_imagify_attachment_generate_nextgen_versions_link( $item->process );
843
844 if ( $button ) {
845 echo $button . '<br/>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
846 }
847 }
848
849 /**
850 * Prints a button to delete next-gen versions when the status is "already_optimized".
851 *
852 * @since 1.9.6
853 *
854 * @param object $item The current item. It must contain at least a $process property.
855 */
856 protected function delete_nextgen_versions_button( $item ) {
857 $button = get_imagify_attachment_delete_nextgen_versions_link( $item->process );
858
859 if ( $button ) {
860 echo $button . '<br/>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
861 }
862 }
863
864 /**
865 * Prints a button to restore the file.
866 *
867 * @since 1.7
868 *
869 * @param object $item The current item. It must contain at least a $process property.
870 */
871 protected function restore_button( $item ) {
872 $data = $item->process->get_data();
873 $media = $item->process->get_media();
874
875 if ( ! $data->is_optimized() || ! $media->has_backup() ) {
876 return;
877 }
878
879 $url = get_imagify_admin_url(
880 'restore-file',
881 [
882 'attachment_id' => $media->get_id(),
883 ]
884 );
885
886 echo $this->views->get_template( 'button/restore', [ 'url' => $url ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
887 }
888
889 /**
890 * Prints a button to check if the file has been modified or not.
891 *
892 * @since 1.7
893 *
894 * @param object $item The current item. It must contain at least a $process property.
895 */
896 protected function refresh_status_button( $item ) {
897 $url = get_imagify_admin_url(
898 'refresh-file-modified',
899 [
900 'attachment_id' => $item->process->get_media()->get_id(),
901 ]
902 );
903
904 echo '<br/>';
905 echo $this->views->get_template( 'button/refresh-status', [ 'url' => $url ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
906 }
907
908 /**
909 * Prints a button for the comparison tool (before / after optimization).
910 *
911 * @since 1.7
912 *
913 * @param object $item The current item. It must contain at least a $process property.
914 */
915 protected function comparison_tool_button( $item ) {
916 $data = $item->process->get_data();
917 $media = $item->process->get_media();
918
919 if ( ! $data->is_optimized() || ! $media->has_backup() || ! $media->is_image() ) {
920 return;
921 }
922
923 $file_path = $media->get_fullsize_path();
924
925 if ( ! $file_path ) {
926 return;
927 }
928
929 $dimensions = $media->get_dimensions();
930
931 if ( $dimensions['width'] < 360 ) {
932 return;
933 }
934
935 $backup_url = $media->get_backup_url();
936
937 echo $this->views->get_template( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
938 'button/compare-images',
939 [
940 'url' => $backup_url, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
941 'backup_url' => $backup_url, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
942 'original_url' => $media->get_fullsize_url(), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
943 'media_id' => $media->get_id(), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
944 'width' => $dimensions['width'], // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
945 'height' => $dimensions['height'], // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
946 ]
947 );
948
949 if ( wp_doing_ajax() ) {
950 ?>
951 <script type="text/javascript">jQuery( window ).trigger( 'comparisonprinted.imagify', [ <?php echo esc_js( $media->get_id() ); ?> ] ); </script>
952 <?php
953 }
954 }
955
956 /**
957 * Add the folder_id and folder_path properties to the $item if not set yet.
958 * It may happen if the $item doesn't come from the prepare() method.
959 *
960 * @since 1.7
961 *
962 * @param object $item The current item. It must contain at least a $process property.
963 * @return object The current item.
964 */
965 protected function maybe_set_item_folder( $item ) {
966 if ( isset( $item->folder_path ) ) {
967 return $item;
968 }
969
970 $item->folder_id = 0;
971 $item->folder_path = false;
972
973 $row = $item->process->get_data()->get_row();
974
975 if ( empty( $row['folder_id'] ) ) {
976 return $item;
977 }
978
979 $folder = Imagify_Folders_DB::get_instance()->get( $row['folder_id'] );
980
981 if ( ! $folder ) {
982 return $item;
983 }
984
985 $item->folder_id = $folder['folder_id'];
986 $item->folder_path = $folder['path'];
987 $item->is_folder_active = (bool) $folder['active'];
988
989 return $item;
990 }
991
992 /**
993 * Get the name of the default primary column.
994 *
995 * @since 1.7
996 *
997 * @return string Name of the default primary column, in this case, 'title'.
998 */
999 protected function get_default_primary_column_name() {
1000 return 'title';
1001 }
1002
1003 /**
1004 * Get a list of CSS classes for the WP_List_Table table tag.
1005 *
1006 * @since 1.7
1007 *
1008 * @return array List of CSS classes for the table tag.
1009 */
1010 protected function get_table_classes() {
1011 return [ 'widefat', 'fixed', 'striped', 'media', $this->_args['plural'] ];
1012 }
1013
1014 /**
1015 * Allow to save the screen options when submitted by the user.
1016 *
1017 * @since 1.7
1018 *
1019 * @param bool|int $status Screen option value. Default false to skip.
1020 * @param string $option The option name.
1021 * @param int $value The number of rows to use.
1022 * @return int|bool
1023 */
1024 public static function save_screen_options( $status, $option, $value ) {
1025 if ( self::PER_PAGE_OPTION === $option ) {
1026 return (int) $value;
1027 }
1028
1029 return $status;
1030 }
1031
1032 /**
1033 * Get the requested folder filter.
1034 *
1035 * @since 1.7
1036 *
1037 * @return string
1038 */
1039 public static function get_folder_filter() {
1040 static $filter;
1041
1042 if ( ! isset( $filter ) ) {
1043 $filter = filter_input( INPUT_GET, 'folder-filter', FILTER_VALIDATE_INT );
1044 $filter = max( 0, $filter );
1045 }
1046
1047 return $filter;
1048 }
1049
1050 /**
1051 * Get the requested status filter.
1052 *
1053 * @since 1.7
1054 *
1055 * @return string
1056 */
1057 public static function get_status_filter() {
1058 static $filter;
1059
1060 if ( isset( $filter ) ) {
1061 return $filter;
1062 }
1063
1064 $values = [
1065 'optimized' => 1,
1066 'unoptimized' => 1,
1067 'errors' => 1,
1068 ];
1069 $filter = isset( $_GET['status-filter'] ) ? trim( sanitize_text_field( wp_unslash( $_GET['status-filter'] ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
1070 $filter = isset( $values[ $filter ] ) ? $filter : '';
1071
1072 return $filter;
1073 }
1074 }
1075