Files
medicalalert-web-reloaded/wp/wp-content/plugins/imagify/inc/classes/class-imagify-custom-folders.php
2024-09-25 09:25:31 -04:00

1317 lines
43 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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;
}
}