Files
medicalalert-web-reloaded/wp/wp-content/plugins/imagify/inc/classes/class-imagify-db.php
Tony Volpe 8f4b5efda6 Merged in feature/MAW-855-import-code-into-aws (pull request #2)
code import from pantheon

* code import from pantheon
2023-12-04 23:08:14 +00:00

581 lines
18 KiB
PHP
Raw 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?' );
/**
* Imagify DB class. It reunites tools to work with the DB.
*
* @since 1.6.13
* @author Grégory Viguier
*/
class Imagify_DB {
/**
* Class version.
*
* @var string
*/
const VERSION = '1.0.1';
/**
* Some hosts limit the number of JOINs in SQL queries, but we need them.
*
* @since 1.6.13
* @access public
* @author Grégory Viguier
*/
public static function unlimit_joins() {
global $wpdb;
static $done = false;
if ( $done ) {
return;
}
$done = true;
$query = 'SET SQL_BIG_SELECTS=1';
/**
* Filter the SQL query allowing to remove the limit on JOINs.
*
* @since 1.6.13
* @author Grégory Viguier
*
* @param string|bool $query The query. False to prevent any query.
*/
$query = apply_filters( 'imagify_db_unlimit_joins_query', $query );
if ( $query && is_string( $query ) ) {
$wpdb->query( $query ); // WPCS: unprepared SQL ok.
}
}
/**
* Change an array of values into a comma separated list, ready to be used in a `IN ()` clause.
*
* @since 1.6.13
* @access public
* @author Grégory Viguier
*
* @param array $values An array of values.
* @return string A comma separated list of values.
*/
public static function prepare_values_list( $values ) {
$values = esc_sql( (array) $values );
$values = array_map( array( __CLASS__, 'quote_string' ), $values );
return implode( ',', $values );
}
/**
* Wrap a value in quotes, unless it's an integer.
*
* @since 1.6.13
* @access public
* @author Grégory Viguier
*
* @param int|string $value A value.
* @return int|string
*/
public static function quote_string( $value ) {
return is_numeric( $value ) ? $value : "'" . addcslashes( $value, "'" ) . "'";
}
/**
* First half of escaping for LIKE special characters % and _ before preparing for MySQL.
* Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security.
*
* Example Prepared Statement:
* $wild = '%';
* $find = 'only 43% of planets';
* $like = $wild . $wpdb->esc_like( $find ) . $wild;
* $sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like );
*
* Example Escape Chain:
* $sql = esc_sql( $wpdb->esc_like( $input ) );
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @param string $text The raw text to be escaped. The input typed by the user should have no extra or deleted slashes.
* @return string Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare() or real_escape next.
*/
public static function esc_like( $text ) {
global $wpdb;
if ( method_exists( $wpdb, 'esc_like' ) ) {
// Introduced in WP 4.0.0.
return $wpdb->esc_like( $text );
}
return addcslashes( $text, '_%\\' );
}
/**
* Get Imagify mime types, ready to be used in a `IN ()` clause.
*
* @since 1.6.13
* @since 1.9 Added $type parameter.
* @access public
* @author Grégory Viguier
*
* @param string $type One of 'image', 'not-image'. Any other value will return all mime types.
* @return string A comma separated list of mime types.
*/
public static function get_mime_types( $type = null ) {
static $mime_types = [];
if ( empty( $type ) ) {
$type = 'all';
}
if ( ! isset( $mime_types[ $type ] ) ) {
$mime_types[ $type ] = self::prepare_values_list( imagify_get_mime_types( $type ) );
}
return $mime_types[ $type ];
}
/**
* Get post statuses related to attachments, ready to be used in a `IN ()` clause.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @return string A comma separated list of post statuses.
*/
public static function get_post_statuses() {
static $statuses;
if ( ! isset( $statuses ) ) {
$statuses = self::prepare_values_list( imagify_get_post_statuses() );
}
return $statuses;
}
/**
* Get the SQL JOIN clause to use to get only attachments that have the required WP metadata.
* It returns an empty string if the database has no attachments without the required metadada.
* It also triggers Imagify_DB::unlimit_joins().
*
* @param string $id_field An ID field to match the metadata ID against in the JOIN clause.
* Default is the posts table `ID` field, using the `p` alias: `p.ID`.
* In case of "false" value or PEBKAC, fallback to the same field without alias.
* @param bool $matching Set to false to get a query to fetch metas NOT matching the file extensions.
* @param bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway.
* @param string $special_join_conditions Special conditions to apply on the join.
*
* @return string
* @author Grégory Viguier
*
* @since 1.7
* @access public
*/
public static function get_required_wp_metadata_join_clause( $id_field = 'p.ID', $matching = true, $test = true, $special_join_conditions = '' ) {
global $wpdb;
if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
return '';
}
self::unlimit_joins();
$clause = '';
if ( ! $id_field || ! is_string( $id_field ) ) {
$id_field = "$wpdb->posts.ID";
}
$join = $matching ? 'INNER' : 'LEFT';
$first = true;
foreach ( self::get_required_wp_metadata_aliases() as $meta_name => $alias ) {
if ( $first ) {
$first = false;
$clause .= "
$join JOIN $wpdb->postmeta AS $alias
ON ( $id_field = $alias.post_id AND $alias.meta_key = '$meta_name' $special_join_conditions )";
continue;
}
$clause .= "
$join JOIN $wpdb->postmeta AS $alias
ON ( $id_field = $alias.post_id AND $alias.meta_key = '$meta_name' )";
}
return $clause;
}
/**
* Get the SQL part to be used in a WHERE clause, to get only attachments that have (in)valid '_wp_attached_file' and '_wp_attachment_metadata' metadatas.
* It returns an empty string if the database has no attachments without the required metadada.
*
* @since 1.7
* @since 1.7.1.2 Use a single $arg parameter instead of 3. New $prepared parameter.
* @access public
* @author Grégory Viguier
*
* @param array $args {
* Optional. An array of arguments.
*
* string $aliases The aliases to use for the meta values.
* bool $matching Set to false to get a query to fetch invalid metas.
* bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway.
* bool $prepared Set to true if the query will be prepared with using $wpdb->prepare().
* }.
* @return string A query.
*/
public static function get_required_wp_metadata_where_clause( $args = array() ) {
static $query = array();
$args = imagify_merge_intersect( $args, array(
'aliases' => array(),
'matching' => true,
'test' => true,
'prepared' => false,
) );
list( $aliases, $matching, $test, $prepared ) = array_values( $args );
if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
return '';
}
if ( $aliases && is_string( $aliases ) ) {
$aliases = array(
'_wp_attached_file' => $aliases,
);
} elseif ( ! is_array( $aliases ) ) {
$aliases = array();
}
$aliases = imagify_merge_intersect( $aliases, self::get_required_wp_metadata_aliases() );
$key = implode( '|', $aliases ) . '|' . (int) $matching;
if ( isset( $query[ $key ] ) ) {
return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
}
unset( $args['prepared'] );
$alias_1 = $aliases['_wp_attached_file'];
$alias_2 = $aliases['_wp_attachment_metadata'];
$extensions = self::get_extensions_where_clause( $args );
if ( $matching ) {
$query[ $key ] = "AND $alias_1.meta_value NOT LIKE '%://%' AND $alias_1.meta_value NOT LIKE '_:\\\\\%' $extensions";
} else {
$query[ $key ] = "AND ( $alias_2.meta_value IS NULL OR $alias_1.meta_value IS NULL OR $alias_1.meta_value LIKE '%://%' OR $alias_1.meta_value LIKE '_:\\\\\%' $extensions )";
}
return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
}
/**
* Get the SQL part to be used in a WHERE clause, to get only attachments that have a valid file extensions.
* It returns an empty string if the database has no attachments without the required metadada.
*
* @since 1.7
* @since 1.7.1.2 Use a single $arg parameter instead of 3. New $prepared parameter.
* @access public
* @author Grégory Viguier
*
* @param array $args {
* Optional. An array of arguments.
*
* string $alias The alias to use for the meta value.
* bool $matching Set to false to get a query to fetch metas NOT matching the file extensions.
* bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway.
* bool $prepared Set to true if the query will be prepared with using $wpdb->prepare().
* }.
* @return string A query.
*/
public static function get_extensions_where_clause( $args = false ) {
static $extensions;
static $query = array();
$args = imagify_merge_intersect( $args, array(
'alias' => array(),
'matching' => true,
'test' => true,
'prepared' => false,
) );
list( $alias, $matching, $test, $prepared ) = array_values( $args );
if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
return '';
}
if ( ! isset( $extensions ) ) {
$extensions = array_keys( imagify_get_mime_types() );
$extensions = implode( '|', $extensions );
$extensions = explode( '|', $extensions );
$extensions = array_map(function ( $ex ) {
return strrev( $ex );
}, $extensions);
}
if ( ! $alias ) {
$alias = self::get_required_wp_metadata_aliases();
$alias = $alias['_wp_attached_file'];
}
$key = $alias . '|' . (int) $matching;
if ( isset( $query[ $key ] ) ) {
return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
}
$regex = '^' . implode( '\..*|^', $extensions ) . '\..*';
if ( $matching ) {
$query[ $key ] = "AND REVERSE (LOWER( $alias.meta_value )) REGEXP '$regex'";
} else {
$query[ $key ] = "AND REVERSE (LOWER( $alias.meta_value )) NOT REGEXP '$regex'";
}
return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
}
/**
* Get the aliases used for the metas in self::get_required_wp_metadata_join_clause(), self::get_required_wp_metadata_where_clause(), and self::get_extensions_where_clause().
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @return array An array with the meta name as key and its alias as value.
*/
public static function get_required_wp_metadata_aliases() {
return array(
'_wp_attached_file' => 'imrwpmt1',
'_wp_attachment_metadata' => 'imrwpmt2',
);
}
/**
* Combine two arrays with some specific keys.
* We use this function to combine the result of 2 SQL queries.
*
* @since 1.6.13
* @access public
* @author Grégory Viguier
*
* @param array $keys An array of keys.
* @param array $values An array of arrays like array( 'id' => id, 'value' => value ).
* @param int $keep_keys_order Set to true to return an array ordered like $keys instead of $values.
* @return array The combined arrays.
*/
public static function combine_query_results( $keys, $values, $keep_keys_order = false ) {
if ( ! $keys || ! $values ) {
return array();
}
$result = array();
$keys = array_flip( $keys );
foreach ( $values as $v ) {
if ( isset( $keys[ $v['id'] ] ) ) {
$result[ $v['id'] ] = $v['value'];
}
}
if ( $keep_keys_order ) {
$keys = array_intersect_key( $keys, $result );
return array_replace( $keys, $result );
}
return $result;
}
/**
* A helper to retrieve all values from one or several post metas, given a list of post IDs.
* The $wpdb cache is flushed to save memory.
*
* @since 1.6.13
* @access public
* @author Grégory Viguier
*
* @param array $metas An array of meta names like:
* array(
* 'key1' => 'meta_name_1',
* 'key2' => 'meta_name_2',
* 'key3' => 'meta_name_3',
* )
* If a key contains 'data', the results will be unserialized.
* @param array $ids An array of post IDs.
* @return array An array of arrays of results like:
* array(
* 'key1' => array( post_id_1 => 'result_1', post_id_2 => 'result_2', post_id_3 => 'result_3' ),
* 'key2' => array( post_id_1 => 'result_4', post_id_3 => 'result_5' ),
* 'key3' => array( post_id_1 => 'result_6', post_id_2 => 'result_7' ),
* )
*/
public static function get_metas( $metas, $ids ) {
global $wpdb;
if ( ! $ids ) {
return array_fill_keys( array_keys( $metas ), array() );
}
$sql_ids = implode( ',', $ids );
foreach ( $metas as $result_name => $meta_name ) {
$metas[ $result_name ] = $wpdb->get_results( // WPCS: unprepared SQL ok.
"SELECT pm.post_id as id, pm.meta_value as value
FROM $wpdb->postmeta as pm
WHERE pm.meta_key = '$meta_name'
AND pm.post_id IN ( $sql_ids )
ORDER BY pm.post_id DESC",
ARRAY_A
);
$wpdb->flush();
$metas[ $result_name ] = self::combine_query_results( $ids, $metas[ $result_name ], true );
if ( strpos( $result_name, 'data' ) !== false ) {
$metas[ $result_name ] = array_map( 'maybe_unserialize', $metas[ $result_name ] );
}
}
return $metas;
}
/**
* Create/Upgrade the table in the database.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @param string $table_name The (prefixed) table name.
* @param string $schema_query Query representing the table schema.
* @return bool True on success. False otherwise.
*/
public static function create_table( $table_name, $schema_query ) {
global $wpdb;
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$wpdb->hide_errors();
$schema_query = trim( $schema_query );
$charset_collate = $wpdb->get_charset_collate();
dbDelta( "CREATE TABLE $table_name ($schema_query) $charset_collate;" );
return empty( $wpdb->last_error ) && self::table_exists( $table_name );
}
/**
* Tell if the given table exists.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @param string $table_name Full name of the table (with DB prefix).
* @return bool
*/
public static function table_exists( $table_name ) {
global $wpdb;
$escaped_table = self::esc_like( $table_name );
$result = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $escaped_table ) );
return $result === $table_name;
}
/**
* Cache transients used for optimization process locks.
*
* @since 1.9
* @access public
* @author Grégory Viguier
*
* @param string $context The context.
* @param array $media_ids The media IDs.
*/
public static function cache_process_locks( $context, $media_ids ) {
global $wpdb;
if ( ! $context || ! $media_ids || wp_using_ext_object_cache() ) {
return;
}
// Sanitize the IDs.
$media_ids = array_filter( $media_ids );
$media_ids = array_unique( $media_ids );
if ( ! $media_ids ) {
return;
}
$context_instance = imagify_get_context( $context );
$context = $context_instance->get_name();
$process_class_name = imagify_get_optimization_process_class_name( $context );
$transient_name = sprintf( $process_class_name::LOCK_NAME, $context, '%' );
$is_network_wide = $context_instance->is_network_wide();
// Do 1 DB query per context (and cache results) before doing 1 get_transient() (2 DB queries) per media ID.
$prefix = $is_network_wide ? '_site_transient_' : '_transient_';
if ( $is_network_wide && is_multisite() ) {
$network_id = function_exists( 'get_current_network_id' ) ? get_current_network_id() : (int) $wpdb->siteid;
$cache_prefix = "$network_id:";
$notoptions_key = "$network_id:notoptions";
$cache_group = 'site-options';
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT meta_key as name, meta_value as value FROM $wpdb->sitemeta WHERE ( meta_key LIKE %s OR meta_key LIKE %s ) AND site_id = %d",
$prefix . $transient_name,
$prefix . 'timeout_' . $transient_name,
$network_id
),
OBJECT_K
); // WPCS: unprepared SQL ok.
} else {
$cache_prefix = '';
$notoptions_key = 'notoptions';
$cache_group = 'options';
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT option_name as name, option_value as value FROM $wpdb->options WHERE ( option_name LIKE %s OR option_name LIKE %s )",
$prefix . $transient_name,
$prefix . 'timeout_' . $transient_name
),
OBJECT_K
); // WPCS: unprepared SQL ok.
}
$not_exist = [];
foreach ( [ '', 'timeout_' ] as $maybe_timeout ) {
foreach ( $media_ids as $id ) {
$option_name = $prefix . $maybe_timeout . str_replace( '%', $id, $transient_name );
if ( isset( $results[ $option_name ] ) ) {
// Cache the value.
$value = $results[ $option_name ]->value;
$value = maybe_unserialize( $value );
wp_cache_set( "$cache_prefix$option_name", $value, $cache_group );
} else {
// No value.
$not_exist[ $option_name ] = true;
}
}
}
if ( ! $not_exist ) {
return;
}
// Cache the options that don't exist in the DB.
$notoptions = wp_cache_get( $notoptions_key, $cache_group );
$notoptions = is_array( $notoptions ) ? $notoptions : [];
$notoptions = array_merge( $notoptions, $not_exist );
wp_cache_set( $notoptions_key, $notoptions, $cache_group );
}
}