first commit
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Styles\CacheValidity;
|
||||
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
|
||||
class Cache_Validity_Item {
|
||||
const CACHE_KEY_PREFIX = 'elementor_atomic_cache_validity__';
|
||||
|
||||
private string $root;
|
||||
|
||||
public function __construct( string $root ) {
|
||||
$this->root = $root;
|
||||
}
|
||||
|
||||
public function get( array $keys ): ?array {
|
||||
return $this->wrap_exception( function() use ( $keys ) {
|
||||
$data = $this->get_stored_data();
|
||||
|
||||
$node = $this->get_node( $data, $keys );
|
||||
|
||||
if ( null === $node ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return is_bool( $node ) ? [ 'state' => $node ] : $node;
|
||||
} );
|
||||
}
|
||||
|
||||
public function validate( array $keys, $meta = null ) {
|
||||
return $this->wrap_exception( function() use ( $keys, $meta ) {
|
||||
$data = $this->get_stored_data();
|
||||
|
||||
if ( empty( $keys ) ) {
|
||||
$data['state'] = true;
|
||||
$data['meta'] = $meta;
|
||||
|
||||
$this->update_stored_data( $data );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->validate_nested_node( $data, $keys, $meta );
|
||||
} );
|
||||
}
|
||||
|
||||
public function invalidate( array $keys ) {
|
||||
return $this->wrap_exception( function() use ( $keys ) {
|
||||
if ( empty( $keys ) ) {
|
||||
$this->delete_stored_data();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->get_stored_data();
|
||||
|
||||
$this->invalidate_nested_node( $data, $keys );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data
|
||||
* @param array<string> $keys
|
||||
* @param mixed | null $meta
|
||||
*/
|
||||
private function validate_nested_node( array $data, array $keys, $meta = null ) {
|
||||
$data = $this->ensure_path( $data, $keys );
|
||||
|
||||
$last_key = array_pop( $keys );
|
||||
|
||||
// parent is guaranteed to be an array as we send the full $keys array to ensure_path
|
||||
$parent = &$this->get_node( $data, $keys );
|
||||
|
||||
$old_node = &$parent['children'][ $last_key ];
|
||||
|
||||
$has_children = is_array( $old_node ) && ! empty( $old_node['children'] );
|
||||
|
||||
if ( null === $meta && ! $has_children ) {
|
||||
$parent['children'][ $last_key ] = true;
|
||||
|
||||
$this->update_stored_data( $data );
|
||||
return;
|
||||
}
|
||||
|
||||
$new_node = [
|
||||
'state' => true,
|
||||
];
|
||||
|
||||
if ( $has_children ) {
|
||||
$new_node['children'] = $old_node['children'];
|
||||
}
|
||||
|
||||
if ( null !== $meta ) {
|
||||
$new_node['meta'] = $meta;
|
||||
}
|
||||
|
||||
$parent['children'][ $last_key ] = $new_node;
|
||||
|
||||
$this->update_stored_data( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data
|
||||
* @param array<string> $keys
|
||||
*/
|
||||
private function invalidate_nested_node( array $data, array $keys ) {
|
||||
$last_key = array_pop( $keys );
|
||||
$parent = &$this->get_node( $data, $keys );
|
||||
|
||||
if ( ! is_array( $parent ) || ! isset( $parent['children'][ $last_key ] ) ) {
|
||||
// node doesn't exist - no need to do anything
|
||||
return;
|
||||
}
|
||||
|
||||
if ( count( $parent['children'] ) === 1 ) {
|
||||
// if the invalidated node is the parent's o nly child - normalize the data
|
||||
$data = $this->get_normalized_data( $data, $keys, $last_key );
|
||||
|
||||
$this->update_stored_data( $data );
|
||||
return;
|
||||
}
|
||||
|
||||
unset( $parent['children'][ $last_key ] );
|
||||
|
||||
$this->update_stored_data( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} $data
|
||||
* @param array<string> $keys
|
||||
* @param string $last_key
|
||||
* @return array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>}
|
||||
*/
|
||||
private function get_normalized_data( array $data, array $keys, string $last_key ) {
|
||||
$obsolete_root_params = &$this->find_empty_parents_path_root( $data, $keys, $last_key );
|
||||
$parent = &$this->get_node( $data, $keys );
|
||||
|
||||
if ( $obsolete_root_params['node'] && $obsolete_root_params['key'] ) {
|
||||
unset( $obsolete_root_params['node']['children'][ $obsolete_root_params['key'] ] );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( $obsolete_root_params['node'] ) {
|
||||
unset( $data['children'] );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( $parent ) {
|
||||
unset( $parent['children'][ $last_key ] );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data
|
||||
* @param array<string> $keys
|
||||
* @return array{key: string | null, node: array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | null}
|
||||
*/
|
||||
private function &find_empty_parents_path_root( array &$data, array $keys ) {
|
||||
$root_node = [
|
||||
'key' => null,
|
||||
'node' => null,
|
||||
];
|
||||
|
||||
$current = &$data;
|
||||
$parent = &$current;
|
||||
|
||||
while ( ! empty( $keys ) ) {
|
||||
$key = array_shift( $keys );
|
||||
$parent = &$current;
|
||||
$current = &$current['children'][ $key ];
|
||||
|
||||
if ( $this->is_empty_parent( $current ) && empty( $root_node['node'] ) ) {
|
||||
$root_node = [
|
||||
'key' => $key,
|
||||
'node' => &$parent,
|
||||
];
|
||||
} elseif ( is_array( $current ) && ! $this->is_empty_parent( $current ) ) {
|
||||
$root_node = [
|
||||
'key' => null,
|
||||
'node' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $root_node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the stored tree, guaranteed to have a path representation based on $keys
|
||||
*
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data
|
||||
* @param array<string> $keys
|
||||
* @return array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>}
|
||||
*/
|
||||
private function ensure_path( array $data, array $keys ): ?array {
|
||||
$current = &$data;
|
||||
|
||||
while ( ! empty( $keys ) ) {
|
||||
$key = array_shift( $keys );
|
||||
|
||||
if ( is_bool( $current ) ) {
|
||||
$current = [ 'state' => $current ];
|
||||
}
|
||||
|
||||
if ( ! isset( $current['children'] ) ) {
|
||||
$current['children'] = [];
|
||||
}
|
||||
|
||||
if ( ! isset( $current['children'][ $key ] ) ) {
|
||||
$current['children'][ $key ] = [ 'state' => false ];
|
||||
}
|
||||
|
||||
$current = &$current['children'][ $key ];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean $data
|
||||
* @param array<string> $keys
|
||||
* @return array<array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} | boolean | null> | null | boolean
|
||||
*/
|
||||
private function &get_node( array &$data, array $keys ) {
|
||||
$current = &$data;
|
||||
|
||||
while ( ! empty( $keys ) ) {
|
||||
$key = array_shift( $keys );
|
||||
|
||||
if ( isset( $current['children'][ $key ] ) ) {
|
||||
$current = &$current['children'][ $key ];
|
||||
} else {
|
||||
$current = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
private function is_empty_parent( $data ): bool {
|
||||
if ( ! is_array( $data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ( ! isset( $data['children'] ) || 1 === count( $data['children'] ) ) && ! $data['state'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>}
|
||||
*/
|
||||
private function get_stored_data() {
|
||||
return get_option( self::CACHE_KEY_PREFIX . $this->root, [ 'state' => false ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{state: boolean, meta: array<string, mixed> | null, children: array<string, self>} $data
|
||||
*/
|
||||
private function update_stored_data( $data ) {
|
||||
// setting autoload with false to avoid unnecessary memory usage
|
||||
update_option( self::CACHE_KEY_PREFIX . $this->root, $data, false );
|
||||
}
|
||||
|
||||
private function delete_stored_data() {
|
||||
delete_option( self::CACHE_KEY_PREFIX . $this->root );
|
||||
}
|
||||
|
||||
private function wrap_exception( callable $callback ) {
|
||||
try {
|
||||
return $callback();
|
||||
} catch ( \Exception $e ) {
|
||||
$this->delete_stored_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user