'imagify-files',
'screen' => isset( $args['screen'] ) ? convert_to_screen( $args['screen'] ) : null,
) );
$this->modes = array(
'list' => __( 'List View', 'imagify' ),
);
$this->filesystem = Imagify_Filesystem::get_instance();
$this->views = Imagify_Views::get_instance();
}
/**
* Prepares the list of items for displaying.
*
* @since 1.7
*/
public function prepare_items() {
global $wpdb;
add_screen_option( 'per_page', array(
'label' => __( 'Number of files per page', 'imagify' ),
'default' => 20,
'option' => self::PER_PAGE_OPTION,
) );
$files_db = Imagify_Files_DB::get_instance();
$files_table = $files_db->get_table_name();
$files_key = $files_db->get_primary_key();
$files_key_esc = esc_sql( $files_key );
$per_page = $this->get_items_per_page( self::PER_PAGE_OPTION );
// Prepare the query to get items.
$page = $this->get_pagenum();
$offset = ( $page - 1 ) * $per_page;
$orderbys = $this->get_sortable_columns();
$orderby = 'path';
$order = 'ASC';
$folders = array();
$file_ids = array();
$where = '';
$sent_orderby = isset( $_GET['orderby'] ) ? htmlspecialchars( wp_unslash( $_GET['orderby'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
$sent_order = isset( $_GET['order'] ) ? htmlspecialchars( wp_unslash( $_GET['order'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
$folder_filter = self::get_folder_filter();
$status_filter = self::get_status_filter();
if ( ! empty( $sent_orderby ) && isset( $orderbys[ $sent_orderby ] ) ) {
$orderby = $sent_orderby;
$order = is_array( $orderbys[ $orderby ] ) ? 'DESC' : 'ASC';
if ( 'optimization' === $orderby ) {
$orderby = 'percent';
}
}
if ( $sent_order ) {
$order = 'ASC' === strtoupper( $sent_order ) ? 'ASC' : 'DESC';
}
if ( $folder_filter ) {
// Display only files from a specific custom folder.
$where = "WHERE folder_id = $folder_filter";
}
if ( $status_filter ) {
// Display files optimized, not optimized, or with error.
$where .= $where ? ' AND ' : 'WHERE ';
switch ( $status_filter ) {
case 'optimized':
$where .= "( status = 'success' OR status = 'already_optimized' )";
break;
case 'unoptimized':
$where .= 'status IS NULL';
break;
case 'errors':
$where .= "status = 'error'";
break;
}
}
// Pagination.
$this->set_pagination_args( array(
'total_items' => (int) $wpdb->get_var( "SELECT COUNT($files_key_esc) FROM $files_table $where" ), // WPCS: unprepared SQL ok.
'per_page' => $per_page,
) );
// Get items.
$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.
if ( ! $this->items ) {
return;
}
// Prepare items.
foreach ( $this->items as $i => $item ) {
// Cast values.
$item = $files_db->cast_row( $item );
// Store the folders used by the items to get their data later in 1 query.
$folders[ $item->folder_id ] = $item->folder_id;
// Store the item IDs to store transients later in 1 query.
$file_ids[ $item->$files_key ] = $item->$files_key;
// Use Imagify objects + add related folder ID and path (set later).
$this->items[ $i ] = (object) [
'process' => imagify_get_optimization_process( $item, 'custom-folders' ),
'folder_id' => $item->folder_id,
'folder_path' => false,
'is_folder_active' => true,
];
if ( ! $this->items[ $i ]->process->is_valid() ) {
unset( $this->items[ $i ] );
}
}
$folders = array_filter( $folders );
// Cache transient values.
Imagify_DB::cache_process_locks( 'custom-folders', $file_ids );
if ( ! $folders ) {
return;
}
// Get folders data.
$folders_db = Imagify_Folders_DB::get_instance();
$folders_table = $folders_db->get_table_name();
$folders_key_esc = esc_sql( $folders_db->get_primary_key() );
$folders = Imagify_DB::prepare_values_list( $folders );
$folders = $wpdb->get_results( "SELECT * FROM $folders_table WHERE $folders_key_esc IN ( $folders )" ); // WPCS: unprepared SQL ok.
if ( ! $folders ) {
return;
}
// Cast folders data and store data into a property.
foreach ( $folders as $folder ) {
$folder = $folders_db->cast_row( $folder );
$this->folders[ $folder->folder_id ] = $folder;
}
// Set folders path to each item.
foreach ( $this->items as $i => $item ) {
if ( $item->folder_id && isset( $this->folders[ $item->folder_id ] ) ) {
$item->folder_path = $this->folders[ $item->folder_id ]->path;
$item->is_folder_active = (bool) $this->folders[ $item->folder_id ]->active;
}
}
// Button templates.
Imagify_Views::get_instance()->print_js_template_in_footer( 'button/processing' );
}
/**
* Message to be displayed when there are no items.
*
* @since 1.7
*/
public function no_items() {
if ( self::get_status_filter() ) {
// Filter by status.
switch ( self::get_status_filter() ) {
case 'optimized':
/* translators: 1 is a link tag start, 2 is the link tag end. */
printf( esc_html__( 'No optimized files. Have you tried the %1$sbulk optimization%2$s yet?', 'imagify' ), '', '' );
return;
case 'unoptimized':
esc_html_e( 'No unoptimized files, hurray!', 'imagify' );
return;
case 'errors':
esc_html_e( 'No errors, hurray!', 'imagify' );
return;
}
}
$args = array(
'action' => 'imagify_scan_custom_folders',
'_wpnonce' => wp_create_nonce( 'imagify_scan_custom_folders' ),
'_wp_http_referer' => get_imagify_admin_url( 'files-list' ),
);
if ( self::get_folder_filter() ) {
// A specific custom folder (selected or not).
$args['folder'] = self::get_folder_filter();
$args['_wp_http_referer'] = rawurlencode( add_query_arg( 'folder-filter', self::get_folder_filter(), $args['_wp_http_referer'] ) );
printf(
/* translators: 1 and 2 are link tag starts, 3 is a link tag end. */
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' ),
'',
'',
''
);
return;
}
if ( Imagify_Folders_DB::get_instance()->has_active_folders() ) {
// All selected custom folders.
$args['_wp_http_referer'] = rawurlencode( $args['_wp_http_referer'] );
printf(
/* translators: 1 and 2 are link tag starts, 3 is a link tag end. */
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' ),
'',
'',
''
);
return;
}
// Nothing selected in the settings.
printf(
/* translators: 1 is a link tag start, 2 is the link tag end. */
esc_html__( 'To see things appear here, you must select folders in the settings page first :)', 'imagify' ),
'',
''
);
}
/**
* Display views.
*
* @since 1.7
*/
public function views() {
global $wpdb;
// Get all folders.
$folders_table = Imagify_Folders_DB::get_instance()->get_table_name();
$folders = $wpdb->get_results( "SELECT folder_id, path FROM $folders_table" ); // WPCS: unprepared SQL ok.
if ( ! $folders ) {
return;
}
$files_db = Imagify_Files_DB::get_instance();
$files_table = $files_db->get_table_name();
$files_key_esc = esc_sql( $files_db->get_primary_key() );
// Filter files by folder.
$folder_filters = array();
$root_id = 0;
$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.
foreach ( $folders as $folder ) {
if ( '{{ROOT}}/' === $folder->path ) {
$root_id = $folder->folder_id;
$folder_filters[ $folder->folder_id ] = '/';
} else {
$folder_filters[ $folder->folder_id ] = '/' . trim( $this->filesystem->make_path_relative( Imagify_Files_Scan::remove_placeholder( $folder->path ) ), '/' );
}
}
natcasesort( $folder_filters );
if ( $root_id ) {
$folder_filters[ $root_id ] = __( 'Site\'s root', 'imagify' );
}
foreach ( $folder_filters as $folder_id => $label ) {
$folder_filters[ $folder_id ] .= ' (' . ( isset( $counts[ $folder_id ] ) ? (int) $counts[ $folder_id ]->count : 0 ) . ')';
}
// Filter files by status.
$counts = $wpdb->get_results( "SELECT status, COUNT( $files_key_esc ) AS count FROM $files_table GROUP BY status", OBJECT_K ); // WPCS: unprepared SQL ok.
$status_filters = array(
'optimized' => 0,
'unoptimized' => 0,
'errors' => 0,
);
if ( isset( $counts['success'] ) ) {
$status_filters['optimized'] += $counts['success']->count;
}
if ( isset( $counts['already_optimized'] ) ) {
$status_filters['optimized'] += $counts['already_optimized']->count;
}
if ( isset( $counts[''] ) ) {
$status_filters['unoptimized'] += $counts['']->count;
}
if ( isset( $counts['error'] ) ) {
$status_filters['errors'] += $counts['error']->count;
}
$status_filters = array(
'' => __( 'All Media Files', 'imagify' ),
'optimized' => _x( 'Optimized', 'Media Files', 'imagify' ) . ' (' . $status_filters['optimized'] . ')',
'unoptimized' => _x( 'Unoptimized', 'Media Files', 'imagify' ) . ' (' . $status_filters['unoptimized'] . ')',
'errors' => _x( 'Errors', 'Media Files', 'imagify' ) . ' (' . $status_filters['errors'] . ')',
);
// Get submitted values.
$folder_filter = self::get_folder_filter();
$status_filter = self::get_status_filter();
// Display the filters.
if ( method_exists( $this->screen, 'render_screen_reader_content' ) ) {
// Introduced in WP 4.4.
$this->screen->render_screen_reader_content( 'heading_views' );
}
?>
'folders-query-submit' ) ); ?>
extra_tablenav( 'bar' ); ?>
option_title ) with the list of bulk actions available on this table.
*
* @since 1.7
*
* @return array
*/
public function get_bulk_actions() {
return array(
'imagify-bulk-refresh-status' => __( 'Refresh status', 'imagify' ),
);
}
/**
* Get a list of columns. The format is:
* 'internal-name' => 'Title'
*
* @since 1.7
*
* @return array
*/
public function get_columns() {
return array(
'cb' => '',
'title' => __( 'File', 'imagify' ),
'folder' => __( 'Folder', 'imagify' ),
'optimization' => __( 'Optimization', 'imagify' ),
'status' => __( 'Status', 'imagify' ),
'optimization_level' => __( 'Optimization Level', 'imagify' ),
'actions' => __( 'Actions', 'imagify' ),
);
}
/**
* Get a list of sortable columns. The format is:
* 'internal-name' => 'orderby'
* or
* 'internal-name' => array( 'orderby', true )
*
* The second format will make the initial sorting order be descending.
*
* @since 1.7
*
* @return array
*/
public function get_sortable_columns() {
return array(
'folder' => 'folder',
'optimization' => array( 'optimization', true ),
'status' => 'status',
'optimization_level' => array( 'optimization_level', true ),
);
}
/**
* Get a column contents.
*
* @since 1.7
*
* @param string $column The column "name": "cb", "title", "optimization_level", etc.
* @param object $item The current item. It must contain at least a $process property.
* @return string HTML contents,
*/
public function get_column( $column, $item ) {
if ( ! method_exists( $this, 'column_' . $column ) ) {
return '';
}
ob_start();
call_user_func( array( $this, 'column_' . $column ), $item );
return ob_get_clean();
}
/**
* Handles the checkbox column output.
*
* @since 1.7
*
* @param object $item The current item. It must contain at least a $process property.
*/
public function column_cb( $item ) {
$media_id = $item->process->get_media()->get_id();
?>
maybe_set_item_folder( $item );
$media = $item->process->get_media();
$url = $media->get_fullsize_url();
$base = ! empty( $item->folder_path ) ? Imagify_Files_Scan::remove_placeholder( $item->folder_path ) : '';
$title = $this->filesystem->make_path_relative( $media->get_fullsize_path(), $base );
list( $mime ) = explode( '/', $media->get_mime_type() );
if ( $media->is_image() ) {
$dimensions = $media->get_dimensions();
$orientation = $dimensions['width'] > $dimensions['height'] ? ' landscape' : ' portrait';
$orientation = $dimensions['width'] && $dimensions['height'] ? $orientation : '';
if ( ! wp_doing_ajax() && $item->process->get_data()->get_optimized_size( false, 0, false ) > 100000 ) {
// LazyLoad.
$image_tag = '
';
$image_tag .= '';
} else {
$image_tag = '
';
}
} else {
$orientation = '';
$image_tag = '
';
}
?>
comparison_tool_button( $item ); ?>
maybe_set_item_folder( $item );
if ( empty( $item->folder_path ) ) {
return;
}
$format = '%s';
$filter = self::get_folder_filter();
if ( $filter !== $item->folder_id ) {
$format = '%s';
}
if ( '{{ROOT}}/' === $item->folder_path ) {
// It's the site's root.
printf( $format, __( 'Site\'s root', 'imagify' ) );
} else {
printf( $format, '/' . trim( $this->filesystem->make_path_relative( Imagify_Files_Scan::remove_placeholder( $item->folder_path ) ), '/' ) . '' );
}
if ( ! $item->is_folder_active ) {
echo '
';
_e( 'This folder is not selected for bulk optimization.', 'imagify' );
}
}
/**
* Handles the optimization data column output.
*
* @since 1.7
*
* @param object $item The current item. It must contain at least a $process property.
*/
public function column_optimization( $item ) {
$data = $item->process->get_data();
$media_id = $item->process->get_media()->get_id();
?>
-
get_original_size() ); ?>
is_optimized() ) { ?>
-
get_optimized_size() ); ?>
-
get_saving_percent(); ?>%
process->get_media()->is_image() ) {
$has_nextgen = $item->process->has_next_gen() ? __( 'Yes', 'imagify' ) : __( 'No', 'imagify' );
?>
-
process->get_data();
$row = $data->get_row();
$status = $data->get_optimization_status();
$messages = [];
if ( ! $status ) {
// File is not optimized.
$messages[] = '' . esc_html_x( 'Not optimized', 'Media File', 'imagify' ) . '';
} elseif ( ! empty( $row['error'] ) ) {
// Error or already optimized.
$messages[] = '' . esc_html( imagify_translate_api_message( $row['error'] ) ) . '';
}
if ( empty( $row['modified'] ) && ! $messages ) {
// No need to display this if we already have another message to display.
$messages[] = '' . esc_html__( 'No changes found', 'imagify' ) . '';
} elseif ( ! empty( $row['modified'] ) ) {
// The file has changed or is missing.
$messages[] = '' . esc_html__( 'The file has changed', 'imagify' ) . '';
}
echo implode( '
', $messages );
$this->refresh_status_button( $item );
}
/**
* Handles the optimization level column output.
*
* @since 1.7
*
* @param object $item The current item. It must contain at least a $process property.
*/
public function column_optimization_level( $item ) {
$data = $item->process->get_data();
if ( ! $data->is_error() ) {
echo imagify_get_optimization_level_label( $data->get_optimization_level(), '%ICON% %s' );
}
}
/**
* Handles the actions column output.
*
* @since 1.7
*
* @param object $item The current item. It must contain at least a $process property.
*/
public function column_actions( $item ) {
static $done = false;
if ( ! Imagify_Requirements::is_api_key_valid() ) {
// Stop the process if the API key isn't valid.
if ( ! $done ) {
// No need to display this on every row.
$done = true;
esc_html_e( 'Invalid API key', 'imagify' );
echo '
' . __( 'Check your Settings', 'imagify' ) . '';
}
return;
}
if ( $item->process->is_locked() ) {
Imagify_Views::get_instance()->print_template( 'button-processing', [
'label' => __( 'Optimizing...', 'imagify' ),
] );
return;
}
$this->optimize_button( $item );
$this->retry_button( $item );
$this->reoptimize_buttons( $item );
$this->generate_nextgen_versions_button( $item );
$this->delete_nextgen_versions_button( $item );
$this->restore_button( $item );
}
/**
* Prints a button to optimize the file.
*
* @since 1.7
*
* @param object $item The current item. It must contain at least a $process property.
*/
protected function optimize_button( $item ) {
if ( $item->process->get_data()->get_optimization_status() ) {
// Already optimized.
return;
}
$media = $item->process->get_media();
$class = $media->has_backup() ? ' file-has-backup' : '';
$url = get_imagify_admin_url( 'optimize-file', [
'attachment_id' => $media->get_id(),
] );
echo $this->views->get_template( 'button/optimize', [
'url' => $url,
'atts' => [
'class' => 'button-primary button-imagify-optimize' . $class,
],
] );
}
/**
* Prints a button to retry to optimize the file.
*
* @since 1.7
*
* @param object $item The current item. It must contain at least a $process property.
*/
protected function retry_button( $item ) {
$data = $item->process->get_data();
if ( ! $data->is_already_optimized() && ! $data->is_error() ) {
// Not optimized or successfully optimized.
return;
}
$media = $item->process->get_media();
$class = $media->has_backup() ? ' file-has-backup' : '';
$url = get_imagify_admin_url( 'optimize-file', [
'attachment_id' => $media->get_id(),
] );
echo $this->views->get_template( 'button/retry-optimize', [
'url' => $url,
'atts' => [
'class' => 'button button-imagify-optimize' . $class,
],
] );
echo '
';
}
/**
* Prints buttons to re-optimize the file to other levels.
*
* @since 1.7
*
* @param object $item The current item. It must contain at least a $process property.
*/
protected function reoptimize_buttons( $item ) {
$data = $item->process->get_data();
if ( ! $data->get_optimization_status() ) {
// Not optimized yet.
return;
}
$is_already_optimized = $data->is_already_optimized();
$media = $item->process->get_media();
$can_reoptimize = $is_already_optimized || $media->has_backup();
// Don't display anything if there is no backup or the image has been optimized.
if ( ! $can_reoptimize ) {
return;
}
$media_level = $data->get_optimization_level();
$data = [];
$url_args = [ 'attachment_id' => $media->get_id() ];
if ( $media_level < 1 ) {
$url_args['optimization_level'] = 2;
$data['optimization_level'] = 2;
$data['url'] = get_imagify_admin_url( 'reoptimize-file', $url_args );
echo $this->views->get_template( 'button/re-optimize', $data );
} elseif ( $media_level > 0 ) {
$url_args['optimization_level'] = 0;
$data['optimization_level'] = 0;
$data['url'] = get_imagify_admin_url( 'reoptimize-file', $url_args );
echo $this->views->get_template( 'button/re-optimize', $data );
}
}
/**
* Prints a button to generate Next gen versions if they are missing.
*
* @since 1.7
*
* @param object $item The current item. It must contain at least a $process property.
*/
protected function generate_nextgen_versions_button( $item ) {
$button = get_imagify_attachment_generate_nextgen_versions_link( $item->process );
if ( $button ) {
echo $button . '
';
}
}
/**
* Prints a button to delete next-gen versions when the status is "already_optimized".
*
* @since 1.9.6
*
* @param object $item The current item. It must contain at least a $process property.
*/
protected function delete_nextgen_versions_button( $item ) {
$button = get_imagify_attachment_delete_nextgen_versions_link( $item->process );
if ( $button ) {
echo $button . '
';
}
}
/**
* Prints a button to restore the file.
*
* @since 1.7
*
* @param object $item The current item. It must contain at least a $process property.
*/
protected function restore_button( $item ) {
$data = $item->process->get_data();
$media = $item->process->get_media();
if ( ! $data->is_optimized() || ! $media->has_backup() ) {
return;
}
$url = get_imagify_admin_url( 'restore-file', array(
'attachment_id' => $media->get_id(),
) );
echo $this->views->get_template( 'button/restore', [ 'url' => $url ] );
}
/**
* Prints a button to check if the file has been modified or not.
*
* @since 1.7
*
* @param object $item The current item. It must contain at least a $process property.
*/
protected function refresh_status_button( $item ) {
$url = get_imagify_admin_url( 'refresh-file-modified', array(
'attachment_id' => $item->process->get_media()->get_id(),
) );
echo '
';
echo $this->views->get_template( 'button/refresh-status', [ 'url' => $url ] );
}
/**
* Prints a button for the comparison tool (before / after optimization).
*
* @since 1.7
*
* @param object $item The current item. It must contain at least a $process property.
*/
protected function comparison_tool_button( $item ) {
$data = $item->process->get_data();
$media = $item->process->get_media();
if ( ! $data->is_optimized() || ! $media->has_backup() || ! $media->is_image() ) {
return;
}
$file_path = $media->get_fullsize_path();
if ( ! $file_path ) {
return;
}
$dimensions = $media->get_dimensions();
if ( $dimensions['width'] < 360 ) {
return;
}
$backup_url = $media->get_backup_url();
echo $this->views->get_template( 'button/compare-images', [
'url' => $backup_url,
'backup_url' => $backup_url,
'original_url' => $media->get_fullsize_url(),
'media_id' => $media->get_id(),
'width' => $dimensions['width'],
'height' => $dimensions['height'],
] );
if ( wp_doing_ajax() ) {
?>
folder_path ) ) {
return $item;
}
$item->folder_id = 0;
$item->folder_path = false;
$row = $item->process->get_data()->get_row();
if ( empty( $row['folder_id'] ) ) {
return $item;
}
$folder = Imagify_Folders_DB::get_instance()->get( $row['folder_id'] );
if ( ! $folder ) {
return $item;
}
$item->folder_id = $folder['folder_id'];
$item->folder_path = $folder['path'];
$item->is_folder_active = (bool) $folder['active'];
return $item;
}
/**
* Get the name of the default primary column.
*
* @since 1.7
*
* @return string Name of the default primary column, in this case, 'title'.
*/
protected function get_default_primary_column_name() {
return 'title';
}
/**
* Get a list of CSS classes for the WP_List_Table table tag.
*
* @since 1.7
*
* @return array List of CSS classes for the table tag.
*/
protected function get_table_classes() {
return array( 'widefat', 'fixed', 'striped', 'media', $this->_args['plural'] );
}
/**
* Allow to save the screen options when submitted by the user.
*
* @since 1.7
*
* @param bool|int $status Screen option value. Default false to skip.
* @param string $option The option name.
* @param int $value The number of rows to use.
* @return int|bool
*/
public static function save_screen_options( $status, $option, $value ) {
if ( self::PER_PAGE_OPTION === $option ) {
return (int) $value;
}
return $status;
}
/**
* Get the requested folder filter.
*
* @since 1.7
*
* @return string
*/
public static function get_folder_filter() {
static $filter;
if ( ! isset( $filter ) ) {
$filter = filter_input( INPUT_GET, 'folder-filter', FILTER_VALIDATE_INT );
$filter = max( 0, $filter );
}
return $filter;
}
/**
* Get the requested status filter.
*
* @since 1.7
*
* @return string
*/
public static function get_status_filter() {
static $filter;
if ( isset( $filter ) ) {
return $filter;
}
$values = array(
'optimized' => 1,
'unoptimized' => 1,
'errors' => 1,
);
$filter = isset( $_GET['status-filter'] ) ? trim( htmlspecialchars( wp_unslash( $_GET['status-filter'] ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
$filter = isset( $values[ $filter ] ) ? $filter : '';
return $filter;
}
}