* * @copyright (c) 2016, Incsub (http://incsub.com) */ namespace Smush\Core\Modules; use Smush\Core\Core; use Smush\Core\Helper; use Smush\Core\Integrations\Nextgen\Stats; use Smush\WP_Smush; use WP_Query; if ( ! defined( 'WPINC' ) ) { die; } /** * Class DB */ class DB { /** * DB constructor. */ public function __construct() { // Recalculate resize savings. add_action( 'wp_smush_image_resized', array( $this, 'resize_savings' ) ); // Update Conversion savings. add_action( 'wp_smush_png_jpg_converted', array( $this, 'conversion_savings' ) ); } /** * Filter the Posts object as per mime type. * * @param array $posts Object of Posts. * * @return mixed array of post ids */ private function filter_by_mime( $posts ) { if ( empty( $posts ) ) { return $posts; } foreach ( $posts as $post_k => $post ) { if ( ! isset( $post->post_mime_type ) || ! in_array( $post->post_mime_type, Core::$mime_types, true ) ) { unset( $posts[ $post_k ] ); } else { $posts[ $post_k ] = $post->ID; } } return $posts; } /** * Fetch all the unsmushed attachments * * @return array $attachments */ public function get_unsmushed_attachments() { if ( isset( $_REQUEST['ids'] ) ) { return array_map( 'intval', explode( ',', $_REQUEST['ids'] ) ); } /** Do not fetch more than this, any time Localizing all rows at once increases the page load and slows down everything */ $r_limit = apply_filters( 'wp_smush_max_rows', 5000 ); // Check if we can get the unsmushed attachments from the other two variables. if ( ! empty( WP_Smush::get_instance()->core()->attachments ) && ! empty( WP_Smush::get_instance()->core()->smushed_attachments ) ) { $unsmushed_posts = array_diff( WP_Smush::get_instance()->core()->attachments, WP_Smush::get_instance()->core()->smushed_attachments ); // Remove skipped attachments. if ( ! empty( WP_Smush::get_instance()->core()->smushed_attachments ) ) { $unsmushed_posts = array_diff( $unsmushed_posts, WP_Smush::get_instance()->core()->skipped_attachments ); } $unsmushed_posts = ! empty( $unsmushed_posts ) && is_array( $unsmushed_posts ) ? array_slice( $unsmushed_posts, 0, $r_limit ) : array(); } else { $limit = apply_filters( 'wp_smush_query_limit', 2000 ); $get_posts = true; $unsmushed_posts = array(); $args = array( 'fields' => array( 'ids', 'post_mime_type' ), 'post_type' => 'attachment', 'post_status' => 'any', 'orderby' => 'ID', 'order' => 'DESC', 'posts_per_page' => $limit, 'offset' => 0, 'meta_query' => array( array( 'key' => Smush::$smushed_meta_key, 'compare' => 'NOT EXISTS', ), array( 'key' => 'wp-smush-ignore-bulk', 'value' => 'true', 'compare' => 'NOT EXISTS', ), ), 'update_post_term_cache' => false, 'no_found_rows' => true, ); // Loop Over to get all the attachments. while ( $get_posts ) { // Remove the Filters added by WP Media Folder. do_action( 'wp_smush_remove_filters' ); $query = new WP_Query( $args ); if ( ! empty( $query->post_count ) && count( $query->posts ) > 0 ) { // Get a filtered list of post ids. $posts = $this->filter_by_mime( $query->posts ); // Merge the results. $unsmushed_posts = array_merge( $unsmushed_posts, $posts ); // Update the offset. $args['offset'] += $limit; } else { // If we didn't get any posts from query, set $get_posts to false. $get_posts = false; } // If we already got enough posts. if ( count( $unsmushed_posts ) >= $r_limit ) { $get_posts = false; } elseif ( ! empty( WP_Smush::get_instance()->core()->total_count ) && WP_Smush::get_instance()->core()->total_count <= $args['offset'] ) { // If total Count is set, and it is alread lesser than offset, don't query. $get_posts = false; } } } // Remove resmush list from unsmushed images. if ( ! empty( WP_Smush::get_instance()->core()->resmush_ids ) && is_array( WP_Smush::get_instance()->core()->resmush_ids ) ) { $unsmushed_posts = array_diff( $unsmushed_posts, WP_Smush::get_instance()->core()->resmush_ids ); } return $unsmushed_posts; } /** * Total image count. * * @param bool $force_update Force update. * * @return bool|int|mixed */ public function total_count( $force_update = false ) { // Retrieve from Cache. if ( ! $force_update && $count = wp_cache_get( 'total_count', 'wp-smush' ) ) { if ( $count ) { return $count; } } // Set Attachment IDs, and total count. $posts = $this->get_media_attachments( '', $force_update ); WP_Smush::get_instance()->core()->attachments = $posts; // Get total count from attachments. $total_count = ! empty( $posts ) && is_array( $posts ) ? count( $posts ) : 0; // Set total count. WP_Smush::get_instance()->core()->total_count = $total_count; wp_cache_add( 'total_count', $total_count, 'wp-smush' ); // Send the count. return $total_count; } /** * Get the media attachment ID/count. * * @param bool $return_count Return count. * @param bool $force_update Force update. * * @return array|bool|int|mixed */ public function get_media_attachments( $return_count = false, $force_update = false ) { global $wpdb; // Return results from cache. if ( ! $force_update ) { $posts = wp_cache_get( 'media_attachments', 'wp-smush' ); $count = ! empty( $posts ) ? count( $posts ) : 0; // Return results only if we've got any. if ( $count ) { return $return_count ? $count : $posts; } } $posts = array(); // Else Get it Fresh!! $offset = 0; $limit = apply_filters( 'wp_smush_query_limit', 2000 ); $mime = implode( "', '", Core::$mime_types ); // Remove the Filters added by WP Media Folder. do_action( 'wp_smush_remove_filters' ); $get_posts = true; while ( $get_posts ) { $results = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = 'attachment' AND post_status = 'inherit' AND post_mime_type IN ('$mime') ORDER BY `ID` DESC LIMIT %d, %d", $offset, $limit ) ); // Db call ok. if ( ! empty( $results ) && is_array( $results ) && count( $results ) > 0 ) { // Get a filtered list of post ids. $posts = array_merge( $posts, $results ); // Update the offset. $offset += $limit; } else { // If we didn't get any posts from query, set $get_posts to false. $get_posts = false; } } // Add the attachments to cache. wp_cache_add( 'media_attachments', $posts, 'wp-smush' ); if ( $return_count ) { return count( $posts ); } return $posts; } /** * Optimised images count or IDs. * * @param bool $return_ids Should return ids?. * @param bool $force_update Should force update?. * * @return array|int */ public function smushed_count( $return_ids = false, $force_update = false ) { global $wpdb; // Don't query again, if the variable is already set. if ( ! $return_ids && ! empty( WP_Smush::get_instance()->core()->smushed_count ) && WP_Smush::get_instance()->core()->smushed_count > 0 ) { return WP_Smush::get_instance()->core()->smushed_count; } // Key for cache. $key = $return_ids ? WP_SMUSH_PREFIX . 'smushed_ids' : WP_SMUSH_PREFIX . 'smushed_count'; // If not forced to update, try to get from cache. if ( ! $force_update ) { // TODO: This is an issue. If not forcing the update, the cached version is never incremented during image Smush. $smushed_count = wp_cache_get( $key, 'wp-smush' ); // Return the cache value if cache is set. if ( false !== $smushed_count && ! empty( $smushed_count ) ) { return $smushed_count; } } /** * Allows to set a limit of mysql query * Default value is 2000. */ $limit = apply_filters( 'wp_smush_query_limit', 2000 ); $offset = 0; $query_next = true; $posts = array(); // Remove the Filters added by WP Media Folder. do_action( 'wp_smush_remove_filters' ); while ( $query_next && $results = $wpdb->get_col( $wpdb->prepare( "SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key=%s ORDER BY `post_id` DESC LIMIT $offset, $limit", Smush::$smushed_meta_key ) ) ) { if ( ! is_wp_error( $results ) && count( $results ) > 0 ) { $posts = array_merge( $posts, $results ); } // Update the offset. $offset += $limit; // Compare the Offset value to total images. if ( ! empty( WP_Smush::get_instance()->core()->total_count ) && WP_Smush::get_instance()->core()->total_count <= $offset ) { $query_next = false; } } // Remove resmush IDs from the list. if ( ! empty( WP_Smush::get_instance()->core()->resmush_ids ) && is_array( WP_Smush::get_instance()->core()->resmush_ids ) ) { $posts = array_diff( $posts, WP_Smush::get_instance()->core()->resmush_ids ); } // Set in cache. wp_cache_set( $key, $return_ids ? $posts : count( $posts ), 'wp-smush' ); return $return_ids ? $posts : count( $posts ); } /** * Return the number of skipped attachments. * * @since 3.0 * * @param bool $force Force data refresh. * * @return array */ public function skipped_count( $force ) { if ( ! $force && $images = wp_cache_get( 'skipped_images', 'wp-smush' ) ) { return $images; } global $wpdb; $images = $wpdb->get_col( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key='wp-smush-ignore-bulk'" ); // Db call ok. wp_cache_set( 'skipped_images', $images, 'wp-smush' ); return $images; } /** * Returns/Updates the number of images Super Smushed. * * @param string $type media/nextgen, Type of images to get/set the super smushed count for. * * @param array $attachments Optional, By default Media attachments will be fetched. * * @return array|mixed * * @todo Refactor Method, Separate Media Library and Nextgen, moreover nextgen functionality is broken */ public function super_smushed_count( $type = 'media', $attachments = array() ) { if ( 'media' === $type ) { $count = $this->get_super_smushed_attachments(); } else { $key = 'wp-smush-super_smushed_nextgen'; // Clear up the stats, if there are no images. if ( method_exists( 'Stats', 'total_count' ) && 0 === Stats::total_count() ) { delete_option( $key ); } // Flag to check if we need to re-evaluate the count. $revaluate = false; $super_smushed = get_option( $key, false ); // Check if need to revalidate. if ( ! $super_smushed || empty( $super_smushed ) || empty( $super_smushed['ids'] ) ) { $super_smushed = array( 'ids' => array(), ); $revaluate = true; } else { $last_checked = $super_smushed['timestamp']; $diff = $last_checked - current_time( 'timestamp' ); // Difference in hour. $diff_h = $diff / 3600; // if last checked was more than 1 hours. if ( $diff_h > 1 ) { $revaluate = true; } } // Do not reevaluate stats if nextgen attachments are not provided. if ( 'nextgen' === $type && empty( $attachments ) && $revaluate ) { $revaluate = false; } // Need to scan all the image. if ( $revaluate ) { // Get all the Smushed attachments ids // Note: Wrong Method called, it'll fetch media images and not NextGen images // Should be $attachments, in place of $super_smushed_images. $super_smushed_images = $this->get_super_smushed_attachments( true ); if ( ! empty( $super_smushed_images ) && is_array( $super_smushed_images ) ) { // Iterate over all the attachments to check if it's already there in list, else add it. foreach ( $super_smushed_images as $id ) { if ( ! in_array( $id, $super_smushed['ids'] ) ) { $super_smushed['ids'][] = $id; } } } $super_smushed['timestamp'] = current_time( 'timestamp' ); update_option( $key, $super_smushed, false ); } $count = ! empty( $super_smushed['ids'] ) ? count( $super_smushed['ids'] ) : 0; } return $count; } /** * Updates the Meta for existing smushed images and retrieves the count of Super Smushed images * * @param bool $return_ids Whether to return ids or just the count. * * @return array|int Super Smushed images Id / Count of Super Smushed images */ private function get_super_smushed_attachments( $return_ids = false ) { // Get all the attachments with wp-smush-lossy. $limit = apply_filters( 'wp_smush_query_limit', 2000 ); $get_posts = true; $super_smushed = array(); $args = array( 'fields' => array( 'ids', 'post_mime_type' ), 'post_type' => 'attachment', 'post_status' => 'any', 'orderby' => 'ID', 'order' => 'DESC', 'posts_per_page' => $limit, 'offset' => 0, 'meta_query' => array( array( 'key' => 'wp-smush-lossy', 'value' => 1, ), ), 'update_post_term_cache' => false, 'no_found_rows' => true, ); // Loop over to get all the attachments. while ( $get_posts ) { // Remove the Filters added by WP Media Folder. do_action( 'wp_smush_remove_filters' ); $query = new WP_Query( $args ); if ( ! empty( $query->post_count ) && count( $query->posts ) > 0 ) { $posts = $this->filter_by_mime( $query->posts ); // Merge the results. $super_smushed = array_merge( $super_smushed, $posts ); // Update the offset. $args['offset'] += $limit; } else { // If we didn't get any posts from query, set $get_posts to false. $get_posts = false; } // If total Count is set, and it is alread lesser than offset, don't query. if ( ! empty( WP_Smush::get_instance()->core()->total_count ) && WP_Smush::get_instance()->core()->total_count <= $args['offset'] ) { $get_posts = false; } } // Remove resmush IDs from the list. if ( ! empty( WP_Smush::get_instance()->core()->resmush_ids ) && is_array( WP_Smush::get_instance()->core()->resmush_ids ) ) { $super_smushed = array_diff( $super_smushed, WP_Smush::get_instance()->core()->resmush_ids ); } return $return_ids ? $super_smushed : count( $super_smushed ); } /** * Get the savings from image resizing, And force update if set to true. * * @param bool $force_update , Whether to Re-Calculate all the stats or not. * @param bool $format Format the Bytes in readable format. * @param bool $return_count Return the resized image count, Set to false by default. * * @return array|bool|mixed|string Array of { * 'bytes', * 'before_size', * 'after_size' * } */ public function resize_savings( $force_update = true, $format = false, $return_count = false ) { $savings = ''; if ( ! $force_update ) { $savings = wp_cache_get( WP_SMUSH_PREFIX . 'resize_savings', 'wp-smush' ); } $count = wp_cache_get( WP_SMUSH_PREFIX . 'resize_count', 'wp-smush' ); // If resize image count is not stored in db, recalculate. if ( $return_count && false === $count ) { $count = 0; $force_update = true; } // If nothing in cache, calculate it. if ( empty( $savings ) || $force_update ) { $savings = array( 'bytes' => 0, 'size_before' => 0, 'size_after' => 0, ); $limit = apply_filters( 'wp_smush_query_limit', 2000 ); $offset = 0; $query_next = true; global $wpdb; while ( $query_next ) { $resize_data = $wpdb->get_results( $wpdb->prepare( "SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key=%s LIMIT %d, %d", WP_SMUSH_PREFIX . 'resize_savings', $offset, $limit ) ); // Db call ok. if ( ! empty( $resize_data ) ) { foreach ( $resize_data as $data ) { // Skip resmush ids. if ( ! empty( WP_Smush::get_instance()->core()->resmush_ids ) && in_array( $data->post_id, WP_Smush::get_instance()->core()->resmush_ids ) ) { continue; } if ( ! empty( $data ) ) { $meta = maybe_unserialize( $data->meta_value ); if ( ! empty( $meta ) && ! empty( $meta['bytes'] ) ) { $savings['bytes'] += $meta['bytes']; $savings['size_before'] += $meta['size_before']; $savings['size_after'] += $meta['size_after']; } } $count++; } } // Update the offset. $offset += $limit; // Compare the offset value to total images. if ( ! empty( WP_Smush::get_instance()->core()->total_count ) && WP_Smush::get_instance()->core()->total_count <= $offset ) { $query_next = false; } elseif ( ! $resize_data ) { // If we didn't get any results. $query_next = false; } } if ( $format ) { $savings['bytes'] = size_format( $savings['bytes'], 1 ); } wp_cache_set( WP_SMUSH_PREFIX . 'resize_savings', $savings, 'wp-smush' ); wp_cache_set( WP_SMUSH_PREFIX . 'resize_count', $count, 'wp-smush' ); } return $return_count ? $count : $savings; } /** * Return/Update PNG -> JPG Conversion savings * * @param bool $force_update Whether to force update the conversion savings or not. * @param bool $format Optionally return formatted savings. * * @return array Savings */ public function conversion_savings( $force_update = true, $format = false ) { $savings = ''; if ( ! $force_update ) { $savings = wp_cache_get( WP_SMUSH_PREFIX . 'pngjpg_savings', 'wp-smush' ); } // If nothing in cache, calculate it. if ( empty( $savings ) || $force_update ) { $savings = array( 'bytes' => 0, 'size_before' => 0, 'size_after' => 0, ); $limit = apply_filters( 'wp_smush_query_limit', 2000 ); $offset = 0; $query_next = true; global $wpdb; while ( $query_next ) { $conversion_savings = $wpdb->get_results( $wpdb->prepare( "SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key=%s LIMIT %d, %d", WP_SMUSH_PREFIX . 'pngjpg_savings', $offset, $limit ) ); // Db call ok. if ( ! empty( $conversion_savings ) ) { foreach ( $conversion_savings as $data ) { // Skip resmush IDs. if ( ! empty( WP_Smush::get_instance()->core()->resmush_ids ) && in_array( $data->post_id, WP_Smush::get_instance()->core()->resmush_ids ) ) { continue; } if ( ! empty( $data ) ) { $meta = maybe_unserialize( $data->meta_value ); if ( is_array( $meta ) ) { foreach ( $meta as $size ) { if ( ! empty( $size ) && is_array( $size ) ) { $savings['bytes'] += $size['bytes']; $savings['size_before'] += $size['size_before']; $savings['size_after'] += $size['size_after']; } } } } } } // Update the offset. $offset += $limit; // Compare the Offset value to total images. if ( ! empty( WP_Smush::get_instance()->core()->total_count ) && WP_Smush::get_instance()->core()->total_count < $offset ) { $query_next = false; } elseif ( ! $conversion_savings ) { // If we didn' got any results. $query_next = false; } } if ( $format ) { $savings['bytes'] = size_format( $savings['bytes'], 1 ); } wp_cache_set( WP_SMUSH_PREFIX . 'pngjpg_savings', $savings, 'wp-smush' ); } return $savings; } /** * Get the savings for the given set of attachments * * @param array $attachments Array of attachment IDs. * * @return array Stats * array( * 'size_before' => 0, * 'size_after' => 0, * 'savings_resize' => 0, * 'savings_conversion' => 0 * ) */ public function get_stats_for_attachments( $attachments = array() ) { $stats = array( 'size_before' => 0, 'size_after' => 0, 'savings_resize' => 0, 'savings_conversion' => 0, 'count_images' => 0, 'count_supersmushed' => 0, 'count_smushed' => 0, 'count_resize' => 0, 'count_remaining' => 0, ); // If we don't have any attachments, return empty array. if ( empty( $attachments ) || ! is_array( $attachments ) ) { return $stats; } // Loop over all the attachments to get the cummulative savings. foreach ( $attachments as $attachment ) { $smush_stats = get_post_meta( $attachment, Smush::$smushed_meta_key, true ); $resize_savings = get_post_meta( $attachment, WP_SMUSH_PREFIX . 'resize_savings', true ); $conversion_savings = Helper::get_pngjpg_savings( $attachment ); if ( ! empty( $smush_stats['stats'] ) ) { // Combine all the stats, and keep the resize and send conversion settings separately. $stats['size_before'] += ! empty( $smush_stats['stats']['size_before'] ) ? $smush_stats['stats']['size_before'] : 0; $stats['size_after'] += ! empty( $smush_stats['stats']['size_after'] ) ? $smush_stats['stats']['size_after'] : 0; } $stats['count_images'] += ! empty( $smush_stats['sizes'] ) && is_array( $smush_stats['sizes'] ) ? count( $smush_stats['sizes'] ) : 0; $stats['count_supersmushed'] += ! empty( $smush_stats['stats'] ) && $smush_stats['stats']['lossy'] ? 1 : 0; // Add resize saving stats. if ( ! empty( $resize_savings ) ) { // Add resize and conversion savings. $stats['savings_resize'] += ! empty( $resize_savings['bytes'] ) ? $resize_savings['bytes'] : 0; $stats['size_before'] += ! empty( $resize_savings['size_before'] ) ? $resize_savings['size_before'] : 0; $stats['size_after'] += ! empty( $resize_savings['size_after'] ) ? $resize_savings['size_after'] : 0; $stats['count_resize'] += 1; } // Add conversion saving stats. if ( ! empty( $conversion_savings ) ) { // Add resize and conversion savings. $stats['savings_conversion'] += ! empty( $conversion_savings['bytes'] ) ? $conversion_savings['bytes'] : 0; $stats['size_before'] += ! empty( $conversion_savings['size_before'] ) ? $conversion_savings['size_before'] : 0; $stats['size_after'] += ! empty( $conversion_savings['size_after'] ) ? $conversion_savings['size_after'] : 0; } $stats['count_smushed'] += 1; } return $stats; } /** * Get the attachments that can be restored. * * @param bool $return_ids Whether to return ids or just the count. * * @return array|int Attachments IDs / Number of attachments. */ public function get_attachments_with_backups( $return_ids = false ) { $images = wp_cache_get( 'images_with_backups', 'wp-smush' ); if ( ! $images ) { global $wpdb; $images = $wpdb->get_col( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='_wp_attachment_backup_sizes' AND post_id IN (SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='wp-smpro-smush-data')" ); // Db call ok. if ( $images ) { wp_cache_set( 'images_with_backups', $images, 'wp-smush' ); } } if ( $return_ids ) { return $images; } return count( $images ); } /** * Returns true if a database table column exists. Otherwise returns false. * * @link http://stackoverflow.com/a/5943905/2489248 * @global \wpdb $wpdb * * @param string $table_name Name of table we will check for column existence. * @param string $column_name Name of column we are checking for. * * @return boolean True if column exists. Else returns false. */ private function table_column_exists( $table_name, $column_name ) { global $wpdb; $column = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = %s ', DB_NAME, $table_name, $column_name ) ); // Db call ok; no-cache ok. if ( ! empty( $column ) ) { return true; } return false; } /** * Drops a specified index from a table. * * @since 1.0.1 * * @global \wpdb $wpdb * * @param string $table Database table name. * @param string $index Index name to drop. * @return true True, when finished. */ private function drop_index( $table, $index ) { global $wpdb; $wpdb->query( $wpdb->prepare( "ALTER TABLE %s DROP INDEX %s", $table, $index ) ); // Db call ok; no-cache ok. return true; } /** * Update path_hash, and store a flag if all the rows were updated * * TODO: Stop running this function after 2-3 updates using version check */ public function update_dir_path_hash() { // If we've already performed the update. if ( get_option( 'smush-directory-path-hash-updated', false ) ) { return; } global $wpdb; // Check if column exists. if ( ! $this->table_column_exists( $wpdb->prefix . 'smush_dir_images', 'path_hash' ) ) { return; } // Update the rows. $wpdb->query( "UPDATE {$wpdb->prefix}smush_dir_images SET path_hash = MD5(path) WHERE path IS NOT NULL" ); // Check if there are any pending rows that needs to be updated. $pending_rows = "SELECT count(*) FROM {$wpdb->prefix}smush_dir_images WHERE path_hash is NULL AND path IS NOT NULL"; $index_exists = "SHOW INDEX FROM {$wpdb->prefix}smush_dir_images WHERE KEY_NAME = 'path'"; // If all the rows are updated and Index exists. if ( ! $wpdb->get_var( $pending_rows ) && $wpdb->get_var( $index_exists ) != null ) { $this->drop_index( $wpdb->prefix . 'smush_dir_images', 'path' ); update_option( 'smush-directory-path-hash-updated', 1 ); } } }