PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 4.2.11
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v4.2.11
4.2.13 4.2.12 4.2.11 4.2.10 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 2.5.5 2.5.6 2.5.7 3.0.0 3.0.1 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.11.0 3.11.1 3.11.2 3.11.3 3.12.0 3.12.1 All 134 releases
optimole-wp / inc / v2 / PageProfiler / Profile.php

Profile.php in Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization 4.2.11, at inc/v2/PageProfiler/Profile.php

511 lines 15.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace OptimoleWP\PageProfiler;
4
5 use OptimoleWP\Preload\Links;
6 use Optml_Lazyload_Replacer;
7
8 /**
9 * Class Profile
10 *
11 * Handles page profiling functionality for Optimole, including storage and retrieval
12 * of above-fold image data for different device types.
13 *
14 * @package OptimoleWP\PageProfiler
15 */
16 class Profile {
17
18 /**
19 * Placeholder used to identify where a profile ID should be inserted.
20 */
21 const PLACEHOLDER = '###pageprofileid###';
22
23 /**
24 * Placeholder used to identify where a profile HMAC should be inserted.
25 */
26 const PLACEHOLDER_HMAC = '###profilehmac###';
27 /**
28 * Placeholder used to identify where a profile time should be inserted.
29 */
30 const PLACEHOLDER_TIME = '###profiletime###';
31
32 /**
33 * Placeholder used to indicate a missing profile ID.
34 */
35 const PLACEHOLDER_MISSING = '###pageprofileidmissing###';
36
37 /**
38 * Placeholder used to identify where a profile request url should be inserted.
39 */
40 const PLACEHOLDER_URL = '###pageprofileurl###';
41
42 /**
43 * Device type constant for mobile devices.
44 */
45 const DEVICE_TYPE_MOBILE = 1;
46
47 /**
48 * Device type constant for desktop devices.
49 *
50 * @var int
51 */
52 const DEVICE_TYPE_DESKTOP = 2;
53
54 /**
55 * Device type constant for global devices.
56 *
57 * @var int
58 */
59 const DEVICE_TYPE_GLOBAL = -1;
60
61 /**
62 * Stores the current profile ID being processed.
63 *
64 * @var string|null
65 */
66 private static $current_profile_id = null;
67
68 /**
69 * Stores the current profile data for all device types.
70 *
71 * @var array
72 */
73 private static $current_profile_data = [];
74
75 /**
76 * The storage handler instance.
77 *
78 * @var Storage\Base
79 */
80 private $storage;
81
82 /**
83 * Constructor.
84 *
85 * Initializes the storage handler based on the provided filter.
86 *
87 * @throws \Exception If an invalid storage class is provided.
88 */
89 public function __construct() {
90 /**
91 * Filter the storage class.
92 * Allows to change the storage class to a different one, i.e a database storage class/file storage class etc.
93 *
94 * @param string $storage_class The storage class.
95 *
96 * @return string The storage class.
97 */
98 $storage_class = apply_filters( 'optml_page_profiler_storage', wp_using_ext_object_cache() ? Storage\ObjectCache::class : Storage\Transients::class );
99 if ( OPTML_DEBUG ) {
100 do_action( 'optml_log', 'storage_class: ' . $storage_class );
101 }
102 if ( ! is_subclass_of( $storage_class, Storage\Base::class ) ) {
103 throw new \Exception( 'Invalid storage class' );
104 }
105 $this->storage = new $storage_class();
106 }
107
108 /**
109 * Generate a unique ID for the page profile
110 *
111 * @param string $content The content of the page.
112 *
113 * @return string New id
114 */
115 public static function generate_id( string $content = '' ): string {
116 if ( OPTML_DEBUG ) {
117 do_action( 'optml_log', 'Generating page profile ID: ' . $content . ' ' . ( $_SERVER['REQUEST_URI'] ?? '' ) );
118 }
119
120 /**
121 * 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.
122 *
123 * @param string $id The page profile ID.
124 * @param string $content The content of the page.
125 *
126 * @return string New id
127 */
128 return apply_filters( 'optml_page_profile_id', self::get_default_id( $content ), $content );
129 }
130
131 /**
132 * Generate a default ID for the page profile
133 *
134 * @param string $content The content of the page.
135 *
136 * @return string New id
137 */
138 public static function get_default_id( string $content ): string {
139 global $post;
140 global $wp_query;
141 $page_id = serialize( get_theme_mods() ) .
142 get_queried_object_id() .
143 ( get_queried_object() ? get_class( get_queried_object() ) : '' ) .
144 ( $post->post_modified ?? '' ) .
145 ( serialize( $wp_query->posts ?? '' ) );
146 if ( OPTML_DEBUG ) {
147 do_action( 'optml_log', 'Default page profile ID: ' . $page_id . '|' . sha1( $page_id ) );
148 }
149 return sha1(
150 $page_id
151 );
152 }
153
154 /**
155 * Stores above-fold image data for a specific profile ID and device type.
156 *
157 * @param string $id The profile ID.
158 * @param int $device_type The device type constant.
159 * @param array<int|string> $above_fold_images Array of above-fold image ids.
160 * @param array<string, array<string, array<int, string>>> $af_bg_selectors Array of above-fold background selectors.
161 * Array structure:
162 * [
163 * 'css_selector' => [
164 * 'above_the_fold_selector' => [
165 * 0 => 'background_image_url',
166 * 1 => 'background_image_url',
167 * ...
168 * ],
169 * ...
170 * ],
171 * ...
172 * ] Array of above-fold background selectors.
173 * @param array{imageId?: string, bgSelector?: string, bgUrls?: array<string>, type?: string} $lcp_data LCP (Largest Contentful Paint) data.
174 * where 'imageId' is the element identifier,
175 * 'bgSelector' is the selector,
176 * 'bgUrls' is an array of URLs
177 * 'type' is the type of the LCP element.
178 * @param array<int, array{w: int, h: int}> $missing_dimensions Array of missing dimensions.
179 * @param array<int, array<int, array{w: int, h: int, d: int, s: string, b: int}>> $missing_srcsets Array of missing srcsets.
180 * @param array<int, bool> $crop_status Array of crop status for images.
181 * @return void
182 */
183 public function store( string $id, int $device_type, array $above_fold_images, $af_bg_selectors = [], $lcp_data = [], $missing_dimensions = [], $missing_srcsets = [], $crop_status = [] ) {
184 if ( ! in_array( (int) $device_type, self::get_active_devices(), true ) ) {
185 return;
186 }
187
188 // store $above_fold_images as image_id => true to faster access.
189 $above_fold_images = array_fill_keys( $above_fold_images, true );
190 // Store missing dimensions only from desktop device to avoid using mobile dimensions on desktop.
191 // Mobile will rely on srcset for proper dimensions.
192 if ( $device_type === self::DEVICE_TYPE_DESKTOP ) {
193 $global_data = [];
194 if ( ! empty( $missing_srcsets ) ) {
195 $global_data['s'] = $missing_srcsets;
196 }
197 if ( ! empty( $missing_dimensions ) ) {
198 $global_data['m'] = $missing_dimensions;
199 }
200 if ( ! empty( $crop_status ) ) {
201 $global_data['c'] = $crop_status;
202 }
203 if ( ! empty( $global_data ) ) {
204 // those measurements are not device specific, so we store them in on a global profile scope.
205 $this->storage->store(
206 $id,
207 $global_data
208 );
209 }
210 }
211 $this->storage->store(
212 $id . '_' . $device_type,
213 [
214 'af' => $above_fold_images,
215 'bg' => $af_bg_selectors,
216 'lcp' => $lcp_data,
217 ]
218 );
219 }
220
221 /**
222 * Gets the missing dimensions for a specific profile ID.
223 *
224 * @param int $image_id The image ID to get the missing dimensions for.
225 *
226 * @return array{w: int, h: int}|array{} The missing dimensions.
227 */
228 public function get_missing_dimensions( int $image_id ): array {
229 return self::$current_profile_data[ self::DEVICE_TYPE_GLOBAL ]['m'][ $image_id ] ?? [];
230 }
231
232 /**
233 * Gets the missing srcsets for a specific profile ID.
234 *
235 * @param int $image_id The image ID to get the missing srcsets for.
236 *
237 * @return array<int, array{w: int, h: int, d: int, s: string, b: int}>|array{} The missing srcsets.
238 */
239 public function get_missing_srcsets( int $image_id ): array {
240 return self::$current_profile_data[ self::DEVICE_TYPE_GLOBAL ]['s'][ $image_id ] ?? [];
241 }
242
243 /**
244 * Gets the crop status for a specific image ID.
245 *
246 * @param int $image_id The image ID to get the crop status for.
247 *
248 * @return bool The crop status.
249 */
250 public function get_crop_status( int $image_id ): bool {
251 return self::$current_profile_data[ self::DEVICE_TYPE_GLOBAL ]['c'][ $image_id ] ?? false;
252 }
253 /**
254 * Checks if profile data exists for all active device types.
255 *
256 * @param string $id The profile ID to check.
257 *
258 * @return bool True if data exists for all device types, false otherwise.
259 */
260 public function exists_all( $id ): bool {
261 foreach ( self::get_active_devices() as $device ) {
262 if ( ! $this->exists( $id, $device ) ) {
263 return false;
264 }
265 }
266
267 return true;
268 }
269
270
271 /**
272 * Gets a list of device types that are missing profile data.
273 *
274 * @param string $id The profile ID to check.
275 *
276 * @return array List of device types missing profile data.
277 */
278 public function missing_devices( $id ): array {
279 $missing = [];
280 foreach ( self::get_active_devices() as $device ) {
281 if ( ! $this->exists( $id, $device ) ) {
282 $missing[] = $device;
283 }
284 }
285
286 return $missing;
287 }
288
289 /**
290 * Checks if profile data exists for a specific device type.
291 *
292 * @param string $id The profile ID to check.
293 * @param int $device The device type constant.
294 *
295 * @return bool True if data exists, false otherwise.
296 */
297 public function exists( $id, $device ): bool {
298 return $this->storage->get( $id . '_' . $device ) !== false;
299 }
300
301 /**
302 * Gets the current profile ID being processed.
303 *
304 * @return string The current profile ID or null if not set.
305 */
306 public static function get_current_profile_id(): string {
307 return self::$current_profile_id;
308 }
309
310 /**
311 * Sets the current profile ID.
312 *
313 * @param string $id The profile ID to set as current.
314 *
315 * @return void
316 */
317 public static function set_current_profile_id( $id ): void {
318 self::$current_profile_id = $id;
319 }
320
321 /**
322 * Gets the current profile data for all device types.
323 *
324 * @return array The current profile data.
325 */
326 public static function get_current_profile_data(): array {
327 return self::$current_profile_data;
328 }
329
330 /**
331 * Sets the current profile data by loading it from storage.
332 *
333 * @return array The loaded profile data.
334 * @throws \Exception If current profile ID is not set.
335 */
336 public function set_current_profile_data(): array {
337 if ( empty( self::get_current_profile_id() ) ) {
338 throw new \Exception( 'Current profile ID is not set' );
339 }
340 if ( ! empty( self::$current_profile_data ) ) {
341 return self::$current_profile_data;
342 }
343 self::$current_profile_data = [
344 self::DEVICE_TYPE_MOBILE => $this->storage->get( self::get_current_profile_id() . '_' . self::DEVICE_TYPE_MOBILE ),
345 self::DEVICE_TYPE_DESKTOP => $this->storage->get( self::get_current_profile_id() . '_' . self::DEVICE_TYPE_DESKTOP ),
346 self::DEVICE_TYPE_GLOBAL => $this->storage->get( self::get_current_profile_id() ),
347 ];
348 if ( OPTML_DEBUG ) {
349 do_action( 'optml_log', 'Profile data: ' . print_r( self::$current_profile_data, true ) . ' for id: ' . self::get_current_profile_id() );
350 }
351
352 return self::$current_profile_data;
353 }
354
355 /**
356 * Checks if an image is in the viewport of all device types.
357 *
358 * @param int $image_id The image ID to check.
359 *
360 * @return bool True if the image is in the viewport of all device types, false otherwise.
361 */
362 public function is_in_all_viewports( int $image_id ): bool {
363 foreach ( self::get_active_devices() as $device ) {
364 // If the data is not available for the device, return false.
365 if ( empty( self::$current_profile_data[ $device ] ?? null ) ) {
366 return false;
367 }
368 // If the image is not in the viewport of the device, return false.
369 if ( ! ( self::$current_profile_data[ $device ]['af'][ $image_id ] ?? false ) ) {
370 return false;
371 }
372 }
373
374 // If the image is in the viewport of all device types, return true.
375 return true;
376 }
377
378 /**
379 * Checks if the LCP image is in the viewport of all device types.
380 *
381 * @param int $image_id The image ID to check.
382 *
383 * @return bool True if the LCP image is in the viewport of all device types, false otherwise.
384 */
385 public function is_lcp_image_in_all_viewports( int $image_id ): bool {
386 foreach ( self::get_active_devices() as $device ) {
387 if ( ( ( self::$current_profile_data[ $device ]['lcp']['type'] ?? '' ) === 'img' ) && ( self::$current_profile_data[ $device ]['lcp']['imageId'] === $image_id ) ) {
388 return true;
389 }
390 }
391
392 return false;
393 }
394
395 /**
396 * Checks if an image is in the viewport of any device type.
397 *
398 * @param mixed $image_id The image ID to check.
399 *
400 * @return int|false The device type if the image is in the viewport, false otherwise.
401 */
402 public function is_in_any_viewport( $image_id ) {
403 foreach ( self::get_active_devices() as $device ) {
404 if ( self::$current_profile_data[ $device ]['af'][ $image_id ] ?? false ) {
405 return $device;
406 }
407 }
408
409 return false;
410 }
411
412 /**
413 * Gets the profile data for a specific ID.
414 *
415 * @param string $id The profile ID to get data for.
416 *
417 * @return array The profile data.
418 */
419 public function get_profile_data( $id ) {
420 $profile_data = [];
421 foreach ( self::get_active_devices() as $device ) {
422 $profile_data[ $device ] = $this->storage->get( $id . '_' . $device );
423 }
424
425 return $profile_data;
426 }
427
428 /**
429 * Resets the current profile ID and data.
430 *
431 * @return void
432 */
433 public static function reset_current_profile() {
434 self::$current_profile_id = null;
435 self::$current_profile_data = [];
436 }
437
438 /**
439 * Gets the list of active device types supported by the profiler.
440 *
441 * @return array Array of device type constants.
442 */
443 public static function get_active_devices(): array {
444 return [
445 self::DEVICE_TYPE_MOBILE,
446 self::DEVICE_TYPE_DESKTOP,
447 ];
448 }
449
450 /**
451 * Check if there is any profile data available for the current profile.
452 *
453 * @return bool
454 */
455 public function is_data_available(): bool {
456 foreach ( self::get_active_devices() as $device ) {
457 if ( empty( self::$current_profile_data[ $device ] ) ) {
458 return false;
459 }
460 }
461
462 return true;
463 }
464
465 /**
466 * Generate HTML comment with profile data and performance metrics.
467 *
468 * @return string HTML comment with profile data and metrics.
469 */
470 public function get_current_profile_html_comment(): string {
471 $profile_data = self::get_current_profile_data();
472
473 if ( empty( $profile_data ) ) {
474 return '<!-- plugin=optimole-wp: No profile ID available -->';
475 }
476
477 // Format the HTML comment
478 $comment_parts = [
479 'plugin=optimole-wp',
480 ];
481
482 // Add device-specific metrics
483 foreach ( $profile_data as $device_type => $device_data ) {
484 $device_name = $this->get_device_name( $device_type );
485 $comment_parts[] = 'measurement#device-' . $device_name . '#' . json_encode( $device_data, JSON_HEX_TAG | JSON_HEX_AMP );
486 }
487
488 return '<!-- ' . implode( ' ', $comment_parts ) . ' -->';
489 }
490
491
492 /**
493 * Get device name from device type constant.
494 *
495 * @param int $device_type The device type constant.
496 * @return string Device name.
497 */
498 private function get_device_name( int $device_type ): string {
499 switch ( $device_type ) {
500 case self::DEVICE_TYPE_MOBILE:
501 return 'mobile';
502 case self::DEVICE_TYPE_DESKTOP:
503 return 'desktop';
504 case self::DEVICE_TYPE_GLOBAL:
505 return 'global';
506 default:
507 return 'unknown';
508 }
509 }
510 }
511