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,20 @@
|
||||
<?php declare( strict_types=1 );
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Optimizer\Hash;
|
||||
|
||||
final class Background_Processor {
|
||||
|
||||
/**
|
||||
* Try to return the request early so this can be processed in
|
||||
* a background thread.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function try_finish(): void {
|
||||
if ( function_exists( 'fastcgi_finish_request' ) ) {
|
||||
fastcgi_finish_request();
|
||||
} elseif ( function_exists( 'litespeed_finish_request' ) ) {
|
||||
litespeed_finish_request();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php declare( strict_types=1 );
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Optimizer\Hash;
|
||||
|
||||
/**
|
||||
* Builds a hash based off specific parts of an HTML document
|
||||
* that could affect which optimizations are in place.
|
||||
*
|
||||
* This is a good compromise for URLs that load random data, so we can
|
||||
* keep as many optimizations as possible, even if they may not apply
|
||||
* to some elements.
|
||||
*/
|
||||
final class Hash_Builder {
|
||||
|
||||
/**
|
||||
* Build a composite hash string with individual component hashes.
|
||||
*
|
||||
* @param string $html The full HTML to be returned to the browser.
|
||||
*
|
||||
* @return string The composite hash string.
|
||||
*/
|
||||
public function build_hash( string $html ): string {
|
||||
$components = [
|
||||
'stylesheet' => hash( 'md5', $this->extract_stylesheet_links( $html ) ),
|
||||
'inline' => hash( 'md5', $this->extract_inline_styles( $html ) ),
|
||||
'blocks' => hash( 'md5', $this->extract_block_structure( $html ) ),
|
||||
'structure' => hash( 'md5', $this->extract_structural_elements( $html ) ),
|
||||
];
|
||||
|
||||
return implode(
|
||||
'|',
|
||||
array_map(
|
||||
static fn( string $key, string $hash ): string => "$key:$hash",
|
||||
array_keys( $components ),
|
||||
$components
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two composite hash strings and return which components changed.
|
||||
*
|
||||
* @param string $old_hash The previous composite hash.
|
||||
* @param string $new_hash The new composite hash.
|
||||
*
|
||||
* @return string[] List of component names that changed.
|
||||
*/
|
||||
public function get_changed_components( string $old_hash, string $new_hash ): array {
|
||||
if ( $old_hash === $new_hash ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$old_parts = $this->parse_composite_hash( $old_hash );
|
||||
$new_parts = $this->parse_composite_hash( $new_hash );
|
||||
$changed = [];
|
||||
|
||||
foreach ( $new_parts as $key => $new_value ) {
|
||||
if ( ! isset( $old_parts[ $key ] ) || $old_parts[ $key ] !== $new_value ) {
|
||||
$changed[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return $changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a composite hash string into components.
|
||||
*
|
||||
* @param string $composite_hash The composite hash string.
|
||||
*
|
||||
* @return array Associative array of component => hash.
|
||||
*/
|
||||
private function parse_composite_hash( string $composite_hash ): array {
|
||||
$parts = explode( '|', $composite_hash );
|
||||
$result = [];
|
||||
|
||||
foreach ( $parts as $part ) {
|
||||
$split = explode( ':', $part, 2 );
|
||||
|
||||
if ( count( $split ) === 2 ) {
|
||||
$result[ $split[0] ] = $split[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract stylesheet and preload link tags.
|
||||
*
|
||||
* @param string $html The HTML content.
|
||||
*
|
||||
* @return string Concatenated link tags.
|
||||
*/
|
||||
private function extract_stylesheet_links( string $html ): string {
|
||||
preg_match_all( '~<link\b[^>]*\brel=["\'](?:stylesheet|preload)["\'][^>]*>~i', $html, $matches );
|
||||
|
||||
return implode( '', $matches[0] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract inline style tags.
|
||||
*
|
||||
* @param string $html The HTML content.
|
||||
*
|
||||
* @return string Concatenated style tags.
|
||||
*/
|
||||
private function extract_inline_styles( string $html ): string {
|
||||
preg_match_all( '~<style\b[^>]*>.*?</style>~is', $html, $matches );
|
||||
|
||||
return implode( '', $matches[0] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract block structures.
|
||||
*
|
||||
* @param string $html The HTML content.
|
||||
*
|
||||
* @return string Pipe-separated block comments.
|
||||
*/
|
||||
private function extract_block_structure( string $html ): string {
|
||||
$matches = [];
|
||||
|
||||
preg_match_all( '~<!-- wp:[^>]*-->~i', $html, $matches );
|
||||
|
||||
return implode( '|', $matches[0] ?? [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract structural element counts.
|
||||
*
|
||||
* @param string $html The HTML content.
|
||||
*
|
||||
* @return string Pipe-separated element counts.
|
||||
*/
|
||||
private function extract_structural_elements( string $html ): string {
|
||||
$matches = [];
|
||||
|
||||
preg_match_all( '~<(div|section|article|header|footer|main)[^>]*>~i', $html, $matches );
|
||||
|
||||
$counts = array_count_values( array_map( 'strtolower', $matches[1] ?? [] ) );
|
||||
|
||||
return implode(
|
||||
'|',
|
||||
array_map(
|
||||
static fn( string $element ): string => "$element:" . ( $counts[ $element ] ?? 0 ),
|
||||
[ 'div', 'section', 'article', 'header', 'footer', 'main' ]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
<?php declare( strict_types=1 );
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Optimizer\Hash;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Enums\Viewport;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Path\Path_Factory;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Request\Request;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Skip_Rules\Rule_Collection;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Store\Contracts\Store;
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerInterface;
|
||||
use KadenceWP\KadenceBlocks\StellarWP\SuperGlobals\SuperGlobals as SG;
|
||||
use KadenceWP\KadenceBlocks\Traits\Viewport_Trait;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Handles output‑buffer hashing during the WordPress shutdown phase to detect
|
||||
* whether the rendered HTML for the current request has changed since the last
|
||||
* optimization pass and invalidates the current optimization data if it's outdated.
|
||||
*/
|
||||
final class Hash_Handler {
|
||||
|
||||
use Viewport_Trait;
|
||||
|
||||
/**
|
||||
* Captures the final HTML before output buffering is
|
||||
* flushed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $html = '';
|
||||
private Hash_Builder $hasher;
|
||||
private Store $store;
|
||||
private Rule_Collection $rules;
|
||||
private Background_Processor $background_processor;
|
||||
private Hash_Store $hash_store;
|
||||
private Path_Factory $path_factory;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* @param Hash_Builder $hasher
|
||||
* @param Store $store
|
||||
* @param Rule_Collection $rules
|
||||
* @param Background_Processor $background_processor
|
||||
* @param Hash_Store $hash_store
|
||||
* @param Path_Factory $path_factory
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(
|
||||
Hash_Builder $hasher,
|
||||
Store $store,
|
||||
Rule_Collection $rules,
|
||||
Background_Processor $background_processor,
|
||||
Hash_Store $hash_store,
|
||||
Path_Factory $path_factory,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->hasher = $hasher;
|
||||
$this->store = $store;
|
||||
$this->rules = $rules;
|
||||
$this->background_processor = $background_processor;
|
||||
$this->hash_store = $hash_store;
|
||||
$this->path_factory = $path_factory;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin buffering the request.
|
||||
*
|
||||
* @action template_redirect
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function start_buffering(): void {
|
||||
ob_start( [ $this, 'end_buffering' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage the current HTML hash state for the request.
|
||||
*
|
||||
* Compares the freshly generated hash of the final output buffer
|
||||
* against the previously stored hash. If the hash differs, this indicates
|
||||
* that the page content has changed since the last optimization pass.
|
||||
*
|
||||
* This method is intended to be called AS LATE AS POSSIBLE in the `shutdown` phase, after
|
||||
* output buffering has captured the full HTML content.
|
||||
*
|
||||
* If running under fastcgi or litespeed, the request will be returned immediately and the logic
|
||||
* processed in the background.
|
||||
*
|
||||
* @action shutdown
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function check_hash(): void {
|
||||
if ( SG::get_get_var( Request::QUERY_OPTIMIZER_PREVIEW ) ) {
|
||||
$this->logger->debug( 'Skipping hash check due to optimizer preview query variable.' );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->html ) {
|
||||
if ( ! is_admin() ) {
|
||||
$this->logger->debug(
|
||||
'Bypassing Optimizer: No HTML found to check',
|
||||
[
|
||||
'request_uri' => SG::get_server_var( 'REQUEST_URI', 'unknown' ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't check hashes on 404 requests.
|
||||
if ( is_404() ) {
|
||||
$this->logger->debug(
|
||||
'Bypassing Optimizer: 404 not found',
|
||||
[
|
||||
'request_uri' => SG::get_server_var( 'REQUEST_URI', 'unknown' ),
|
||||
]
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Return request early, if possible, so we can process this in the background.
|
||||
$this->background_processor->try_finish();
|
||||
|
||||
$viewport = Viewport::current( $this->is_mobile() );
|
||||
|
||||
// Process skip rules and bail if required.
|
||||
foreach ( $this->rules->all() as $rule ) {
|
||||
if ( $rule->should_skip() ) {
|
||||
$this->logger->debug(
|
||||
'Bypassing Optimizer: skip rule',
|
||||
[
|
||||
'rule' => get_class( $rule ),
|
||||
'viewport' => $viewport->value(),
|
||||
]
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$path = $this->path_factory->make();
|
||||
} catch ( InvalidArgumentException $e ) {
|
||||
$this->logger->error(
|
||||
'Hash handler unable to determine the path',
|
||||
[
|
||||
'viewport' => $viewport->value(),
|
||||
'exception' => $e,
|
||||
]
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate a hash based on the final HTML markup, note this differs for mobile vs desktop.
|
||||
$hash = $this->hasher->build_hash( $this->html );
|
||||
$stored_hash = $this->hash_store->get( $path, $viewport );
|
||||
|
||||
// The frontend script will pass this get var as a hash set request.
|
||||
$maybe_set_hash = (bool) SG::get_get_var( 'kadence_set_optimizer_hash', false );
|
||||
|
||||
if ( $maybe_set_hash ) {
|
||||
$this->logger->debug(
|
||||
'Attempting to store new optimizer hash',
|
||||
[
|
||||
'path' => $path->path(),
|
||||
'viewport' => $viewport->value(),
|
||||
'hash' => $hash,
|
||||
]
|
||||
);
|
||||
|
||||
// Store the hash for the current viewport.
|
||||
$this->hash_store->set( $path, $viewport, $hash );
|
||||
|
||||
do_action( 'kadence_blocks_optimizer_set_hash', $hash, $path, $viewport );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// The HTML has been changed somehow, invalidate the optimization data, so that the next request will not have the data.
|
||||
if ( $stored_hash && $stored_hash !== $hash ) {
|
||||
$changes = $this->hasher->get_changed_components( $stored_hash, $hash );
|
||||
|
||||
$this->logger->debug(
|
||||
'Optimizer hash does not match...deleting',
|
||||
[
|
||||
'path' => $path->path(),
|
||||
'viewport' => $viewport->value(),
|
||||
'changes' => $changes,
|
||||
]
|
||||
);
|
||||
|
||||
// Delete the viewport hash.
|
||||
$this->hash_store->delete( $path, $viewport );
|
||||
|
||||
$analysis = $this->store->get( $path );
|
||||
|
||||
// This page isn't optimized or the data is already invalidated.
|
||||
if ( ! $analysis ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set data to stale to force invalidate data for all viewports.
|
||||
try {
|
||||
$this->logger->debug(
|
||||
'Marking optimizer path as stale to remove on next page load',
|
||||
[
|
||||
'path' => $path->path(),
|
||||
]
|
||||
);
|
||||
|
||||
$analysis->isStale = true;
|
||||
$this->store->set( $path, $analysis );
|
||||
|
||||
do_action( 'kadence_blocks_optimizer_data_invalidated', $analysis->isStale, $path );
|
||||
} catch ( Throwable $e ) {
|
||||
// Our DateTimeImmutable should never throw an exception, but this is here just in case.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->html = '';
|
||||
|
||||
do_action( 'kadence_blocks_hash_check_complete' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML, which will differ as the request proceeds.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function html(): string {
|
||||
return $this->html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that receives the buffer's contents. Captures the full page HTML
|
||||
* in our property for use when we manage the hash state down the line.
|
||||
*
|
||||
* @param string $html The final HTML.
|
||||
* @param int $phase The bitmask of the PHP_OUTPUT_HANDLER_* constants.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function end_buffering( string $html, int $phase ): string {
|
||||
if ( $phase & PHP_OUTPUT_HANDLER_FINAL ) {
|
||||
$this->html = $html;
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php declare( strict_types=1 );
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Optimizer\Hash;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Database\Viewport_Query;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Enums\Viewport;
|
||||
use KadenceWP\KadenceBlocks\Optimizer\Path\Path;
|
||||
|
||||
/**
|
||||
* Stores a hash based on the rendered HTML for each viewport
|
||||
* in order to invalidate optimizer data if it changes.
|
||||
*/
|
||||
final class Hash_Store {
|
||||
|
||||
private Viewport_Query $q;
|
||||
|
||||
public function __construct( Viewport_Query $query ) {
|
||||
$this->q = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash for a viewport.
|
||||
*
|
||||
* @param Path $path The current path object.
|
||||
* @param Viewport $vp The viewport enum.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get( Path $path, Viewport $vp ): ?string {
|
||||
$hash = $this->q->get_var(
|
||||
$this->q->qb()->select( 'html_hash' )
|
||||
->where( 'path_hash', $path->hash() )
|
||||
->where( 'viewport', $vp->value() )
|
||||
->getSQL()
|
||||
);
|
||||
|
||||
return $hash ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hash for a viewport.
|
||||
*
|
||||
* @param Path $path The current path object.
|
||||
* @param Viewport $vp The viewport enum.
|
||||
* @param string $hash The hash to store.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set( Path $path, Viewport $vp, string $hash ): bool {
|
||||
return (bool) $this->q->qb()->upsert(
|
||||
[
|
||||
'path_hash' => $path->hash(),
|
||||
'viewport' => $vp->value(),
|
||||
'html_hash' => $hash,
|
||||
],
|
||||
[
|
||||
'path_hash',
|
||||
'viewport',
|
||||
],
|
||||
[
|
||||
'%s',
|
||||
'%s',
|
||||
'%s',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a viewport hash.
|
||||
*
|
||||
* @param Path $path The current path object.
|
||||
* @param Viewport $vp The viewport enum.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete( Path $path, Viewport $vp ): bool {
|
||||
return (bool) $this->q->qb()
|
||||
->where( 'path_hash', $path->hash() )
|
||||
->where( 'viewport', $vp->value() )
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php declare( strict_types=1 );
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Optimizer\Hash;
|
||||
|
||||
use KadenceWP\KadenceBlocks\StellarWP\ProphecyMonorepo\Container\Contracts\Provider as Provider_Contract;
|
||||
|
||||
final class Provider extends Provider_Contract {
|
||||
|
||||
public function register(): void {
|
||||
$this->register_hash_store();
|
||||
$this->register_hash_handling();
|
||||
}
|
||||
|
||||
private function register_hash_store(): void {
|
||||
$this->container->singleton( Hash_Store::class, Hash_Store::class );
|
||||
}
|
||||
|
||||
private function register_hash_handling(): void {
|
||||
$this->container->singleton( Hash_Builder::class, Hash_Builder::class );
|
||||
$this->container->singleton( Hash_Handler::class, Hash_Handler::class );
|
||||
|
||||
// Don't hook in the optimizer under these scenarios for optimal performance.
|
||||
if (
|
||||
! defined( 'WP_UNINSTALL_PLUGIN' ) &&
|
||||
! wp_installing() &&
|
||||
! wp_doing_ajax() &&
|
||||
! wp_is_json_request() &&
|
||||
! wp_doing_cron()
|
||||
) {
|
||||
add_action(
|
||||
'template_redirect',
|
||||
$this->container->callback( Hash_Handler::class, 'start_buffering' ),
|
||||
1,
|
||||
0
|
||||
);
|
||||
|
||||
add_action(
|
||||
'shutdown',
|
||||
$this->container->callback( Hash_Handler::class, 'check_hash' ),
|
||||
PHP_INT_MAX - 1,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user