Sid Gifari File Manager
🏠 Root
/
home
/
ailwtbdh
/
superstepsgroup.com
/
wp-content
/
plugins
/
all-in-one-seo-pack
/
app
/
Common
/
Utils
/
Editing: Cache.php
<?php namespace AIOSEO\Plugin\Common\Utils; // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Handles our cache. * * @since 4.1.5 */ class Cache { /** * Our cache table. * * @since 4.1.5 * * @var string */ private $table = 'aioseo_cache'; /** * Our cached cache. * * @since 4.1.5 * * @var array */ private static $cache = []; /** * The action for the scheduled cache prune. * * @since 4.9.4.2 * * @var string */ private $pruneAction = 'aioseo_cache_prune'; /** * The action for the scheduled old cache clean. * * @since 4.9.4.2 * * @var string */ private $optionCacheCleanAction = 'aioseo_old_cache_clean'; /** * Prefix for this cache. * * @since 4.1.5 * * @var string */ protected $prefix = ''; /** * Whether to use transients as fallback. * * @since 4.9.4.2 * * @var bool|null */ private $useTransientFallback = null; /** * Class constructor. * * @since 4.7.7.1 */ public function __construct() { add_action( 'init', [ $this, 'checkIfTableExists' ] ); // This needs to run on init because the DB. // class gets instantiated along with the cache class. } /** * Checks if the cache table exists and creates it if it doesn't. * Also registers and schedules cache maintenance actions. * * @since 4.7.7.1 * @version 4.9.4.2 Absorb prune logic from CachePrune class. * * @return void */ public function checkIfTableExists() { if ( ! aioseo()->core->db->tableExists( $this->table ) ) { aioseo()->preUpdates->createCacheTable(); } add_action( $this->pruneAction, [ $this, 'prune' ] ); add_action( $this->optionCacheCleanAction, [ $this, 'optionCacheClean' ] ); if ( aioseo()->actionScheduler->isScheduled( $this->pruneAction ) ) { return; } aioseo()->actionScheduler->scheduleRecurrent( $this->pruneAction, 0, DAY_IN_SECONDS ); } /** * Returns the cache value for a key if it exists and is not expired. * * @since 4.1.5 * * @param string $key The cache key name. Use a '%' for a like query. * @param bool|array $allowedClasses Deprecated. No longer used since migrating from serialize to JSON. * @return mixed The value or null if the cache does not exist. */ public function get( $key, $allowedClasses = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable // In dev mode, bypass Action Scheduler cache records so idle/lock timeouts don't block execution. if ( 0 === strpos( $key, 'as_' ) && aioseo()->helpers->isDev() ) { return null; } $key = $this->prepareKey( $key ); // Check if we're supposed to do a LIKE get. $isLikeGet = preg_match( '/%/', (string) $key ); // Check static cache first (only for non-LIKE queries). if ( ! $isLikeGet && isset( self::$cache[ $key ] ) ) { return self::$cache[ $key ]; } // Check if we should use transients. if ( ! $this->isCacheTableAvailable() ) { if ( $isLikeGet ) { // Use custom query for LIKE patterns. return $this->getTransientLike( $key ); } $value = $this->getTransient( $key ); if ( null !== $value ) { self::$cache[ $key ] = $value; } return $value; } $result = aioseo()->core->db ->start( $this->table ) ->select( '`key`, `value`, `is_object`' ) ->whereRaw( '( `expiration` IS NULL OR `expiration` > \'' . aioseo()->helpers->timeToMysql( time() ) . '\' )' ); if ( $isLikeGet ) { $result->whereLike( 'key', $key, true ); } else { $key = esc_sql( $key ); $result->where( 'key', $key ); } $result->output( ARRAY_A )->run(); // If we have nothing in the cache let's return a hard null. $values = $result->nullSet() ? null : $result->result(); // If we have something let's normalize it. if ( $values ) { foreach ( $values as &$value ) { // Use is_object flag to determine decode type: if 0 (false) decode to array, if 1 (true) decode to object. $value['value'] = json_decode( $value['value'], empty( $value['is_object'] ) ); } // Return only the single cache value. if ( ! $isLikeGet ) { $values = $values[0]['value']; } } // Return values without a static cache. // This is here because clearing the like cache is not simple. if ( $isLikeGet ) { return $values; } self::$cache[ $key ] = $values; return self::$cache[ $key ]; } /** * Updates the given cache or creates it if it doesn't exist. * * @since 4.1.5 * * @param string $key The cache key name. * @param mixed $value The value. * @param int $expiration The expiration time in seconds. Defaults to 24 hours. 0 to no expiration. * @return void */ public function update( $key, $value, $expiration = DAY_IN_SECONDS ) { $key = $this->prepareKey( $key ); // If the value is null we'll convert it and give it a shorter expiration. if ( null === $value ) { $value = false; $expiration = 10 * MINUTE_IN_SECONDS; } // Check if we should use transients. if ( ! $this->isCacheTableAvailable() ) { $this->updateTransient( $key, $value, $expiration ); $this->updateStatic( $key, $value ); return; } $isObject = is_object( $value ); $jsonValue = wp_json_encode( $value ); $expiration = 0 < $expiration ? aioseo()->helpers->timeToMysql( time() + $expiration ) : null; // Handle JSON encoding errors. if ( false === $jsonValue && JSON_ERROR_NONE !== json_last_error() ) { if ( aioseo()->helpers->isDev() ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( 'AIOSEO Cache: JSON encode failed for key "' . $key . '" - ' . json_last_error_msg() ); } return; } aioseo()->core->db->insert( $this->table ) ->set( [ 'key' => $this->prepareKey( $key ), 'value' => $jsonValue, 'is_object' => $isObject, 'expiration' => $expiration, 'created' => aioseo()->helpers->timeToMysql( time() ), 'updated' => aioseo()->helpers->timeToMysql( time() ) ] ) ->onDuplicate( [ 'value' => $jsonValue, 'is_object' => $isObject, 'expiration' => $expiration, 'updated' => aioseo()->helpers->timeToMysql( time() ) ] ) ->run(); $this->updateStatic( $key, $value ); } /** * Deletes the given cache key. * * @since 4.1.5 * * @param string $key The cache key. * @return void */ public function delete( $key ) { $key = $this->prepareKey( $key ); // Check if we should use transients. if ( ! $this->isCacheTableAvailable() ) { $this->deleteTransient( $key ); $this->clearStatic( $key ); return; } aioseo()->core->db->delete( $this->table ) ->where( 'key', $key ) ->run(); $this->clearStatic( $key ); } /** * Prepares the key before using the cache. * * @since 4.1.5 * * @param string $key The key to prepare. * @return string The prepared key. */ private function prepareKey( $key ) { $key = trim( (string) $key ); $key = $this->prefix && 0 !== strpos( $key, $this->prefix ) ? $this->prefix . $key : $key; if ( aioseo()->helpers->isDev() && 80 < mb_strlen( $key, 'UTF-8' ) ) { throw new \Exception( 'You are using a cache key that is too large, shorten your key and try again: [' . esc_html( $key ) . ']' ); } return $key; } /** * Clears all of our cache. * * @since 4.1.5 * * @return void */ public function clear() { if ( $this->prefix ) { $this->clearPrefix( '' ); return; } // Check if we should use transients. if ( ! $this->isCacheTableAvailable() ) { // Delete all AIOSEO cache transients. $this->deleteAllTransients(); $this->clearStatic(); return; } // Try to acquire the lock. if ( ! aioseo()->core->db->acquireLock( 'aioseo_cache_clear_lock', 0 ) ) { // If we couldn't acquire the lock, exit early without doing anything. // This means another process is already clearing the cache. return; } // If we find the activation redirect, we'll need to reset it after clearing. $activationRedirect = $this->get( 'activation_redirect' ); // Create a temporary table with the same structure. $table = aioseo()->core->db->prefix . $this->table; $newTable = aioseo()->core->db->prefix . $this->table . '_new'; $oldTable = aioseo()->core->db->prefix . $this->table . '_old'; try { // Drop the temp table if it exists from a previous failed attempt. if ( false === aioseo()->core->db->execute( "DROP TABLE IF EXISTS {$newTable}" ) ) { throw new \Exception( 'Failed to drop temporary table' ); } // Create the new empty table with the same structure. if ( false === aioseo()->core->db->execute( "CREATE TABLE {$newTable} LIKE {$table}" ) ) { throw new \Exception( 'Failed to create temporary table' ); } // Rename tables (atomic operation in MySQL). if ( false === aioseo()->core->db->execute( "RENAME TABLE {$table} TO {$oldTable}, {$newTable} TO {$table}" ) ) { throw new \Exception( 'Failed to rename tables' ); } // Drop the old table. if ( false === aioseo()->core->db->execute( "DROP TABLE {$oldTable}" ) ) { throw new \Exception( 'Failed to drop old table' ); } } catch ( \Exception $e ) { // If something fails, ensure we clean up any temporary tables. aioseo()->core->db->execute( "DROP TABLE IF EXISTS {$newTable}" ); aioseo()->core->db->execute( "DROP TABLE IF EXISTS {$oldTable}" ); // Truncate table to clear the cache. aioseo()->core->db->truncate( $this->table )->run(); } $this->clearStatic(); if ( $activationRedirect ) { $this->update( 'activation_redirect', $activationRedirect, 30 ); } } /** * Clears all of our cache under a certain prefix. * * @since 4.1.5 * * @param string $prefix A prefix to clear or empty to clear everything. * @return void */ public function clearPrefix( $prefix ) { $prefix = $this->prepareKey( $prefix ); // Check if we should use transients. if ( ! $this->isCacheTableAvailable() ) { // Delete transients by prefix. $this->deleteTransientsByPrefix( $prefix ); $this->clearStaticPrefix( $prefix ); return; } aioseo()->core->db->delete( $this->table ) ->whereLike( 'key', $prefix . '%', true ) ->run(); $this->clearStaticPrefix( $prefix ); } /** * Clears all of our static in-memory cache of a prefix. * * @since 4.1.5 * * @param string $prefix A prefix to clear. * @return void */ private function clearStaticPrefix( $prefix ) { $prefix = $this->prepareKey( $prefix ); foreach ( array_keys( self::$cache ) as $key ) { if ( 0 === strpos( $key, $prefix ) ) { unset( self::$cache[ $key ] ); } } } /** * Clears all of our static in-memory cache. * * @since 4.1.5 * * @param string $key A key to clear. * @return void */ private function clearStatic( $key = null ) { if ( empty( $key ) ) { self::$cache = []; return; } unset( self::$cache[ $this->prepareKey( $key ) ] ); } /** * Clears all of our static in-memory cache or the cache for a single given key. * * @since 4.7.1 * * @param string $key A key to clear (optional). * @param string $value A value to update (optional). * @return void */ private function updateStatic( $key = null, $value = null ) { if ( empty( $key ) ) { $this->clearStatic( $key ); return; } self::$cache[ $this->prepareKey( $key ) ] = $value; } /** * Prunes expired cache rows from the cache table. * * @since 4.9.4.2 * * @return void */ public function prune() { aioseo()->core->db->delete( $this->getTableName() ) ->whereRaw( '( `expiration` IS NOT NULL AND expiration <= \'' . aioseo()->helpers->timeToMysql( time() ) . '\' )' ) ->run(); } /** * Cleans legacy cache entries from the wp_options table. * Processes in batches and self-schedules until all entries are removed. * * @since 4.9.4.2 * * @return void */ public function optionCacheClean() { $optionCache = aioseo()->core->db->delete( aioseo()->core->db->db->options, true ) ->whereLike( 'option_name', '_aioseo_cache_%', true ) ->limit( 10000 ) ->run(); // Schedule a new run if we're not done cleaning. if ( 0 !== $optionCache->db->rows_affected ) { aioseo()->actionScheduler->scheduleSingle( $this->optionCacheCleanAction, MINUTE_IN_SECONDS, [], true ); } } /** * Returns the action name for the old cache clean. * * @since 4.9.4.2 * * @return string */ public function getOptionCacheCleanAction() { return $this->optionCacheCleanAction; } /** * Returns the cache table name. * * @since 4.1.5 * * @return string */ public function getTableName() { return $this->table; } /** * Checks if the cache table is available for use. * * @since 4.9.4.2 * * @return bool True if table exists and is accessible. */ private function isCacheTableAvailable() { if ( null !== $this->useTransientFallback ) { return ! $this->useTransientFallback; } // Check transient first to avoid a DB query on every request. if ( get_transient( 'aioseo_cache_table_exists' ) ) { $this->useTransientFallback = false; return true; } // Transient not set — check the DB directly (avoids circular dependency with Database::tableExists). global $wpdb; $tableName = $wpdb->prefix . $this->table; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $tableExists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $tableName ) ); if ( ! $tableExists ) { $this->useTransientFallback = true; return false; } set_transient( 'aioseo_cache_table_exists', 1, WEEK_IN_SECONDS ); $this->useTransientFallback = false; return true; } /** * Gets the transient name for a cache key. * * @since 4.9.4.2 * * @param string $key The cache key. * @return string The transient name. */ private function getTransientName( $key ) { // Store the original key to maintain compatibility with LIKE queries. return 'aioseo_cache_' . $key; } /** * Gets a value from transients. * * @since 4.9.4.2 * * @param string $key The cache key. * @return mixed The cached value or null. */ private function getTransient( $key ) { $transientName = $this->getTransientName( $key ); $data = get_transient( $transientName ); if ( false === $data ) { return null; } // Decode the wrapper structure. $wrapper = json_decode( $data, true ); if ( ! is_array( $wrapper ) || ! isset( $wrapper['value'] ) ) { return null; } // Decode JSON value using is_object flag (matching database cache behavior). // If is_object is 1 (true), decode to object; if 0 (false), decode to array. $decoded = json_decode( $wrapper['value'], empty( $wrapper['is_object'] ) ); return $decoded; } /** * Gets multiple transients using a LIKE query. * * @since 4.9.4.2 * * @param string $pattern The pattern to match (with % wildcards). * @return mixed Array of matching cache entries or null. */ private function getTransientLike( $pattern ) { global $wpdb; $transientPattern = $this->getTransientName( $pattern ); // Query for non-expired transients matching the pattern. $results = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "SELECT REPLACE(option_name, '_transient_', '') as `key`, option_value as `value` FROM {$wpdb->options} WHERE option_name LIKE %s AND option_name NOT LIKE %s AND ( NOT EXISTS ( SELECT 1 FROM {$wpdb->options} timeout WHERE timeout.option_name = CONCAT('_transient_timeout_', REPLACE(option_name, '_transient_', '')) AND CAST(timeout.option_value AS UNSIGNED) > 0 AND CAST(timeout.option_value AS UNSIGNED) < UNIX_TIMESTAMP() ) )", '_transient_' . implode( '%', array_map( [ $wpdb, 'esc_like' ], explode( '%', $transientPattern ) ) ), '_transient_timeout_%' ), ARRAY_A ); if ( empty( $results ) ) { return null; } // Decode JSON values with is_object flag. foreach ( $results as &$result ) { $result['key'] = str_replace( 'aioseo_cache_', '', $result['key'] ); // Decode the wrapper structure. $wrapper = json_decode( $result['value'], true ); if ( is_array( $wrapper ) && isset( $wrapper['value'] ) ) { // Decode JSON value using is_object flag (matching database cache behavior). // If is_object is 1 (true), decode to object; if 0 (false), decode to array. $result['value'] = json_decode( $wrapper['value'], empty( $wrapper['is_object'] ) ); } else { // Fallback for malformed data. $result['value'] = null; } } return $results; } /** * Updates a transient value. * * @since 4.9.4.2 * * @param string $key The cache key. * @param mixed $value The value to cache. * @param int $expiration Expiration in seconds. * @return void */ private function updateTransient( $key, $value, $expiration ) { $transientName = $this->getTransientName( $key ); // Detect if value is an object to match database cache behavior. $isObject = is_object( $value ); // Encode as JSON to match database cache behavior. $jsonValue = wp_json_encode( $value ); if ( false === $jsonValue && JSON_ERROR_NONE !== json_last_error() ) { if ( aioseo()->helpers->isDev() ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( 'AIOSEO Cache: JSON encode failed for key "' . $key . '" - ' . json_last_error_msg() ); } return; } // Wrap the value with metadata including is_object flag. $wrapper = [ 'value' => $jsonValue, 'is_object' => $isObject ]; $wrappedValue = wp_json_encode( $wrapper ); if ( false === $wrappedValue ) { if ( aioseo()->helpers->isDev() ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( 'AIOSEO Cache: JSON encode failed for wrapper on key "' . $key . '"' ); } return; } set_transient( $transientName, $wrappedValue, $expiration ); } /** * Deletes a transient. * * @since 4.9.4.2 * * @param string $key The cache key. * @return void */ private function deleteTransient( $key ) { $transientName = $this->getTransientName( $key ); delete_transient( $transientName ); } /** * Deletes all transients matching a prefix. * * @since 4.9.4.2 * * @param string $prefix The prefix to match. * @return void */ private function deleteTransientsByPrefix( $prefix ) { global $wpdb; $escapedPrefix = $wpdb->esc_like( $this->getTransientName( $prefix ) ); // Delete both the transient and its timeout. $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", '_transient_' . $escapedPrefix . '%', '_transient_timeout_' . $escapedPrefix . '%' ) ); } /** * Deletes all AIOSEO cache transients. * * @since 4.9.4.2 * * @return void */ private function deleteAllTransients() { global $wpdb; $prefix = 'aioseo_cache_%'; // Delete both transients and their timeouts. $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", '_transient_' . $wpdb->esc_like( $prefix ), '_transient_timeout_' . $wpdb->esc_like( $prefix ) ) ); } }
Save
Cancel