feat: initial ACRIB WordPress deployment
- WordPress 6.9.4 (es_ES) with Kadence theme - Homepage: Hero, La Asociación, Pilares, Beneficios, Eventos, Miembros, Hazte Miembro, Contacto - Brand identity: #13294b navy, #a12932 burgundy, #c69c48 gold - Fonts: Raleway (headings) + Source Sans 3 (body) + Lato (UI) - Plugins: Kadence Blocks, Polylang, Contact Form 7 - Custom CSS with full brand styling and responsive layout - HTTPS enforced via wp-config.php proxy detection
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
<?php declare( strict_types=1 );
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Optimizer\Store;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Path\Path;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Response\WebsiteAnalysis;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Store\Contracts\Store;
|
||||
|
||||
/**
|
||||
* A Store Decorator that caches the results of the underlying concrete
|
||||
* store using the object cache.
|
||||
*/
|
||||
final class Cached_Store_Decorator implements Contracts\Store {
|
||||
|
||||
/**
|
||||
* The cache group.
|
||||
*/
|
||||
public const GROUP = 'kb_optimizer';
|
||||
|
||||
/**
|
||||
* The cache time to live in seconds.
|
||||
*/
|
||||
public const TTL = 43200;
|
||||
|
||||
/**
|
||||
* The underlying store concrete.
|
||||
*
|
||||
* @var Store
|
||||
*/
|
||||
private Store $store;
|
||||
|
||||
public function __construct( Store $store ) {
|
||||
$this->store = $store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a Path has optimization data.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has( Path $path ): bool {
|
||||
$cached = wp_cache_get( $this->get_has_key( $path ), self::GROUP, false, $found );
|
||||
|
||||
if ( $found ) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$has = $this->store->has( $path );
|
||||
|
||||
wp_cache_set( $this->get_has_key( $path ), $has, self::GROUP, self::TTL );
|
||||
|
||||
return $has;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only return the optimization if the analysis data is newer than the post's last modified
|
||||
* date.
|
||||
*
|
||||
* @param Path $path The path object associated with the stored data.
|
||||
*
|
||||
* @return WebsiteAnalysis|null
|
||||
*/
|
||||
public function get( Path $path ): ?WebsiteAnalysis {
|
||||
$cached = wp_cache_get( $this->get_key( $path ), self::GROUP );
|
||||
|
||||
if ( $cached instanceof WebsiteAnalysis ) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$analysis = $this->store->get( $path );
|
||||
|
||||
if ( $analysis ) {
|
||||
wp_cache_set( $this->get_key( $path ), $analysis, self::GROUP, self::TTL );
|
||||
wp_cache_set( $this->get_has_key( $path ), true, self::GROUP, self::TTL );
|
||||
}
|
||||
|
||||
return $analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the optimization data for a post.
|
||||
*
|
||||
* @param Path $path The path object associated with the stored data.
|
||||
* @param WebsiteAnalysis $analysis The website analysis data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set( Path $path, WebsiteAnalysis $analysis ): bool {
|
||||
$result = $this->store->set( $path, $analysis );
|
||||
|
||||
if ( $result ) {
|
||||
wp_cache_set( $this->get_key( $path ), $analysis, self::GROUP, self::TTL );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the optimization data for a post.
|
||||
*
|
||||
* @param Path $path The path object associated with the stored data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete( Path $path ): bool {
|
||||
wp_cache_delete( $this->get_key( $path ), self::GROUP );
|
||||
wp_cache_delete( $this->get_has_key( $path ), self::GROUP );
|
||||
|
||||
return $this->store->delete( $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a cache key.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_key( Path $path ): string {
|
||||
return sprintf( 'kb_optimizer_url_%s', $path->hash() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key for if optimization data exists.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_has_key( Path $path ): string {
|
||||
return sprintf( '%s_%s', $this->get_key( $path ), '_has' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php declare( strict_types=1 );
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Optimizer\Store\Contracts;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Path\Path;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Response\WebsiteAnalysis;
|
||||
|
||||
interface Store {
|
||||
|
||||
/**
|
||||
* Whether a Path has optimization data.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has( Path $path ): bool;
|
||||
|
||||
/**
|
||||
* Get the optimization data for a post.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return WebsiteAnalysis|null
|
||||
*/
|
||||
public function get( Path $path ): ?WebsiteAnalysis;
|
||||
|
||||
/**
|
||||
* Set the optimization data for a post.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
* @param WebsiteAnalysis $analysis The website analysis data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set( Path $path, WebsiteAnalysis $analysis ): bool;
|
||||
|
||||
/**
|
||||
* Delete the optimization data for a post.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete( Path $path ): bool;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php declare( strict_types=1 );
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Optimizer\Store;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Path\Path;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Response\WebsiteAnalysis;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Status\Status;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Store\Contracts\Store;
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Store decorator that blocks optimization operations on excluded posts.
|
||||
*
|
||||
* Excluded posts are user-controlled and should never have optimization data.
|
||||
* This decorator provides infrastructure-level protection.
|
||||
*
|
||||
* Responsibility: Prevent optimization of excluded posts.
|
||||
*/
|
||||
final class Excluded_Store_Decorator implements Store {
|
||||
|
||||
private Store $store;
|
||||
private Status $status;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
public function __construct(
|
||||
Store $store,
|
||||
Status $status,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->store = $store;
|
||||
$this->status = $status;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* An excluded post should return false for if it has data,
|
||||
* even if it does.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has( Path $path ): bool {
|
||||
$post_id = $path->post_id();
|
||||
|
||||
if ( $post_id && $this->status->is_excluded( $post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->store->has( $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the optimization data.
|
||||
*
|
||||
* @param Path $path The path object associated with the stored data.
|
||||
*
|
||||
* @return WebsiteAnalysis|null
|
||||
*/
|
||||
public function get( Path $path ): ?WebsiteAnalysis {
|
||||
return $this->store->get( $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Block optimization of excluded posts.
|
||||
*
|
||||
* If post is excluded, silently fail and log.
|
||||
* This provides defense-in-depth even if application layer checks fail.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
* @param WebsiteAnalysis $analysis The website analysis data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set( Path $path, WebsiteAnalysis $analysis ): bool {
|
||||
$post_id = $path->post_id();
|
||||
|
||||
if ( ! $post_id ) {
|
||||
$this->logger->warning(
|
||||
'Missing post_id in Path: Skipping exclusion check and storing optimization data without post_id',
|
||||
[ 'path' => $path->path() ]
|
||||
);
|
||||
|
||||
return $this->store->set( $path, $analysis );
|
||||
}
|
||||
|
||||
// Check if post is excluded.
|
||||
if ( $this->status->is_excluded( $post_id ) ) {
|
||||
// Silently block - excluded posts should never be optimized.
|
||||
$this->logger->debug(
|
||||
'Blocked optimization of excluded post',
|
||||
[
|
||||
'post_id' => $post_id,
|
||||
'path' => $path->path(),
|
||||
]
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->store->set( $path, $analysis );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow deletion even for excluded posts.
|
||||
*
|
||||
* Clearing optimization data is safe for excluded posts.
|
||||
* Status_Sync_Store_Decorator will preserve the excluded status meta.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete( Path $path ): bool {
|
||||
return $this->store->delete( $path );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php declare( strict_types=1 );
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Optimizer\Store;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Path\Path;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Response\WebsiteAnalysis;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Store\Contracts\Store;
|
||||
|
||||
/**
|
||||
* A Store Decorator that checks if the data is expired before
|
||||
* returning it.
|
||||
*/
|
||||
final class Expired_Store_Decorator implements Contracts\Store {
|
||||
|
||||
/**
|
||||
* The underlying store concrete.
|
||||
*
|
||||
* @var Store
|
||||
*/
|
||||
private Store $store;
|
||||
|
||||
public function __construct( Store $store ) {
|
||||
$this->store = $store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a Path has optimization data, stale or not.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has( Path $path ): bool {
|
||||
return $this->store->has( $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Only return the optimization if the analysis data is newer than the post's last modified date.
|
||||
*
|
||||
* @param Path $path The path object associated with the stored data.
|
||||
*
|
||||
* @return WebsiteAnalysis|null
|
||||
*/
|
||||
public function get( Path $path ): ?WebsiteAnalysis {
|
||||
$analysis = $this->store->get( $path );
|
||||
|
||||
if ( ! $analysis ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $analysis->isStale ? null : $analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the optimization data for a post.
|
||||
*
|
||||
* @param Path $path The path object associated with the stored data.
|
||||
* @param WebsiteAnalysis $analysis The website analysis data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set( Path $path, WebsiteAnalysis $analysis ): bool {
|
||||
return $this->store->set( $path, $analysis );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the optimization data for a post.
|
||||
*
|
||||
* @param Path $path The path object associated with the stored data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete( Path $path ): bool {
|
||||
return $this->store->delete( $path );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php declare( strict_types=1 );
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Optimizer\Store;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Store\Cached_Store_Decorator;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Store\Contracts\Store;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Store\Excluded_Store_Decorator;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Store\Expired_Store_Decorator;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Store\Status_Sync_Store_Decorator;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Store\Table_Store;
|
||||
use KadenceWP\KadenceBlocks\StellarWP\ProphecyMonorepo\Container\Contracts\Provider as Provider_Contract;
|
||||
|
||||
final class Provider extends Provider_Contract {
|
||||
|
||||
public function register(): void {
|
||||
// Decorator chain (bottom-up execution, each wraps the one below):
|
||||
// When calling Store methods, execution flows TOP to BOTTOM through decorators.
|
||||
//
|
||||
// Reads (get): Expired → Cache → Excluded → Status_Sync → Table
|
||||
// Writes (set): Expired → Cache → Excluded (blocks excluded) → Status_Sync (pure) → Table
|
||||
//
|
||||
// Critical ordering:
|
||||
// 1. Expired MUST be outermost to filter stale data even from cache.
|
||||
// 2. Excluded MUST be before Status_Sync so sync logic stays pure.
|
||||
$this->container->bindDecorators(
|
||||
Store::class,
|
||||
[
|
||||
Expired_Store_Decorator::class,
|
||||
Cached_Store_Decorator::class,
|
||||
Excluded_Store_Decorator::class,
|
||||
Status_Sync_Store_Decorator::class,
|
||||
Table_Store::class,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php declare( strict_types=1 );
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Optimizer\Store;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Path\Path;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Response\WebsiteAnalysis;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Status\Status;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Store\Contracts\Store;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Store decorator that automatically synchronizes Status post meta.
|
||||
*
|
||||
* Pure synchronization logic - no business rules or exclusion checks.
|
||||
* Exclusion is handled by Excluded_Store_Decorator upstream.
|
||||
*
|
||||
* Responsibility: Keep status meta in sync with optimization data.
|
||||
*
|
||||
* Benefits:
|
||||
* - Single source of truth for optimization state
|
||||
* - Impossible to forget to update status meta
|
||||
* - Automatic synchronization on all write operations
|
||||
* - No overhead on read operations
|
||||
*/
|
||||
final class Status_Sync_Store_Decorator implements Store {
|
||||
|
||||
private Store $store;
|
||||
private Status $status;
|
||||
|
||||
public function __construct( Store $store, Status $status ) {
|
||||
$this->store = $store;
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a Path has optimization data.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has( Path $path ): bool {
|
||||
return $this->store->has( $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the optimization data.
|
||||
*
|
||||
* @param Path $path The path object associated with the stored data.
|
||||
*
|
||||
* @return WebsiteAnalysis|null
|
||||
*/
|
||||
public function get( Path $path ): ?WebsiteAnalysis {
|
||||
return $this->store->get( $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set optimization data and sync status meta.
|
||||
*
|
||||
* Pure sync logic - no exclusion checks needed!
|
||||
* Excluded_Store_Decorator upstream prevents optimization of excluded posts.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
* @param WebsiteAnalysis $analysis The website analysis data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set( Path $path, WebsiteAnalysis $analysis ): bool {
|
||||
$result = $this->store->set( $path, $analysis );
|
||||
|
||||
if ( ! $result ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$post_id = $this->resolve_post_id( $path );
|
||||
|
||||
if ( $post_id ) {
|
||||
$this->status->sync_from_analysis( $post_id, $analysis );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete optimization data and sync status meta.
|
||||
*
|
||||
* Pure cleanup logic - no exclusion checks needed!
|
||||
*
|
||||
* Note: Status::delete() will preserve excluded status as a safety measure.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete( Path $path ): bool {
|
||||
$post_id = $this->resolve_post_id( $path );
|
||||
$result = $this->store->delete( $path );
|
||||
|
||||
if ( $post_id ) {
|
||||
$this->status->delete( $post_id );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve post ID from path.
|
||||
*
|
||||
* Priority order:
|
||||
* 1. Explicit post_id from Path object (most reliable)
|
||||
* 2. Current request context via get_post() and $post global
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
private function resolve_post_id( Path $path ): ?int {
|
||||
// Use explicit post_id if provided (most reliable).
|
||||
if ( $path->post_id() ) {
|
||||
return $path->post_id();
|
||||
}
|
||||
|
||||
// Try to get from current request context via get_post().
|
||||
$current_post = get_post();
|
||||
|
||||
if ( ! $current_post instanceof WP_Post ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $current_post->ID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php declare( strict_types=1 );
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Optimizer\Store;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Database\Optimizer_Query;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Path\Path;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Response\WebsiteAnalysis;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Optimizer table storage.
|
||||
*/
|
||||
final class Table_Store implements Contracts\Store {
|
||||
|
||||
private Optimizer_Query $q;
|
||||
|
||||
public function __construct( Optimizer_Query $query ) {
|
||||
$this->q = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a Path has optimization data.
|
||||
*
|
||||
* @param Path $path The path object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has( Path $path ): bool {
|
||||
return (bool) $this->q->get_var(
|
||||
$this->q->qb()->select( '1' )
|
||||
->where( 'path_hash', $path->hash() )
|
||||
->limit( 1 )->getSQL()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the optimization data for a path object.
|
||||
*
|
||||
* @param Path $path The path object associated with the stored data.
|
||||
*
|
||||
* @return WebsiteAnalysis|null
|
||||
*/
|
||||
public function get( Path $path ): ?WebsiteAnalysis {
|
||||
$json = $this->q->get_var(
|
||||
$this->q->qb()->select( 'analysis' )
|
||||
->where( 'path_hash', $path->hash() )
|
||||
->getSQL()
|
||||
);
|
||||
|
||||
if ( ! $json ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return WebsiteAnalysis::from( json_decode( $json, true ) );
|
||||
} catch ( Throwable $e ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the optimization data for a path object.
|
||||
*
|
||||
* @param Path $path The path object associated with the stored data.
|
||||
* @param WebsiteAnalysis $analysis The website analysis data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set( Path $path, WebsiteAnalysis $analysis ): bool {
|
||||
$json = wp_json_encode( $analysis->toArray(), JSON_UNESCAPED_SLASHES );
|
||||
|
||||
return false !== $this->q->qb()->upsert(
|
||||
[
|
||||
'path_hash' => $path->hash(),
|
||||
'path' => $path->path(),
|
||||
'analysis' => $json,
|
||||
],
|
||||
[
|
||||
'path_hash',
|
||||
],
|
||||
[
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the optimization data for a path object.
|
||||
*
|
||||
* @param Path $path The path object associated with the stored data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete( Path $path ): bool {
|
||||
return (bool) $this->q->qb()
|
||||
->where( 'path_hash', $path->hash() )
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user