OwlCyberSecurity - MANAGER
Edit File: class-imagify-custom-folders.php
<?php defined( 'ABSPATH' ) || die( 'Cheatin’ uh?' ); /** * Class that regroups things about "custom folders". * * @since 1.7 * @author Grégory Viguier */ class Imagify_Custom_Folders { /** * Class version. * * @var string */ const VERSION = '1.1'; /** ----------------------------------------------------------------------------------------- */ /** BACKUP FOLDER =========================================================================== */ /** ----------------------------------------------------------------------------------------- */ /** * Get the path to the backups directory (custom folders). * * @since 1.7 * @access public * @author Grégory Viguier * * @return string Path to the backups directory. */ public static function get_backup_dir_path() { static $backup_dir; if ( isset( $backup_dir ) ) { return $backup_dir; } $filesystem = imagify_get_filesystem(); $backup_dir = $filesystem->get_site_root() . 'imagify-backup/'; /** * Filter the backup directory path (custom folders). * * @since 1.7 * @author Grégory Viguier * * @param string $backup_dir The backup directory path. */ $backup_dir = apply_filters( 'imagify_files_backup_directory', $backup_dir ); $backup_dir = $filesystem->normalize_dir_path( $backup_dir ); return $backup_dir; } /** * Tell if the folder containing the backups is writable (custom folders). * * @since 1.7 * @access public * @author Grégory Viguier * * @return bool */ public static function backup_dir_is_writable() { return imagify_get_filesystem()->make_dir( self::get_backup_dir_path() ); } /** * Get the backup path of a specific file (custom folders). * * @since 1.7 * @access public * @author Grégory Viguier * * @param string $file_path The file path. * @return string|bool The backup path. False on failure. */ public static function get_file_backup_path( $file_path ) { $file_path = wp_normalize_path( (string) $file_path ); $site_root = imagify_get_filesystem()->get_site_root(); $backup_dir = self::get_backup_dir_path(); if ( ! $file_path ) { return false; } return preg_replace( '@^' . preg_quote( $site_root, '@' ) . '@', $backup_dir, $file_path ); } /** * Add index.php files recursively to a given directory and all its subdirectories. * * @since 1.9.11 * * @param string $backup_dir (optional) Path to the directory where we will start adding indexes. * Defaults to custom-folders backup dir. * * @return void */ public static function add_indexes( $backup_dir = '' ) { $filesystem = Imagify_Filesystem::get_instance(); if ( empty( $backup_dir ) ) { $backup_dir = self::get_backup_dir_path(); } if ( ! $filesystem->is_writable( $backup_dir ) ) { return; } try { $directory = new RecursiveDirectoryIterator( $backup_dir ); $iterator = new RecursiveIteratorIterator( $directory ); foreach ( $iterator as $fileinfo ) { if ( '.' !== $fileinfo->getFilename() ) { continue; } $path = trailingslashit( $fileinfo->getRealPath() ); if ( ! $filesystem->is_file( $path . 'index.html' ) && ! $filesystem->is_file( $path . 'index.php' ) ) { $filesystem->touch( $path . 'index.php' ); } } } catch ( Exception $e ) { return; } } /** ----------------------------------------------------------------------------------------- */ /** SINGLE FILE ============================================================================= */ /** ----------------------------------------------------------------------------------------- */ /** * Insert a file into the DB. * * @since 1.7 * @access public * @author Grégory Viguier * * @param array $args An array of arguments to pass to Imagify_Files_DB::insert(). Required values are 'folder_id' and ( 'path' or 'file_path'). * @return int The file ID on success. 0 on failure. */ public static function insert_file( $args = array() ) { if ( empty( $args['folder_id'] ) ) { return 0; } if ( empty( $args['path'] ) ) { if ( empty( $args['file_path'] ) ) { return 0; } $args['path'] = Imagify_Files_Scan::add_placeholder( $args['file_path'] ); } if ( empty( $args['file_path'] ) ) { $args['file_path'] = Imagify_Files_Scan::remove_placeholder( $args['path'] ); } $filesystem = imagify_get_filesystem(); if ( ! $filesystem->is_readable( $args['file_path'] ) ) { return 0; } if ( empty( $args['file_date'] ) || '0000-00-00 00:00:00' === $args['file_date'] ) { $args['file_date'] = $filesystem->get_date( $args['file_path'] ); } if ( empty( $args['mime_type'] ) ) { $args['mime_type'] = $filesystem->get_mime_type( $args['file_path'] ); } if ( ( empty( $args['width'] ) || empty( $args['height'] ) ) && strpos( $args['mime_type'], 'image/' ) === 0 ) { $file_size = $filesystem->get_image_size( $args['file_path'] ); $args['width'] = $file_size ? $file_size['width'] : 0; $args['height'] = $file_size ? $file_size['height'] : 0; } if ( empty( $args['hash'] ) ) { $args['hash'] = md5_file( $args['file_path'] ); } if ( empty( $args['original_size'] ) ) { $args['original_size'] = (int) $filesystem->size( $args['file_path'] ); } $files_db = Imagify_Files_DB::get_instance(); $primary_key = $files_db->get_primary_key(); unset( $args[ $primary_key ] ); return $files_db->insert( $args ); } /** * Delete a custom file. * * @since 1.7 * @access public * @author Grégory Viguier * * @param array $args An array of arguments. * At least: 'file_id'. At best: 'file_id', 'file_path' (or 'path' for the placeholder), and 'backup_path'. */ public static function delete_file( $args = [] ) { $args = array_merge( [ 'file_id' => 0, 'file_path' => '', 'path' => '', 'backup_path' => '', 'process' => false, ], $args ); $filesystem = imagify_get_filesystem(); // Fill the blanks. if ( $args['process'] && $args['process'] instanceof \Imagify\Optimization\Process\ProcessInterface ) { $process = $args['process']; } else { $process = imagify_get_optimization_process( $args['file_id'], 'custom-folders' ); } if ( ! $process->is_valid() ) { // You fucked up! return; } if ( ! $args['file_path'] && $args['path'] ) { $args['file_path'] = Imagify_Files_Scan::remove_placeholder( $args['path'] ); } if ( ! $args['file_path'] && $args['file_id'] ) { $args['file_path'] = $process->get_media()->get_fullsize_path(); } if ( ! $args['backup_path'] && $args['file_path'] ) { $args['backup_path'] = self::get_file_backup_path( $args['file_path'] ); } if ( ! $args['backup_path'] && $args['file_id'] ) { $args['backup_path'] = $process->get_media()->get_raw_backup_path(); } // Trigger a common hook. imagify_trigger_delete_media_hook( $process ); // The file. if ( $args['file_path'] && $filesystem->exists( $args['file_path'] ) ) { $filesystem->delete( $args['file_path'] ); } // The backup file. if ( $args['backup_path'] && $filesystem->exists( $args['backup_path'] ) ) { $filesystem->delete( $args['backup_path'] ); } // WebP. $mime_type = $filesystem->get_mime_type( $args['file_path'] ); $is_image = $mime_type && strpos( $mime_type, 'image/' ) === 0; $webp_path = $is_image ? imagify_path_to_webp( $args['file_path'] ) : false; if ( $webp_path && $filesystem->is_writable( $webp_path ) ) { $filesystem->delete( $webp_path ); } // In the database. $process->get_media()->delete_row(); } /** * Check if a file has been modified, and update the database accordingly. * * @since 1.7 * @access public * @author Grégory Viguier * * @param ProcessInterface $process A \Imagify\Optimization\Process\ProcessInterface object. * @param bool $is_folder_active Tell if the folder is active. * @return int|bool|object The file ID if modified. False if not modified. A WP_Error object if the entry has been removed from the database. * The entry is removed from the database if: * - The file doesn't exist anymore. * - Or if its folder is not active and: the file has been modified, or the file is not optimized by Imagify, or the file is orphan (its folder is not in the database anymore). */ public static function refresh_file( $process, $is_folder_active = null ) { global $wpdb; if ( ! $process->is_valid() ) { return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) ); } $filesystem = imagify_get_filesystem(); $media = $process->get_media(); $file_path = $media->get_fullsize_path(); $mime_type = $filesystem->get_mime_type( $file_path ); $is_image = $mime_type && strpos( $mime_type, 'image/' ) === 0; $webp_path = $is_image ? imagify_path_to_webp( $file_path ) : false; $has_webp = $webp_path && $filesystem->is_writable( $webp_path ); $modified = false; if ( ! $file_path || ! $filesystem->exists( $file_path ) ) { /** * The file doesn't exist anymore. */ // Delete the backup file. $process->delete_backup(); // Get the folder ID before removing the row. $folder_id = $media->get_row(); $folder_id = $folder_id['folder_id']; // Remove the entry from the database. $media->delete_row(); // Remove the corresponding folder if inactive and have no files left. self::remove_empty_inactive_folders( $folder_id ); // Delete the WebP version. if ( $has_webp ) { $filesystem->delete( $webp_path ); } return new WP_Error( 'no-file', __( 'The file was missing or its path could not be retrieved from the database. The entry has been deleted from the database.', 'imagify' ) ); } /** * The file still exists. */ $old_data = $media->get_row(); $new_data = []; // Folder ID. if ( $old_data['folder_id'] ) { $folder = wp_cache_get( 'custom_folder_' . $old_data['folder_id'], 'imagify' ); if ( false === $folder ) { // The folder is not in the cache. $folder = Imagify_Folders_DB::get_instance()->get( $old_data['folder_id'] ); $folder = $folder ? $folder : 0; } if ( ! $folder ) { // The folder is not in the database anymore. $old_data['folder_id'] = 0; $new_data['folder_id'] = 0; } } else { $folder = 0; } // Hash + modified. $current_hash = md5_file( $file_path ); if ( ! $old_data['hash'] ) { $new_data['modified'] = 0; } else { $new_data['modified'] = (int) ! hash_equals( $old_data['hash'], $current_hash ); } // The file is modified or is not optimized. if ( $new_data['modified'] || ! $process->get_data()->is_optimized() ) { if ( ! isset( $is_folder_active ) ) { $is_folder_active = $folder && $folder['active']; } // Its folder is not active: remove the entry from the database and delete the backup. if ( ! $is_folder_active ) { // Delete the backup file. $process->delete_backup(); // Remove the entry from the database. $media->delete_row(); // Remove the corresponding folder if inactive and have no files left. if ( $old_data['folder_id'] ) { self::remove_empty_inactive_folders( $old_data['folder_id'] ); } // Delete the WebP version. if ( $has_webp ) { $filesystem->delete( $webp_path ); } return new WP_Error( 'folder-not-active', __( 'The file has been modified or was not optimized: its folder not being selected in the settings, the entry has been deleted from the database.', 'imagify' ) ); } } $new_data['hash'] = $current_hash; // The file is modified. if ( $new_data['modified'] ) { // Delete all optimization data and update file data. $modified = true; $mime_type = ! empty( $old_data['mime_type'] ) ? $old_data['mime_type'] : $filesystem->get_mime_type( $file_path ); if ( $is_image ) { $size = $filesystem->get_image_size( $file_path ); // Delete the WebP version. if ( $has_webp ) { $filesystem->delete( $webp_path ); } } else { $size = false; } $new_data = array_merge( $new_data, [ 'file_date' => $filesystem->get_date( $file_path ), 'width' => $size ? $size['width'] : 0, 'height' => $size ? $size['height'] : 0, 'original_size' => $filesystem->size( $file_path ), 'optimized_size' => null, 'percent' => null, 'optimization_level' => null, 'status' => null, 'error' => null, 'data' => [], ] ); // Delete the backup of the previous file. $process->delete_backup(); } else { // Update file data to make sure nothing is missing. $backup_path = $media->get_backup_path(); $path = $backup_path ? $backup_path : $file_path; $mime_type = ! empty( $old_data['mime_type'] ) ? $old_data['mime_type'] : $filesystem->get_mime_type( $path ); $file_date = ! empty( $old_data['file_date'] ) && '0000-00-00 00:00:00' !== $old_data['file_date'] ? $old_data['file_date'] : $filesystem->get_date( $path ); if ( $is_image ) { $size = $filesystem->get_image_size( $path ); } else { $size = false; } $new_data = array_merge( $new_data, [ 'file_date' => $file_date, 'width' => $size ? $size['width'] : 0, 'height' => $size ? $size['height'] : 0, 'original_size' => $filesystem->size( $path ), ] ); // WebP. $webp_size = 'full' . $process::WEBP_SUFFIX; if ( $has_webp && empty( $old_data['data'][ $webp_size ]['success'] ) ) { $webp_file_size = $filesystem->size( $webp_path ); $old_data['data'][ $webp_size ] = [ 'success' => true, 'original_size' => $new_data['original_size'], 'optimized_size' => $webp_file_size, 'percent' => round( ( ( $new_data['original_size'] - $webp_file_size ) / $new_data['original_size'] ) * 100, 2 ), ]; } } // Save the new data. $old_data = array_intersect_key( $old_data, $new_data ); ksort( $old_data ); ksort( $new_data ); if ( $old_data !== $new_data ) { $media->update_row( $new_data ); } return $modified ? $media->get_id() : false; } /** ----------------------------------------------------------------------------------------- */ /** FOLDERS AND FILES ======================================================================= */ /** ----------------------------------------------------------------------------------------- */ /** * Get folders from the DB. * * @since 1.7 * @access public * @author Grégory Viguier * * @param array $args A list of arguments to tell more precisely what to fetch: * - bool $active True to fetch only "active" folders (checked in the settings). False to fetch only folders that are not "active". * @return array An array of arrays containing the following values: * - int $folder_id The folder ID. * - string $path The folder path, with placeholder. * - int $active 1 if the folder should be optimized. 0 otherwize. * - string $folder_path The real absolute folder path. * Example: * Array( * [7] => Array( * [folder_id] => 7 * [path] => {{ROOT}}/custom-path/ * [active] => 1 * [folder_path] => /absolute/path/to/custom-path/ * ) * [13] => Array( * [folder_id] => 13 * [path] => {{CONTENT}}/another-custom-path/ * [active] => 1 * [folder_path] => /absolute/path/to/wp-content/another-custom-path/ * ) * ) */ public static function get_folders( $args = array() ) { global $wpdb; $folders_db = Imagify_Folders_DB::get_instance(); $folders_table = $folders_db->get_table_name(); $primary_key = $folders_db->get_primary_key(); $where_active = ''; if ( isset( $args['active'] ) ) { if ( $args['active'] ) { $args['active'] = true; $where_active = 'WHERE active = 1'; } else { $args['active'] = false; $where_active = 'WHERE active = 0'; } } // Get the folders from the DB. $results = $wpdb->get_results( "SELECT * FROM $folders_table $where_active;", ARRAY_A ); // WPCS: unprepared SQL ok. if ( ! $results || ! is_array( $results ) ) { return array(); } // Cast results, add absolute paths. $folders = array(); foreach ( $results as $row_fields ) { // Cast the row. $row_fields = $folders_db->cast_row( $row_fields ); // Add the absolute path. $row_fields['folder_path'] = Imagify_Files_Scan::remove_placeholder( $row_fields['path'] ); // Add the row to the list. $folders[ $row_fields[ $primary_key ] ] = $row_fields; } return $folders; } /** * Get files belonging to the given folders. * Files are scanned from the folders, then: * - If a file doesn't exist in the DB, it is added (maybe, depending on arguments provided). * - If a file is in the DB, but with a wrong folder_id, it is fixed. * - If a file doesn't exist, it is removed from the database and its backup is deleted. * * @since 1.7 * @access public * @see Imagify_Custom_Folders::get_folders() * @author Grégory Viguier * * @param array $folders An array of arrays containing at least the keys 'folder_path' and 'active'. See Imagify_Custom_Folders::get_folders() for the format. * @param array $args A list of arguments to tell more precisely what to fetch: * - int $optimization_level If set with an integer, only files that needs to be optimized to this level will be returned (the status is also checked). * - bool $return_only_old_files True to return only files that have not been newly inserted. * - bool $add_inactive_folder_files When true: if a file is not in the database and its folder is not "active", it is added to the DB. Default false: new files are not added to the database if the folder is not active. * @return array A list of files in the following format: * Array( * [_2] => Array( * [file_id] => 2 * [folder_id] => 7 * [path] => {{ROOT}}/custom-path/image-1.jpg * [optimization_level] => null * [status] => null * [file_path] => /absolute/path/to/custom-path/image-1.jpg * ), * [_3] => Array( * [file_id] => 3 * [folder_id] => 7 * [path] => {{ROOT}}/custom-path/image-2.jpg * [optimization_level] => 2 * [status] => success * [file_path] => /absolute/path/to/custom-path/image-2.jpg * ), * [_6] => Array( * [file_id] => 6 * [folder_id] => 13 * [path] => {{CONTENT}}/another-custom-path/image-1.jpg * [optimization_level] => 0 * [status] => error * [file_path] => /absolute/path/to/wp-content/another-custom-path/image-1.jpg * ), * ) * The fields 'optimization_level' and 'status' are set only if the argument 'optimization_level' was set. */ public static function get_files_from_folders( $folders, $args = array() ) { global $wpdb; if ( ! $folders ) { return array(); } $filesystem = imagify_get_filesystem(); $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 ); $optimization = isset( $args['optimization_level'] ) && is_numeric( $args['optimization_level'] ); $no_new_files = ! empty( $args['return_only_old_files'] ); $add_inactive_folder_files = ! empty( $args['add_inactive_folder_files'] ); /** * Scan folders for files. $files_from_scan will be in the following format: * Array( * [7] => Array( * [/absolute/path/to/custom-path/image-1.jpg] => 0 * [/absolute/path/to/custom-path/image-2.jpg] => 1 * ) * [13] => Array( * [/absolute/path/to/wp-content/another-custom-path/image-1.jpg] => 0 * [/absolute/path/to/wp-content/another-custom-path/image-2.jpg] => 1 * [/absolute/path/to/wp-content/another-custom-path/image-3.jpg] => 2 * ) * ) */ $files_from_scan = array(); foreach ( $folders as $folder_id => $folder ) { $files_from_scan[ $folder_id ] = Imagify_Files_Scan::get_files_from_folder( $folder['folder_path'] ); if ( is_wp_error( $files_from_scan[ $folder_id ] ) ) { unset( $files_from_scan[ $folder_id ] ); } } $files_from_scan = array_map( 'array_flip', $files_from_scan ); /** * Get the files from DB. $files_from_db will be in the same format as the function output. */ $already_optimized = array(); $folder_ids = array_keys( $folders ); $files_from_db = array_fill_keys( $folder_ids, array() ); $folder_ids = Imagify_DB::prepare_values_list( $folder_ids ); $select_fields = "$files_key_esc, folder_id, path" . ( $optimization ? ', optimization_level, status' : '' ); if ( $optimization ) { $orderby = " CASE status WHEN 'already_optimized' THEN 3 WHEN 'error' THEN 2 ELSE 1 END ASC, $files_key_esc DESC"; } else { $orderby = "folder_id, $files_key_esc"; } $results = $wpdb->get_results( "SELECT $select_fields FROM $files_table WHERE folder_id IN ( $folder_ids ) ORDER BY $orderby;", ARRAY_A ); // WPCS: unprepared SQL ok. if ( $results ) { $wpdb->flush(); foreach ( $results as $i => $row_fields ) { // Cast the row. $row_fields = $files_db->cast_row( $row_fields ); // Add the absolute path. $row_fields['file_path'] = Imagify_Files_Scan::remove_placeholder( $row_fields['path'] ); // Remove the file from the scan. unset( $files_from_scan[ $row_fields['folder_id'] ][ $row_fields['file_path'] ] ); if ( $optimization ) { if ( 'error' !== $row_fields['status'] && $row_fields['optimization_level'] === $args['optimization_level'] ) { // Try the same level only if the status is an error. continue; } if ( 'already_optimized' === $row_fields['status'] && $row_fields['optimization_level'] >= $args['optimization_level'] ) { // If the image is already compressed, optimize only if the requested level is higher. continue; } if ( 'success' === $row_fields['status'] && $args['optimization_level'] !== $row_fields['optimization_level'] ) { $file_backup_path = self::get_file_backup_path( $row_fields['file_path'] ); if ( ! $file_backup_path || ! $filesystem->exists( $file_backup_path ) ) { // Don't try to re-optimize if there is no backup file. continue; } } } if ( ! $filesystem->exists( $row_fields['file_path'] ) ) { // If the file doesn't exist: remove all traces of it and bail out. self::delete_file( array( 'file_id' => $row_fields[ $files_key ], 'file_path' => $row_fields['file_path'], ) ); continue; } if ( $optimization && 'already_optimized' === $row_fields['status'] ) { $already_optimized[ '_' . $row_fields[ $files_key ] ] = 1; } // Add the row to the list. $files_from_db[ $row_fields['folder_id'] ][ '_' . $row_fields[ $files_key ] ] = $row_fields; } } unset( $results ); $files_from_scan = array_filter( $files_from_scan ); // Make sure files from the scan are not already in the DB with another folder (shouldn't be possible, but, you know...). if ( $files_from_scan ) { $folders_by_placeholder = array(); foreach ( $files_from_scan as $folder_id => $folder_files ) { foreach ( $folder_files as $file_path => $i ) { $placeholder = Imagify_Files_Scan::add_placeholder( $file_path ); $folders_by_placeholder[ $placeholder ] = $folder_id; $files_from_scan[ $folder_id ][ $file_path ] = $placeholder; } } $placeholders = Imagify_DB::prepare_values_list( array_keys( $folders_by_placeholder ) ); $select_fields = "$files_key_esc, folder_id, path" . ( $optimization ? ', optimization_level, status' : '' ); $results = $wpdb->get_results( "SELECT $select_fields FROM $files_table WHERE path IN ( $placeholders ) ORDER BY folder_id, $files_key_esc;", ARRAY_A ); // WPCS: unprepared SQL ok. if ( $results ) { // Damn... $wpdb->flush(); foreach ( $results as $i => $row_fields ) { // Cast the row. $row_fields = $files_db->cast_row( $row_fields ); $old_folder_id = $row_fields['folder_id']; // Add the absolute path. $row_fields['file_path'] = Imagify_Files_Scan::remove_placeholder( $row_fields['path'] ); // Set the new folder ID. $row_fields['folder_id'] = $folders_by_placeholder[ $row_fields['path'] ]; // Remove the file from everywhere. unset( $files_from_db[ $old_folder_id ][ '_' . $row_fields[ $files_key ] ], $files_from_scan[ $old_folder_id ][ $row_fields['file_path'] ], $files_from_scan[ $row_fields['folder_id'] ][ $row_fields['file_path'] ] ); if ( $optimization ) { if ( 'error' !== $row_fields['status'] && $row_fields['optimization_level'] === $args['optimization_level'] ) { // Try the same level only if the status is an error. continue; } if ( 'already_optimized' === $row_fields['status'] && $row_fields['optimization_level'] >= $args['optimization_level'] ) { // If the image is already compressed, optimize only if the requested level is higher. continue; } if ( 'success' === $row_fields['status'] && $args['optimization_level'] !== $row_fields['optimization_level'] ) { $file_backup_path = self::get_file_backup_path( $row_fields['file_path'] ); if ( ! $file_backup_path || ! $filesystem->exists( $file_backup_path ) ) { // Don't try to re-optimize if there is no backup file. continue; } } } if ( ! $filesystem->exists( $row_fields['file_path'] ) ) { // If the file doesn't exist: remove all traces of it and bail out. self::delete_file( array( 'file_id' => $row_fields[ $files_key ], 'file_path' => $row_fields['file_path'], ) ); continue; } // Set the correct folder ID in the DB. $success = $files_db->update( $row_fields[ $files_key ], array( 'folder_id' => $row_fields['folder_id'], ) ); if ( $success ) { if ( $optimization && 'already_optimized' === $row_fields['status'] ) { $already_optimized[ '_' . $row_fields[ $files_key ] ] = 1; } $files_from_db[ $row_fields['folder_id'] ][ '_' . $row_fields[ $files_key ] ] = $row_fields; } } } unset( $results, $folders_by_placeholder ); } $files_from_scan = array_filter( $files_from_scan ); // Insert the remaining files into the DB. if ( $files_from_scan ) { foreach ( $files_from_scan as $folder_id => $placeholders ) { // Don't add the file to the DB if its folder is not "active". if ( ! $add_inactive_folder_files && empty( $folders[ $folder_id ]['active'] ) ) { unset( $files_from_scan[ $folder_id ] ); continue; } foreach ( $placeholders as $file_path => $placeholder ) { $file_id = self::insert_file( array( 'folder_id' => $folder_id, 'path' => $placeholder, 'file_path' => $file_path, ) ); if ( $file_id && ! $no_new_files ) { $files_from_db[ $folder_id ][ '_' . $file_id ] = array( 'file_id' => $file_id, 'folder_id' => $folder_id, 'path' => $placeholder, 'optimization_level' => null, 'status' => null, 'file_path' => $file_path, ); } } unset( $files_from_scan[ $folder_id ] ); } } $files_from_db = array_filter( $files_from_db ); if ( ! $files_from_db ) { return array(); } $files_from_db = call_user_func_array( 'array_merge', array_values( $files_from_db ) ); if ( $already_optimized ) { // Put the files already optimized at the end of the list. $already_optimized = array_intersect_key( $files_from_db, $already_optimized ); $files_from_db = array_diff_key( $files_from_db, $already_optimized ); $files_from_db = array_merge( $files_from_db, $already_optimized ); } return $files_from_db; } /** * Check if files inside the given folders have been modified, and update the database accordingly. * * @since 1.7 * @access public * @author Grégory Viguier * * @param array $folders A list of folders. See Imagify_Custom_Folders::get_folders() for the format. */ public static function synchronize_files_from_folders( $folders ) { global $wpdb; /** * Get the files from DB, and from the folder. */ $files = self::get_files_from_folders( $folders, array( 'return_only_old_files' => true, ) ); if ( ! $files ) { // This folder doesn't have (new) images. return; } $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 ); $file_ids = wp_list_pluck( $files, $files_key ); $file_ids = Imagify_DB::prepare_values_list( $file_ids ); $results = $wpdb->get_results( "SELECT * FROM $files_table WHERE $files_key IN ( $file_ids ) ORDER BY $files_key_esc;", ARRAY_A ); // WPCS: unprepared SQL ok. if ( ! $results ) { // WAT?! return; } // Caching the folders will prevent unecessary SQL queries in Imagify_Custom_Folders::refresh_file(). foreach ( $folders as $folder_id => $folder ) { wp_cache_set( 'custom_folder_' . $folder_id, $folder, 'imagify' ); } // Finally, refresh the files data. foreach ( $results as $file ) { $file = $files_db->cast_row( $file ); $folder_id = $file['folder_id']; $process = imagify_get_optimization_process( $file, 'custom-folders' ); self::refresh_file( $process, $folders[ $folder_id ]['active'] ); } foreach ( $folders as $folder_id => $folder ) { wp_cache_delete( 'custom_folder_' . $folder_id, 'imagify' ); } } /** ----------------------------------------------------------------------------------------- */ /** WHEN SAVING SELECTED FOLDERS ============================================================ */ /** ----------------------------------------------------------------------------------------- */ /** * Dectivate all active folders. * * @since 1.7 * @access public * @author Grégory Viguier */ public static function deactivate_all_folders() { self::deactivate_not_selected_folders(); } /** * Dectivate folders that are not selected. * * @since 1.7 * @access public * @author Grégory Viguier * * @param array|object|string $selected_paths A list of "placeholdered" paths corresponding to the selected folders. */ public static function deactivate_not_selected_folders( $selected_paths = array() ) { global $wpdb; $folders_table = Imagify_Folders_DB::get_instance()->get_table_name(); if ( $selected_paths ) { if ( is_array( $selected_paths ) || is_object( $selected_paths ) ) { $selected_paths = Imagify_DB::prepare_values_list( $selected_paths ); } $selected_paths_clause = "AND path NOT IN ( $selected_paths )"; } else { $selected_paths_clause = ''; } // Remove the active status from the folders that are not selected. $wpdb->query( "UPDATE $folders_table SET active = 0 WHERE active != 0 $selected_paths_clause" ); // WPCS: unprepared SQL ok. } /** * Activate folders that are selected. * * @since 1.7 * @access public * @author Grégory Viguier * * @param array|object $selected_paths A list of "placeholdered" paths corresponding to the selected folders. * @return array An array of paths of folders that are not in the DB. */ public static function activate_selected_folders( $selected_paths ) { global $wpdb; if ( ! $selected_paths ) { return $selected_paths; } $folders_db = Imagify_Folders_DB::get_instance(); $folders_table = $folders_db->get_table_name(); $folders_key = $folders_db->get_primary_key(); $selected_paths = (array) $selected_paths; $selected_in = Imagify_DB::prepare_values_list( $selected_paths ); // Get folders that already are in the DB. $folders = $wpdb->get_results( "SELECT * FROM $folders_table WHERE path IN ( $selected_in );", ARRAY_A ); // WPCS: unprepared SQL ok. if ( ! $folders ) { return $selected_paths; } $selected_paths = array_flip( $selected_paths ); foreach ( $folders as $folder ) { $folder = $folders_db->cast_row( $folder ); if ( Imagify_Files_Scan::placeholder_path_exists( $folder['path'] ) ) { if ( ! $folder['active'] ) { // Add the active status only if not already set and if the folder exists. $folders_db->update( $folder[ $folders_key ], array( 'active' => 1, ) ); } } else { // Remove the active status if the folder does not exist. $folders_db->update( $folder[ $folders_key ], array( 'active' => 0, ) ); } // Remove the path from the selected list, so the remaining will be created. unset( $selected_paths[ $folder['path'] ] ); } // Paths of folders that are not in the DB. return array_flip( $selected_paths ); } /** * Insert folders into the database. * * @since 1.7 * @access public * @author Grégory Viguier * * @param array $folders An array of "placeholdered" paths. * @return array An array of folder IDs. */ public static function insert_folders( $folders ) { if ( ! $folders ) { return array(); } $folder_ids = array(); $filesystem = imagify_get_filesystem(); $folders_db = Imagify_Folders_DB::get_instance(); foreach ( $folders as $placeholder ) { $full_path = Imagify_Files_Scan::remove_placeholder( $placeholder ); $full_path = realpath( $full_path ); if ( ! $full_path || ! $filesystem->is_readable( $full_path ) || ! $filesystem->is_dir( $full_path ) ) { continue; } if ( Imagify_Files_Scan::is_path_forbidden( trailingslashit( $full_path ) ) ) { continue; } $folder_ids[] = $folders_db->insert( array( 'path' => $placeholder, 'active' => 1, ) ); } return array_filter( $folder_ids ); } /** * Remove files that are in inactive folders and are not optimized. * * @since 1.7 * @access public * @author Grégory Viguier */ public static function remove_unoptimized_files_from_inactive_folders() { global $wpdb; $folders_db = Imagify_Folders_DB::get_instance(); $folders_key = $folders_db->get_primary_key(); $files_table = Imagify_Files_DB::get_instance()->get_table_name(); $folder_ids = $folders_db->get_active_folders_column( $folders_key ); if ( $folder_ids ) { $folder_ids = Imagify_DB::prepare_values_list( $folder_ids ); $wpdb->query( "DELETE FROM $files_table WHERE folder_id NOT IN ( $folder_ids ) AND ( status != 'success' OR status IS NULL )" ); // WPCS: unprepared SQL ok. } else { $wpdb->query( "DELETE FROM $files_table WHERE status != 'success' OR status IS NULL" ); // WPCS: unprepared SQL ok. } } /** * Reassign inactive files to active folders. * Example: * - Consider the file "/a/b/c/d/file.png". * - The folder "/a/b/c/", previously active, becomes inactive. * - The folder "/a/b/", previously inactive, becomes active. * - The file is reassigned to the folder "/a/b/". * * @since 1.7 * @access public * @author Grégory Viguier */ public static function reassign_inactive_files() { global $wpdb; $folders_db = Imagify_Folders_DB::get_instance(); $folders_table = $folders_db->get_table_name(); $folders_key = $folders_db->get_primary_key(); $folders_key_esc = esc_sql( $folders_key ); $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 ); // All active folders. $active_folders = $wpdb->get_results( "SELECT $folders_key_esc, path FROM $folders_table WHERE active = 1;", ARRAY_A ); // WPCS: unprepared SQL ok. if ( ! $active_folders ) { return; } $active_folder_ids = array(); $has_site_root = false; foreach ( $active_folders as $i => $active_folder ) { $active_folders[ $i ] = $folders_db->cast_row( $active_folder ); $active_folder_ids[] = $active_folders[ $i ][ $folders_key ]; if ( '{{ROOT}}/' === $active_folders[ $i ]['path'] ) { $has_site_root = true; break; } } // Files not in active folders. $active_folder_ids = Imagify_DB::prepare_values_list( $active_folder_ids ); $inactive_files = $wpdb->get_results( "SELECT $files_key_esc, path FROM $files_table WHERE folder_id NOT IN ( $active_folder_ids )", ARRAY_A ); // WPCS: unprepared SQL ok. if ( ! $inactive_files ) { return; } $filesystem = imagify_get_filesystem(); $file_ids_by_folder = array(); $active_folders = self::sort_folders( $active_folders, true ); foreach ( $inactive_files as $inactive_file ) { $inactive_file = $files_db->cast_row( $inactive_file ); $inactive_file['full_path'] = Imagify_Files_Scan::remove_placeholder( $inactive_file['path'] ); if ( $has_site_root ) { $inactive_file['dirname'] = $filesystem->dir_path( $inactive_file['full_path'] ); } foreach ( $active_folders as $active_folder ) { $folder_id = $active_folder[ $folders_key ]; if ( strpos( $inactive_file['full_path'], $active_folder['full_path'] ) !== 0 ) { // The file is not in this folder. continue; } if ( ! isset( $file_ids_by_folder[ $folder_id ] ) ) { $file_ids_by_folder[ $folder_id ] = array(); } if ( '{{ROOT}}/' === $active_folder['path'] ) { // For the site's root: only direct childs. if ( $inactive_file['dirname'] === $active_folder['full_path'] ) { // This file is in the site's root folder. $file_ids_by_folder[ $folder_id ][] = $inactive_file[ $files_key ]; } break; } // This file is not in the site's root, but still a grand-child of this folder. $file_ids_by_folder[ $folder_id ][] = $inactive_file[ $files_key ]; break; } } $file_ids_by_folder = array_filter( $file_ids_by_folder ); if ( ! $file_ids_by_folder ) { return; } // Set the new folder ID. foreach ( $file_ids_by_folder as $folder_id => $file_ids ) { $file_ids = Imagify_DB::prepare_values_list( $file_ids ); $wpdb->query( "UPDATE $files_table SET folder_id = $folder_id WHERE $files_key_esc IN ( $file_ids )" ); // WPCS: unprepared SQL ok. } } /** * Remove the given folders from the DB if they are inactive and have no files. * * @since 1.7 * @access public * @author Grégory Viguier * * @param array $folder_ids An array of folder IDs. * @return int Number of removed folders. */ public static function remove_empty_inactive_folders( $folder_ids = null ) { global $wpdb; $folders_db = Imagify_Folders_DB::get_instance(); $folders_table = $folders_db->get_table_name(); $folders_key = $folders_db->get_primary_key(); $folders_key_esc = esc_sql( $folders_key ); $files_table = Imagify_Files_DB::get_instance()->get_table_name(); $folder_ids = array_filter( (array) $folder_ids ); if ( $folder_ids ) { $folder_ids = $folders_db->cast_col( $folder_ids, $folders_key ); $folder_ids = Imagify_DB::prepare_values_list( $folder_ids ); $in_clause = "folders.$folders_key_esc IN ( $folder_ids )"; } else { $in_clause = '1=1'; } // Within the range of given folder IDs, filter the ones that are inactive and have no files. $results = $wpdb->get_col( // WPCS: unprepared SQL ok. " SELECT folders.$folders_key_esc FROM $folders_table AS folders LEFT JOIN $files_table AS files ON folders.$folders_key_esc = files.folder_id WHERE $in_clause AND folders.active != 1 AND files.folder_id IS NULL" ); if ( ! $results ) { return 0; } $results = $folders_db->cast_col( $results, $folders_key ); $results = Imagify_DB::prepare_values_list( $results ); // Remove inactive folders with no files. $wpdb->query( "DELETE FROM $folders_table WHERE $folders_key_esc IN ( $results )" ); // WPCS: unprepared SQL ok. return (int) $wpdb->rows_affected; } /** ----------------------------------------------------------------------------------------- */ /** TOOLS =================================================================================== */ /** ----------------------------------------------------------------------------------------- */ /** * Sort folders by full path. * The row "full_path" is added to each folder. * * @since 1.7 * @access public * @author Grégory Viguier * * @param array $folders An array of folders with at least a "path" row. * @param bool $reverse Reverse the order. * @return array */ public static function sort_folders( $folders, $reverse = false ) { if ( ! $folders ) { return array(); } $keyed_folders = array(); $keyed_paths = array(); foreach ( $folders as $folder ) { $folder = (array) $folder; $folder['full_path'] = Imagify_Files_Scan::remove_placeholder( $folder['path'] ); $keyed_folders[ $folder['path'] ] = $folder; $keyed_paths[ $folder['path'] ] = $folder['full_path']; } natcasesort( $keyed_paths ); if ( $reverse ) { $keyed_paths = array_reverse( $keyed_paths, true ); } $keyed_folders = array_merge( $keyed_paths, $keyed_folders ); return array_values( $keyed_folders ); } /** * Remove sub-paths: if 'a/b/' and 'a/b/c/' are in the array, we keep only the "parent" 'a/b/'. * * @since 1.7 * @access public * @author Grégory Viguier * * @param array $placeholders A list of "placeholdered" paths. * @return array */ public static function remove_sub_paths( $placeholders ) { sort( $placeholders ); foreach ( $placeholders as $i => $placeholder_path ) { if ( '{{ROOT}}/' === $placeholder_path ) { continue; } if ( ! isset( $prev_path ) ) { $prev_path = strtolower( Imagify_Files_Scan::remove_placeholder( $placeholder_path ) ); continue; } $placeholder_path = strtolower( Imagify_Files_Scan::remove_placeholder( $placeholder_path ) ); if ( strpos( $placeholder_path, $prev_path ) === 0 ) { unset( $placeholders[ $i ] ); } else { $prev_path = $placeholder_path; } } return $placeholders; } }