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 #7242 3.19 - Preload Fonts - Data insertion part #7274

Open
wants to merge 8 commits into
base: feature/preload-fonts
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions inc/Engine/Media/PreloadFonts/AJAX/Controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php
declare(strict_types=1);

namespace WP_Rocket\Engine\Media\PreloadFonts\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\PreloadFonts\Database\Queries\PreloadFonts as PreloadFontsQuery;
use WP_Rocket\Engine\Optimization\UrlTrait;

class Controller implements ControllerInterface {
use AJAXControllerTrait;

/**
* PLFQuery instance
*
* @var PreloadFontsQuery
*/
private $query;

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

/**
* Constructor
*
* @param PreloadFontsQuery $query PLFQuery instance.
* @param ContextInterface $context Context interface.
*/
public function __construct( PreloadFontsQuery $query, ContextInterface $context ) {
$this->query = $query;
$this->context = $context;
}


/**
* Add Preload fonts 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['preload_fonts'] = '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) [ 'preload_fonts' => [] ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$fonts = $results->preload_fonts ?? [];

$preload_fonts = [];

/**
* Filters the maximum number of fonts 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 preload fonts.
*/
$max_preload_fonts_number = wpm_apply_filters_typed( 'integer', 'rocket_preload_fonts_number', 20, $url, $fonts );
if ( 0 >= $max_preload_fonts_number ) {
$max_preload_fonts_number = 1;
}

foreach ( (array) $fonts as $index => $font ) {
$preload_fonts[ $index ] = sanitize_text_field( wp_unslash( $font ) );
--$max_preload_fonts_number;
}

$row = $this->query->get_row( $url, $is_mobile );
if ( ! empty( $row ) ) {
$payload['preload_fonts'] = '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,
'fonts' => wp_json_encode( $preload_fonts ),
'created_at' => current_time( 'mysql', true ),
'last_accessed' => current_time( 'mysql', true ),
];

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

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

return $payload;
}

$payload['preload_fonts'] = $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 fonts 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 = [
'preload_fonts' => false,
];

check_ajax_referer( 'rocket_beacon', 'rocket_beacon_nonce' );

if ( ! $this->context->is_allowed() ) {
$payload['preload_fonts'] = 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['preload_fonts'] = true;
}

return $payload;
}

/**
* Preload Fonts Deletion interval filter.
*
* @return int
*/
public static function deletion_interval(): int {
/**
* Filters the interval (in months) to determine when a Preload Fonts entry is considered 'old'.
* Old PLF entries are eligible for deletion. By default, a PLF entry is considered old if it hasn't been accessed in the last month.
*
* @param int $delete_interval The interval in months after which a preload fonts entry is considered old. Default is 1 month.
*/
return wpm_apply_filters_typed( 'integer', 'rocket_preload_fonts_cleanup_interval', 1 );
}
}
44 changes: 44 additions & 0 deletions inc/Engine/Media/PreloadFonts/Context/Context.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);

namespace WP_Rocket\Engine\Media\PreloadFonts\Context;

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

class Context implements ContextInterface {
/**
* Instance of the Option_Data class.
*
* @var Options_Data
*/
private $options;

/**
* Constructor.
*
* @param Options_Data $options Instance of the Option_Data class.
*/
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' ) ) {
return false;
}

/**
* Filters to manage preload fonts
*
* @param bool $allow True to allow, false otherwise.
*/
return wpm_apply_filters_typed( 'boolean', 'rocket_preload_fonts', true );
}
}
96 changes: 96 additions & 0 deletions inc/Engine/Media/PreloadFonts/Database/Queries/PreloadFonts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

namespace WP_Rocket\Engine\Media\PreloadFonts\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\PreloadFonts\AJAX\Controller;
use WP_Rocket\Engine\Media\PreloadFonts\Database\Schema\PreloadFonts as PreloadFontsSchema;
use WP_Rocket\Engine\Media\PreloadFonts\Database\Rows\PreloadFonts as PreloadFontsRows;
class PreloadFonts extends AbstractQueries implements QueriesInterface {

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

/**
* 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_plf';

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

/**
* 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 = 'preload_fonts';

/**
* 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 = 'preload_fonts';

/**
* 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 = PreloadFontsRows::class;


/**
* Deletes old rows from the database.
*
* This method is used to delete rows from the database that have not been accessed in the last month.
*
* @return bool|int Returns a boolean or integer value. The exact return value depends on the implementation.
*/
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 = Controller::deletion_interval();

if ( $delete_interval <= 0 ) {
return false;
}

$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