id = (int) $id; $this->get_row(); } elseif ( is_array( $id ) || is_object( $id ) ) { $prim_key = $this->get_row_db_instance()->get_primary_key(); $this->row = (array) $id; $this->id = $this->row[ $prim_key ]; } else { $this->invalidate_row(); } $this->filesystem = Imagify_Filesystem::get_instance(); $this->optimization_state_transient = 'imagify-file-async-in-progress-' . $this->id; } /** * Get the original file path. * * @since 1.7 * @access public * @author Grégory Viguier * * @return string */ public function get_original_path() { $row = $this->get_row(); if ( ! $row || empty( $row['path'] ) ) { return ''; } return Imagify_Files_Scan::remove_placeholder( $row['path'] ); } /** * Get the original file URL. * * @since 1.7 * @access public * @author Grégory Viguier * * @return string */ public function get_original_url() { $row = $this->get_row(); if ( ! $row || empty( $row['path'] ) ) { return ''; } return Imagify_Files_Scan::remove_placeholder( $row['path'], 'url' ); } /** * Get the backup file path, even if the file doesn't exist. * * @since 1.7 * @access public * @author Grégory Viguier * * @return string|bool The file path. False on failure. */ public function get_raw_backup_path() { if ( ! $this->is_valid() ) { return false; } return Imagify_Custom_Folders::get_file_backup_path( $this->get_original_path() ); } /** * Get the backup URL. * * @since 1.7 * @access public * @author Grégory Viguier * * @return string|bool The file URL. False on failure. */ public function get_backup_url() { if ( ! $this->is_valid() ) { return false; } return site_url( '/' ) . $this->filesystem->make_path_relative( $this->get_raw_backup_path() ); } /** * Get the optimization data. * * @since 1.7 * @access public * @author Grégory Viguier * * @return array */ public function get_data() { if ( ! $this->is_valid() ) { return array(); } $data = array_merge( $this->get_row_db_instance()->get_column_defaults(), $this->get_row() ); unset( $data['file_id'] ); return $data; } /** * Get the optimization level. * * @since 1.7 * @access public * @author Grégory Viguier * * @return int|bool */ public function get_optimization_level() { $row = $this->get_row(); return isset( $row['optimization_level'] ) && is_int( $row['optimization_level'] ) ? $row['optimization_level'] : false; } /** * Get the file optimization status (success, already_optimized, or error). * * @since 1.7 * @access public * @author Grégory Viguier * * @return string */ public function get_status() { $row = $this->get_row(); return ! empty( $row['status'] ) ? $row['status'] : ''; } /** * Get width and height of the original image. * * @since 1.7 * @access public * @author Grégory Viguier * * @return array */ public function get_dimensions() { $row = $this->get_row(); return array( 'width' => ! empty( $row['width'] ) ? $row['width'] : 0, 'height' => ! empty( $row['height'] ) ? $row['height'] : 0, ); } /** * Get the attachment error if there is one. * * @since 1.7 * @access public * @author Grégory Viguier * * @return string The message error */ public function get_optimized_error() { $row = $this->get_row(); return ! empty( $row['error'] ) && is_string( $row['error'] ) ? trim( $row['error'] ) : ''; } /** * Count number of optimized sizes. * * @since 1.7 * @access public * @author Grégory Viguier * * @return int */ public function get_optimized_sizes_count() { return $this->get_status() === 'success' ? 1 : 0; } /** * Delete the data related to optimization. * * @since 1.7 * @access public * @author Grégory Viguier */ public function delete_imagify_data() { if ( ! $this->is_valid() ) { return; } $this->update_row( $this->get_reset_imagify_data() ); } /** * Tell if the current file extension is supported. * If it's in the DB, it's supported. * * @since 1.7 * @access public * @author Grégory Viguier * * @return bool */ public function is_extension_supported() { return $this->is_valid(); } /** * Tell if the current file mime type is supported. * If it's in the DB, it's supported. * * @since 1.7 * @access public * @author Grégory Viguier * * @return bool */ public function is_mime_type_supported() { return $this->is_valid(); } /** * Tell if the current attachment has the required WP metadata. * Well, these are not attachments, so... * * @since 1.7 * @access public * @author Grégory Viguier * * @return bool */ public function has_required_metadata() { return $this->is_valid(); } /** * Get the original file size. * * @since 1.7 * @access public * @author Grégory Viguier * * @param bool $human_format True to display the image human format size (1Mb). * @param int $decimals Precision of number of decimal places. * @return string|int */ public function get_original_size( $human_format = true, $decimals = 2 ) { if ( ! $this->is_valid() ) { return $human_format ? imagify_size_format( 0, $decimals ) : 0; } $row = $this->get_row(); if ( ! empty( $row['original_size'] ) ) { $size = $row['original_size']; } else { // Check for the backup file first. $file_path = $this->get_backup_path(); if ( ! $file_path ) { $file_path = $this->get_original_path(); $file_path = $file_path && $this->filesystem->exists( $file_path ) ? $file_path : false; } $size = $file_path ? $this->filesystem->size( $file_path ) : 0; } if ( $human_format ) { return imagify_size_format( (int) $size, $decimals ); } return (int) $size; } /** * Get the optimized file size. * * @since 1.7 * @access public * @author Grégory Viguier * * @param bool $human_format True to display the image human format size (1Mb). * @param int $decimals Precision of number of decimal places. * @return string|int */ public function get_optimized_size( $human_format = true, $decimals = 2 ) { if ( ! $this->is_valid() ) { return $human_format ? imagify_size_format( 0, $decimals ) : 0; } $row = $this->get_row(); if ( ! empty( $row['optimized_size'] ) ) { $size = $row['optimized_size']; } else { $file_path = $this->get_original_path(); $file_path = $file_path && $this->filesystem->exists( $file_path ) ? $file_path : false; $size = $file_path ? $this->filesystem->size( $file_path ) : 0; } if ( $human_format ) { return imagify_size_format( (int) $size, $decimals ); } return (int) $size; } /** * Get the optimized attachment size. * * @since 1.7 * @access public * @author Grégory Viguier * * @return float A 2-decimals float. */ public function get_saving_percent() { if ( ! $this->is_valid() ) { return round( (float) 0, 2 ); } $row = $this->get_row(); if ( ! empty( $row['percent'] ) ) { return $row['percent'] / 100; } $original_size = $this->get_original_size( false ); if ( ! $original_size ) { return round( (float) 0, 2 ); } $optimized_size = $this->get_optimized_size( false ); if ( ! $optimized_size ) { return round( (float) 0, 2 ); } return round( ( $original_size - $optimized_size ) / $original_size * 100, 2 ); } /** * Get the overall optimized size (all thumbnails). * And since we don't have thumbnails... * * @since 1.7 * @access public * @author Grégory Viguier * * @return float A 2-decimals float. */ public function get_overall_saving_percent() { return $this->get_saving_percent(); } /** * Get the statistics of the file. * * @since 1.7 * @access public * @author Grégory Viguier * * @param string $size The thumbnail slug. Not used here. * @param string $key The specific data slug. * @return array|string */ public function get_size_data( $size = null, $key = null ) { if ( ! isset( $key ) ) { $key = $size; } if ( ! $this->is_valid() ) { return isset( $key ) ? '' : array(); } $data = imagify_merge_intersect( $this->get_row(), array( 'original_size' => 0, 'optimized_size' => false, 'percent' => 0, 'status' => false, 'error' => false, ) ); $data['success'] = 'success' === $data['status']; if ( $data['status'] ) { unset( $data['status'], $data['error'] ); if ( empty( $data['percent'] ) && $data['original_size'] && $data['optimized_size'] ) { $data['percent'] = round( ( $data['original_size'] - $data['optimized_size'] ) / $data['original_size'] * 100, 2 ); } elseif ( ! empty( $data['percent'] ) ) { $data['percent'] = $data['percent'] / 100; } } else { unset( $data['status'], $data['original_size'], $data['optimized_size'], $data['percent'] ); } if ( isset( $key ) ) { return isset( $data[ $key ] ) ? $data[ $key ] : ''; } return $data; } /** * Get the global statistics data or a specific one. * * @since 1.7 * @access public * @author Grégory Viguier * * @param string $key The specific data slug. * @return array|string */ public function get_stats_data( $key = null ) { if ( ! $this->is_valid() ) { return isset( $key ) ? '' : array(); } $stats = $this->get_size_data( $key ); $default = array( 'original_size' => 0, 'optimized_size' => 0, 'percent' => 0, ); return imagify_merge_intersect( $stats, $default ); } /** * Since these files are not resized, this method is not needed. * * @since 1.7 * @access public * @author Grégory Viguier * * @return bool */ public function update_metadata_size() { return false; } /** * Get the unoptimized sizes for a specific attachment. * * @since 1.7 * @access public * @author Grégory Viguier * * @return array */ public function get_unoptimized_sizes() { return array(); } /** * Get default values used to reset Imagify data. * * @since 1.7 * @access public * @author Grégory Viguier */ public function get_reset_imagify_data() { static $column_defaults; if ( ! isset( $column_defaults ) ) { $column_defaults = $this->get_row_db_instance()->get_column_defaults(); // All DB columns that have `null` as default value, are Imagify data. foreach ( $column_defaults as $column_name => $value ) { if ( 'hash' === $column_name || 'modified' === $column_name ) { continue; } if ( isset( $value ) ) { unset( $column_defaults[ $column_name ] ); } } } $imagify_columns = $column_defaults; // Also set the new file hash. $file_path = $this->get_original_path(); if ( $file_path && $this->filesystem->exists( $file_path ) ) { $imagify_columns['hash'] = md5_file( $file_path ); } return $imagify_columns; } /** * Fills statistics data with values from $data array. * * @since 1.7 * @access public * @author Grégory Viguier * * @param array $data The statistics data. * @param object $response The API response. * @param string $size The attachment size key. Not used here. * @return bool|array False if the original size has an error or an array contains the data for other result. */ public function fill_data( $data, $response, $size = null ) { $data = is_array( $data ) ? $data : array(); $data = imagify_merge_intersect( $data, $this->get_reset_imagify_data() ); if ( is_wp_error( $response ) ) { // Error or already optimized. $data['error'] = $response->get_error_message(); if ( false !== strpos( $data['error'], 'This image is already compressed' ) ) { $data['status'] = 'already_optimized'; } else { $data['status'] = 'error'; } return $data; } // Success. $old_data = $this->get_data(); $original_size = $old_data['original_size']; $data['percent'] = 0; $data['status'] = 'success'; if ( ! empty( $response->original_size ) && ! $original_size ) { $data['original_size'] = (int) $response->original_size; $original_size = $data['original_size']; } if ( ! empty( $response->new_size ) ) { $data['optimized_size'] = (int) $response->new_size; } else { $file_path = $this->get_original_path(); $file_path = $file_path && $this->filesystem->exists( $file_path ) ? $file_path : false; $data['optimized_size'] = $file_path ? $this->filesystem->size( $file_path ) : 0; } if ( $original_size && $data['optimized_size'] ) { $data['percent'] = round( ( $original_size - $data['optimized_size'] ) / $original_size * 10000 ); } return $data; } /** * Optimize the file with Imagify. * * @since 1.7 * @access public * @author Grégory Viguier * * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal). * @param array $metadata The attachment meta data. Not used here. * @return bool|object True on success (status success or already_optimized). A WP_Error object on failure. */ public function optimize( $optimization_level = null, $metadata = null ) { // Check if the file extension is allowed. if ( ! $this->is_extension_supported() ) { return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) ); } $optimization_level = is_numeric( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' ); // Check if the image is already optimized. if ( $this->is_optimized() && ( $this->get_optimization_level() === $optimization_level ) ) { return new WP_Error( 'same_optimization_level', __( 'This file is already optimized with this level.', 'imagify' ) ); } /** * Fires before optimizing a file. * * @since 1.7 * @author Grégory Viguier * * @param int $id The file ID. */ do_action( 'before_imagify_optimize_file', $this->id ); $this->set_running_status(); // Optimize the image. $response = do_imagify( $this->get_original_path(), array( 'optimization_level' => $optimization_level, 'context' => 'File', 'original_size' => $this->get_original_size( false ), 'backup_path' => $this->get_raw_backup_path(), ) ); // Fill the data. $data = $this->fill_data( array( 'optimization_level' => $optimization_level, ), $response ); // Save the data. $this->update_row( $data ); if ( is_wp_error( $response ) ) { $this->delete_running_status(); if ( 'error' === $data['status'] ) { return $response; } // Already optimized. return true; } /** * Fires after optimizing an attachment. * * @since 1.7 * @author Grégory Viguier * * @param int $id The attachment ID. * @param array $optimized_data The optimization data. */ do_action( 'after_imagify_optimize_file', $this->id, $this->get_data() ); $this->delete_running_status(); return true; } /** * Process an attachment restoration from the backup file. * * @since 1.7 * @access public * @author Grégory Viguier * * @return bool|object True on success (status success or already_optimized). A WP_Error object on failure. */ public function restore() { // Check if the file extension is allowed. if ( ! $this->is_extension_supported() ) { return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) ); } $backup_path = $this->get_backup_path(); // Stop the process if there is no backup file to restore. if ( ! $backup_path ) { return new WP_Error( 'source_doesnt_exist', __( 'The backup file does not exist.', 'imagify' ) ); } $file_path = $this->get_original_path(); if ( ! $file_path ) { return new WP_Error( 'empty_path', __( 'The file path is empty.', 'imagify' ) ); } /** * Fires before restoring a file. * * @since 1.7 * @author Grégory Viguier * * @param int $id The file ID. */ do_action( 'before_imagify_restore_file', $this->id ); // Create the original image from the backup. $this->filesystem->copy( $backup_path, $file_path, true ); $this->filesystem->chmod_file( $file_path ); // Remove old optimization data. $this->delete_imagify_data(); /** * Fires after restoring a file. * * @since 1.7 * @author Grégory Viguier * * @param int $id The file ID. */ do_action( 'after_imagify_restore_file', $this->id ); } /** ----------------------------------------------------------------------------------------- */ /** DB ROW ================================================================================== */ /** ----------------------------------------------------------------------------------------- */ /** * Invalidate the row. * * @since 1.7 * @author Grégory Viguier * @access public * * @return array The row */ public function invalidate_row() { // Since the ID doesn't exist in any other table (not a Post ID, not a NGG gallery ID), it must be reset. $this->id = 0; $this->row = array(); return $this->row; } }