| Server IP : 103.88.176.108 / Your IP : 216.73.216.211 Web Server : Apache/2.4.41 (Ubuntu) System : Linux webserver 5.4.0-42-generic #46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020 x86_64 User : www-data ( 33) PHP Version : 7.4.3-4ubuntu2.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /var/www/html/wp-content/plugins/optimole-wp/inc/v2/PageProfiler/ |
Upload File : |
<?php
namespace OptimoleWP\PageProfiler;
use OptimoleWP\Preload\Links;
use Optml_Lazyload_Replacer;
/**
* Class Profile
*
* Handles page profiling functionality for Optimole, including storage and retrieval
* of above-fold image data for different device types.
*
* @package OptimoleWP\PageProfiler
*/
class Profile {
/**
* Placeholder used to identify where a profile ID should be inserted.
*/
const PLACEHOLDER = '###pageprofileid###';
/**
* Placeholder used to identify where a profile HMAC should be inserted.
*/
const PLACEHOLDER_HMAC = '###profilehmac###';
/**
* Placeholder used to identify where a profile time should be inserted.
*/
const PLACEHOLDER_TIME = '###profiletime###';
/**
* Placeholder used to indicate a missing profile ID.
*/
const PLACEHOLDER_MISSING = '###pageprofileidmissing###';
/**
* Placeholder used to identify where a profile request url should be inserted.
*/
const PLACEHOLDER_URL = '###pageprofileurl###';
/**
* Device type constant for mobile devices.
*/
const DEVICE_TYPE_MOBILE = 1;
/**
* Device type constant for desktop devices.
*
* @var int
*/
const DEVICE_TYPE_DESKTOP = 2;
/**
* Device type constant for global devices.
*
* @var int
*/
const DEVICE_TYPE_GLOBAL = -1;
/**
* Stores the current profile ID being processed.
*
* @var string|null
*/
private static $current_profile_id = null;
/**
* Stores the current profile data for all device types.
*
* @var array
*/
private static $current_profile_data = [];
/**
* The storage handler instance.
*
* @var Storage\Base
*/
private $storage;
/**
* Constructor.
*
* Initializes the storage handler based on the provided filter.
*
* @throws \Exception If an invalid storage class is provided.
*/
public function __construct() {
/**
* Filter the storage class.
* Allows to change the storage class to a different one, i.e a database storage class/file storage class etc.
*
* @param string $storage_class The storage class.
*
* @return string The storage class.
*/
$storage_class = apply_filters( 'optml_page_profiler_storage', wp_using_ext_object_cache() ? Storage\ObjectCache::class : Storage\Transients::class );
if ( OPTML_DEBUG ) {
do_action( 'optml_log', 'storage_class: ' . $storage_class );
}
if ( ! is_subclass_of( $storage_class, Storage\Base::class ) ) {
throw new \Exception( 'Invalid storage class' );
}
$this->storage = new $storage_class();
}
/**
* Generate a unique ID for the page profile
*
* @param string $content The content of the page.
*
* @return string New id
*/
public static function generate_id( string $content = '' ): string {
if ( OPTML_DEBUG ) {
do_action( 'optml_log', 'Generating page profile ID: ' . $content . ' ' . ( $_SERVER['REQUEST_URI'] ?? '' ) );
}
/**
* Filter the page profile ID. This can be altered to change to logic differently, i.e generate the id based on the url or query args or other parameters.
*
* @param string $id The page profile ID.
* @param string $content The content of the page.
*
* @return string New id
*/
return apply_filters( 'optml_page_profile_id', self::get_default_id( $content ), $content );
}
/**
* Generate a default ID for the page profile
*
* @param string $content The content of the page.
*
* @return string New id
*/
public static function get_default_id( string $content ): string {
global $post;
global $wp_query;
$page_id = serialize( get_theme_mods() ) .
get_queried_object_id() .
( get_queried_object() ? get_class( get_queried_object() ) : '' ) .
( $post->post_modified ?? '' ) .
( serialize( $wp_query->posts ?? '' ) );
if ( OPTML_DEBUG ) {
do_action( 'optml_log', 'Default page profile ID: ' . $page_id . '|' . sha1( $page_id ) );
}
return sha1(
$page_id
);
}
/**
* Stores above-fold image data for a specific profile ID and device type.
*
* @param string $id The profile ID.
* @param int $device_type The device type constant.
* @param array<string> $above_fold_images Array of above-fold images.
* @param array<string, array<string, array<int, string>>> $af_bg_selectors Array of above-fold background selectors.
* Array structure:
* [
* 'css_selector' => [
* 'above_the_fold_selector' => [
* 0 => 'background_image_url',
* 1 => 'background_image_url',
* ...
* ],
* ...
* ],
* ...
* ] Array of above-fold background selectors.
* @param array{imageId?: string, bgSelector?: string, bgUrls?: array<string>, type?: string} $lcp_data LCP (Largest Contentful Paint) data.
* where 'imageId' is the element identifier,
* 'bgSelector' is the selector,
* 'bgUrls' is an array of URLs
* 'type' is the type of the LCP element.
* @param array<int, array{w: int, h: int}> $missing_dimensions Array of missing dimensions.
* @param array<int, array<int, array{w: int, h: int, d: int, s: string, b: int}>> $missing_srcsets Array of missing srcsets.
* @param array<int, bool> $crop_status Array of crop status for images.
* @return void
*/
public function store( string $id, int $device_type, array $above_fold_images, $af_bg_selectors = [], $lcp_data = [], $missing_dimensions = [], $missing_srcsets = [], $crop_status = [] ) {
if ( ! in_array( (int) $device_type, self::get_active_devices(), true ) ) {
return;
}
// store $above_fold_images as image_id => true to faster access.
$above_fold_images = array_fill_keys( $above_fold_images, true );
// Store missing dimensions only from desktop device to avoid using mobile dimensions on desktop.
// Mobile will rely on srcset for proper dimensions.
if ( $device_type === self::DEVICE_TYPE_DESKTOP ) {
$global_data = [];
if ( ! empty( $missing_srcsets ) ) {
$global_data['s'] = $missing_srcsets;
}
if ( ! empty( $missing_dimensions ) ) {
$global_data['m'] = $missing_dimensions;
}
if ( ! empty( $crop_status ) ) {
$global_data['c'] = $crop_status;
}
if ( ! empty( $global_data ) ) {
// those measurements are not device specific, so we store them in on a global profile scope.
$this->storage->store(
$id,
$global_data
);
}
}
$this->storage->store(
$id . '_' . $device_type,
[
'af' => $above_fold_images,
'bg' => $af_bg_selectors,
'lcp' => $lcp_data,
]
);
}
/**
* Gets the missing dimensions for a specific profile ID.
*
* @param int $image_id The image ID to get the missing dimensions for.
*
* @return array{w: int, h: int}|array{} The missing dimensions.
*/
public function get_missing_dimensions( int $image_id ): array {
return self::$current_profile_data[ self::DEVICE_TYPE_GLOBAL ]['m'][ $image_id ] ?? [];
}
/**
* Gets the missing srcsets for a specific profile ID.
*
* @param int $image_id The image ID to get the missing srcsets for.
*
* @return array<int, array{w: int, h: int, d: int, s: string, b: int}>|array{} The missing srcsets.
*/
public function get_missing_srcsets( int $image_id ): array {
return self::$current_profile_data[ self::DEVICE_TYPE_GLOBAL ]['s'][ $image_id ] ?? [];
}
/**
* Gets the crop status for a specific image ID.
*
* @param int $image_id The image ID to get the crop status for.
*
* @return bool The crop status.
*/
public function get_crop_status( int $image_id ): bool {
return self::$current_profile_data[ self::DEVICE_TYPE_GLOBAL ]['c'][ $image_id ] ?? false;
}
/**
* Checks if profile data exists for all active device types.
*
* @param string $id The profile ID to check.
*
* @return bool True if data exists for all device types, false otherwise.
*/
public function exists_all( $id ): bool {
foreach ( self::get_active_devices() as $device ) {
if ( ! $this->exists( $id, $device ) ) {
return false;
}
}
return true;
}
/**
* Gets a list of device types that are missing profile data.
*
* @param string $id The profile ID to check.
*
* @return array List of device types missing profile data.
*/
public function missing_devices( $id ): array {
$missing = [];
foreach ( self::get_active_devices() as $device ) {
if ( ! $this->exists( $id, $device ) ) {
$missing[] = $device;
}
}
return $missing;
}
/**
* Checks if profile data exists for a specific device type.
*
* @param string $id The profile ID to check.
* @param int $device The device type constant.
*
* @return bool True if data exists, false otherwise.
*/
public function exists( $id, $device ): bool {
return $this->storage->get( $id . '_' . $device ) !== false;
}
/**
* Gets the current profile ID being processed.
*
* @return string The current profile ID or null if not set.
*/
public static function get_current_profile_id(): string {
return self::$current_profile_id;
}
/**
* Sets the current profile ID.
*
* @param string $id The profile ID to set as current.
*
* @return void
*/
public static function set_current_profile_id( $id ): void {
self::$current_profile_id = $id;
}
/**
* Gets the current profile data for all device types.
*
* @return array The current profile data.
*/
public static function get_current_profile_data(): array {
return self::$current_profile_data;
}
/**
* Sets the current profile data by loading it from storage.
*
* @return array The loaded profile data.
* @throws \Exception If current profile ID is not set.
*/
public function set_current_profile_data(): array {
if ( empty( self::get_current_profile_id() ) ) {
throw new \Exception( 'Current profile ID is not set' );
}
if ( ! empty( self::$current_profile_data ) ) {
return self::$current_profile_data;
}
self::$current_profile_data = [
self::DEVICE_TYPE_MOBILE => $this->storage->get( self::get_current_profile_id() . '_' . self::DEVICE_TYPE_MOBILE ),
self::DEVICE_TYPE_DESKTOP => $this->storage->get( self::get_current_profile_id() . '_' . self::DEVICE_TYPE_DESKTOP ),
self::DEVICE_TYPE_GLOBAL => $this->storage->get( self::get_current_profile_id() ),
];
if ( OPTML_DEBUG ) {
do_action( 'optml_log', 'Profile data: ' . print_r( self::$current_profile_data, true ) . ' for id: ' . self::get_current_profile_id() );
}
return self::$current_profile_data;
}
/**
* Checks if an image is in the viewport of all device types.
*
* @param int $image_id The image ID to check.
*
* @return bool True if the image is in the viewport of all device types, false otherwise.
*/
public function is_in_all_viewports( int $image_id ): bool {
foreach ( self::get_active_devices() as $device ) {
// If the data is not available for the device, return false.
if ( empty( self::$current_profile_data[ $device ] ?? null ) ) {
return false;
}
// If the image is not in the viewport of the device, return false.
if ( ! ( self::$current_profile_data[ $device ]['af'][ $image_id ] ?? false ) ) {
return false;
}
}
// If the image is in the viewport of all device types, return true.
return true;
}
/**
* Checks if the LCP image is in the viewport of all device types.
*
* @param int $image_id The image ID to check.
*
* @return bool True if the LCP image is in the viewport of all device types, false otherwise.
*/
public function is_lcp_image_in_all_viewports( int $image_id ): bool {
foreach ( self::get_active_devices() as $device ) {
if ( ( ( self::$current_profile_data[ $device ]['lcp']['type'] ?? '' ) === 'img' ) && ( self::$current_profile_data[ $device ]['lcp']['imageId'] === $image_id ) ) {
return true;
}
}
return false;
}
/**
* Checks if an image is in the viewport of any device type.
*
* @param mixed $image_id The image ID to check.
*
* @return int|false The device type if the image is in the viewport, false otherwise.
*/
public function is_in_any_viewport( $image_id ) {
foreach ( self::get_active_devices() as $device ) {
if ( self::$current_profile_data[ $device ]['af'][ $image_id ] ?? false ) {
return $device;
}
}
return false;
}
/**
* Gets the profile data for a specific ID.
*
* @param string $id The profile ID to get data for.
*
* @return array The profile data.
*/
public function get_profile_data( $id ) {
$profile_data = [];
foreach ( self::get_active_devices() as $device ) {
$profile_data[ $device ] = $this->storage->get( $id . '_' . $device );
}
return $profile_data;
}
/**
* Resets the current profile ID and data.
*
* @return void
*/
public static function reset_current_profile() {
self::$current_profile_id = null;
self::$current_profile_data = [];
}
/**
* Gets the list of active device types supported by the profiler.
*
* @return array Array of device type constants.
*/
public static function get_active_devices(): array {
return [
self::DEVICE_TYPE_MOBILE,
self::DEVICE_TYPE_DESKTOP,
];
}
/**
* Check if there is any profile data available for the current profile.
*
* @return bool
*/
public function is_data_available(): bool {
foreach ( self::get_active_devices() as $device ) {
if ( empty( self::$current_profile_data[ $device ] ) ) {
return false;
}
}
return true;
}
/**
* Generate HTML comment with profile data and performance metrics.
*
* @return string HTML comment with profile data and metrics.
*/
public function get_current_profile_html_comment(): string {
$profile_data = self::get_current_profile_data();
if ( empty( $profile_data ) ) {
return '<!-- plugin=optimole-wp: No profile ID available -->';
}
// Format the HTML comment
$comment_parts = [
'plugin=optimole-wp',
];
// Add device-specific metrics
foreach ( $profile_data as $device_type => $device_data ) {
$device_name = $this->get_device_name( $device_type );
$comment_parts[] = 'measurement#device-' . $device_name . '#' . json_encode( $device_data );
}
return '<!-- ' . implode( ' ', $comment_parts ) . ' -->';
}
/**
* Get device name from device type constant.
*
* @param int $device_type The device type constant.
* @return string Device name.
*/
private function get_device_name( int $device_type ): string {
switch ( $device_type ) {
case self::DEVICE_TYPE_MOBILE:
return 'mobile';
case self::DEVICE_TYPE_DESKTOP:
return 'desktop';
case self::DEVICE_TYPE_GLOBAL:
return 'global';
default:
return 'unknown';
}
}
}