Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes #7248 Preconnect to external domains - Data insertion part #7310

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
use WP_Rocket\Dependencies\BerlinDB\Database\Query;

class AbstractQueries extends Query {
/**
* Cleanup interval.
*
* @var int
*/
public $cleanup_interval;

/**
* Table status.
*
Expand Down Expand Up @@ -163,4 +170,17 @@ public function get_rows_by_url( string $url ) {

return $query;
}

/**
* Set cleanup interval
*
* @param int $interval The interval duration, usually default to 1.
*
* @return object
*/
public function set_cleanup_interval( int $interval ): object {
$this->cleanup_interval = $interval;

return $this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,13 @@ interface QueriesInterface {
* @return bool|int Returns a boolean or integer value. The exact return value depends on the implementation.
*/
public function delete_old_rows();

/**
* Sets the cleanup interval.
*
* This method sets the interval at which the cleanup process should run.
*
* @param int $interval The interval in seconds.
*/
public function set_cleanup_interval( int $interval );
}
144 changes: 144 additions & 0 deletions inc/Engine/Media/PreconnectExternalDomains/AJAX/Controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);

namespace WP_Rocket\Engine\Media\PreconnectExternalDomains\AJAX;

use WP_Rocket\Engine\Common\Context\ContextInterface;
use WP_Rocket\Engine\Common\PerformanceHints\AJAX\AJAXControllerTrait;
use WP_Rocket\Engine\Common\PerformanceHints\AJAX\ControllerInterface;
use WP_Rocket\Engine\Media\PreconnectExternalDomains\Database\Queries\PreconnectExternalDomains as PreconnectQuery;

class Controller implements ControllerInterface {
use AJAXControllerTrait;

/**
* Preconnect external domain instance
*
* @var PreconnectQuery
*/
private $query;

/**
* PreconnectExternalDomains Context.
*
* @var ContextInterface
*/
protected $context;

/**
* Constructor
*
* @param PreconnectQuery $query Preconnect External Domains Query instance.
* @param ContextInterface $context Context interface.
*/
public function __construct( PreconnectQuery $query, ContextInterface $context ) {
$this->query = $query;
$this->context = $context;
}

/**
* Add Preconnect external domains data to the database
*
* @return array
*/
public function add_data(): array {
check_ajax_referer( 'rocket_beacon', 'rocket_beacon_nonce' );
$payload = [];

if ( ! $this->context->is_allowed() ) {
$payload['preconnect_external_domains'] = 'not allowed';

return $payload;
}

$url = isset( $_POST['url'] ) ? untrailingslashit( esc_url_raw( wp_unslash( $_POST['url'] ) ) ) : '';
$is_mobile = isset( $_POST['is_mobile'] ) ? filter_var( wp_unslash( $_POST['is_mobile'] ), FILTER_VALIDATE_BOOLEAN ) : false;
$results = isset( $_POST['results'] ) ? json_decode( wp_unslash( $_POST['results'] ) ) : (object) [ 'domains' => [] ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$domains = $results->domains ?? [];

$preconnect_domains = [];

/**
* Filters the maximum number of preconnect external domains being saved into the database.
*
* @param int $max_number Maximum number to allow.
* @param string $url Current page url.
* @param string[]|array $hashes Current list of preconnect external domains.
*/
$max_preconnect_domains_number = wpm_apply_filters_typed( 'integer', 'rocket_preconnect_external_domains_number', 20, $url, $domains );
if ( 0 >= $max_preconnect_domains_number ) {
$max_preconnect_domains_number = 1;
}

foreach ( (array) $domains as $index => $domain ) {
$preconnect_domains[ $index ] = sanitize_url( wp_unslash( $domain ) );
--$max_preconnect_domains_number;
}

$row = $this->query->get_row( $url, $is_mobile );
if ( ! empty( $row ) ) {
$payload['preconnect_external_domains'] = 'item already in the database';

return $payload;
}

$status = isset( $_POST['status'] ) ? sanitize_text_field( wp_unslash( $_POST['status'] ) ) : '';
list( $status_code, $status_message ) = $this->get_status_code_message( $status );

$item = [
'url' => $url,
'is_mobile' => $is_mobile,
'status' => $status_code,
'error_message' => $status_message,
'domains' => wp_json_encode( $preconnect_domains ),
'created_at' => current_time( 'mysql', true ),
'last_accessed' => current_time( 'mysql', true ),
];

$result = $this->query->add_item( $item );

if ( ! $result ) {
$payload['preconnect_external_domains'] = 'error when adding the entry to the database';

return $payload;
}

$payload['preconnect_external_domains'] = $item;

return $payload;
}

/**
* Checks if there is existing data for the current URL and device type from the beacon script.
*
* This method is called via AJAX. It checks if there is existing preconnect domains data for the current URL and device type.
* If the data exists, it returns a JSON success response with true. If the data does not exist, it returns a JSON success response with false.
* If the context is not allowed, it returns a JSON error response with false.
*
* @return array
*/
public function check_data(): array {
$payload = [
'preconnect_external_domains' => false,
];

check_ajax_referer( 'rocket_beacon', 'rocket_beacon_nonce' );

if ( ! $this->context->is_allowed() ) {
$payload['preconnect_external_domains'] = true;

return $payload;
}

$is_mobile = isset( $_POST['is_mobile'] ) ? filter_var( wp_unslash( $_POST['is_mobile'] ), FILTER_VALIDATE_BOOLEAN ) : false;
$url = isset( $_POST['url'] ) ? untrailingslashit( esc_url_raw( wp_unslash( $_POST['url'] ) ) ) : '';

$row = $this->query->get_row( $url, $is_mobile );

if ( ! empty( $row ) ) {
$payload['preconnect_external_domains'] = true;
}

return $payload;
}
}
46 changes: 46 additions & 0 deletions inc/Engine/Media/PreconnectExternalDomains/Context/Context.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);

namespace WP_Rocket\Engine\Media\PreconnectExternalDomains\Context;

use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Engine\Common\Context\ContextInterface;

class Context implements ContextInterface {

/**
* Options instance
*
* @var Options_Data
*/
private $options;


/**
* Constructor
*
* @param Options_Data $options Options instance.
*/
public function __construct( Options_Data $options ) {
$this->options = $options;
}

/**
* Determine if the action is allowed.
*
* @param array $data Data to pass to the context.
* @return bool
*/
public function is_allowed( array $data = [] ): bool {
if ( $this->options->get( 'wp_rocket_no_licence', 0 ) ) {
return false;
}

/**
* Filters to manage above the fold optimization
*
* @param bool $allow True to allow, false otherwise.
*/
return wpm_apply_filters_typed( 'boolean', 'rocket_preconnect_external_domains_optimization', true );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);

namespace WP_Rocket\Engine\Media\PreconnectExternalDomains\Database\Queries;

use WP_Rocket\Engine\Common\PerformanceHints\Database\Queries\AbstractQueries;
use WP_Rocket\Engine\Common\PerformanceHints\Database\Queries\QueriesInterface;
use WP_Rocket\Engine\Media\PreconnectExternalDomains\Database\Schema\PreconnectExternalDomains as PreconnectExternalDomainsSchema;
use WP_Rocket\Engine\Media\PreconnectExternalDomains\Database\Row\PreconnectExternalDomains as PreconnectExternalDomainsRow;

class PreconnectExternalDomains extends AbstractQueries implements QueriesInterface {

/**
* Name of the database table to query.
*
* @var string
*/
protected $table_name = 'wpr_preconnect_external_domains';

/**
* String used to alias the database table in MySQL statement.
*
* Keep this short, but descriptive. I.E. "tr" for term relationships.
*
* This is used to avoid collisions with JOINs.
*
* @var string
*/
protected $table_alias = 'wpr_pre';

/**
* Name of class used to setup the database schema.
*
* @var string
*/
protected $table_schema = PreconnectExternalDomainsSchema::class;

/** Item ******************************************************************/

/**
* Name for a single item.
*
* Use underscores between words. I.E. "term_relationship"
*
* This is used to automatically generate action hooks.
*
* @var string
*/
protected $item_name = 'preconnect_external_domains';

/**
* Plural version for a group of items.
*
* Use underscores between words. I.E. "term_relationships"
*
* This is used to automatically generate action hooks.
*
* @var string
*/
protected $item_name_plural = 'preconnect_external_domains';

/**
* Name of class used to turn IDs into first-class objects.
*
* This is used when looping through return values to guarantee their shape.
*
* @var mixed
*/
protected $item_shape = PreconnectExternalDomainsRow::class;

/**
* Delete all rows which were not accessed in the last month.
*
* @return bool|int
*/
public function delete_old_rows() {
// Get the database interface.
$db = $this->get_db();

// Early bailout if no database interface is available.
if ( ! $db ) {
return false;
}

$delete_interval = $this->cleanup_interval;

$prefixed_table_name = $db->prefix . $this->table_name;
$query = "DELETE FROM `$prefixed_table_name` WHERE status = 'failed' OR `last_accessed` <= date_sub(now(), interval $delete_interval month)";

return $db->query( $query );
}
}
Loading
Loading