PluginProbe
BerqWP – All-In-One Optimization for Core Web Vitals, Cache, CDN, Images, CSS & JavaScript / 4.0.28
BerqWP – All-In-One Optimization for Core Web Vitals, Cache, CDN, Images, CSS & JavaScript v4.0.28
4.1.16 4.1.15 4.1.14 4.1.13 4.1.12 4.1.11 4.1.10 4.0.30 4.0.29 4.0.28 4.0.27 4.0.26 4.0.24 4.0.25 4.0.23 4.0.22 4.0.21 4.0.19 4.0.18 4.0.17 4.0.16 1.9.3 1.9.4 1.9.5 1.9.6 All 170 releases
searchpro / inc / photon / class-berqPageOptimizer.php

class-berqPageOptimizer.php in BerqWP – All-In-One Optimization for Core Web Vitals, Cache, CDN, Images, CSS & JavaScript 4.0.28, at inc/photon/class-berqPageOptimizer.php

1,569 lines 56.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) exit;
3
4 use BerqWP\Cache;
5 use BerqWP_Deps\voku\helper\HtmlDomParser;
6
7 class berqPageOptimizer {
8 public $page_slug = null;
9 public $page_url = null;
10 public $settings = null;
11
12 function __construct() {
13
14 if ($this->settings === null) {
15 $this->settings = berqwp_get_page_params(home_url());
16 }
17
18 add_filter('berqwp_photon_before_closing_body', [$this, 'img_lazy_load_js']);
19 add_filter('berqwp_photon_before_closing_body', [$this, 'video_lazy_load_js']);
20 add_filter('berqwp_photon_before_closing_body', [$this, 'iframe_lazy_load_js']);
21 add_filter('berqwp_photon_before_closing_body', [$this, 'dynamic_js_loading_script']);
22 add_filter('berqwp_photon_before_closing_body', [$this, 'dynamic_css_loading_script']);
23 add_filter('berqwp_photon_before_closing_body', [$this, 'prerender_on_hover']);
24
25 }
26
27 function start_cache() {
28 add_action('template_redirect', [$this, 'buffer_start'], 2);
29 }
30
31 function set_slug($slug) {
32 $this->page_slug = $slug;
33 }
34
35 function set_page($page_url) {
36 $this->page_url = $page_url;
37 }
38
39
40 function buffer_start() {
41 ob_start([$this, 'buffer_end']);
42 }
43
44 function prerender_on_hover($script) {
45
46 if ($this->settings['prerender_link']) {
47 $script .= "
48 <script id='prefetch-links' defer>
49
50 function berq_prerender(url) {
51 const s = document.createElement('script');
52 s.type = 'speculationrules';
53 s.textContent = JSON.stringify({
54 prerender: [{ urls: [url] }]
55 });
56 document.head.appendChild(s);
57 }
58
59 // Set to keep track of prefetched links
60 const prefetchedLinks = new Set();
61
62 // Get all anchor tags on the page
63 const links = document.querySelectorAll('a:not([data-price-key])');
64
65 // Loop through each anchor tag
66 links.forEach(link => {
67 // Add mouseover event listener
68 link.addEventListener('mouseover', () => {
69 const excludes = ['tel:', 'mailto:', 'sms:', 'geo:'];
70
71 const isExcluded = excludes.some(prefix =>
72 link.href.startsWith(prefix)
73 );
74
75 // Check if the link has already been prefetched
76 if (!prefetchedLinks.has(link.href) && !isExcluded) {
77 berq_prerender(link.href);
78
79 // Add the link to the set of prefetched links
80 prefetchedLinks.add(link.href);
81 }
82 });
83 });
84
85 </script>";
86 }
87
88 return $script;
89 }
90
91 function video_lazy_load_js($script) {
92
93 if ($this->settings['lazy_load_videos']) {
94
95 $script .= "
96 <script async>
97 (function(){
98 // window.addEventListener('load', function() {
99 window.addEventListener('DOMContentLoaded', function() {
100 // Function to load video and its sources
101 function loadLazyVideo(video) {
102 // Set video src from data attribute if exists
103 const videoSrc = video.getAttribute('data-berqwpsrc');
104 if (videoSrc) {
105 video.setAttribute('src', videoSrc);
106 }
107
108 // Find all <source> tags inside the video and apply their data-berqwpsrc as src
109 const sources = video.querySelectorAll('source');
110 sources.forEach(source => {
111 const sourceSrc = source.getAttribute('data-berqwpsrc');
112 if (sourceSrc) {
113 source.setAttribute('src', sourceSrc);
114 }
115 });
116
117 // Load the video once the sources have been set
118 video.load();
119 }
120
121 // Use IntersectionObserver to detect when the video is about to enter the viewport
122 let lazyVideos = [].slice.call(document.querySelectorAll('video.berqwp-lazy-video'));
123
124 if ('IntersectionObserver' in window) {
125 let lazyVideoObserver = new IntersectionObserver(function(entries, observer) {
126 entries.forEach(function(entry) {
127 if (entry.isIntersecting) {
128 let video = entry.target;
129 loadLazyVideo(video);
130 // Unobserve the video once it's loaded
131 lazyVideoObserver.unobserve(video);
132 }
133 });
134 });
135
136 // Observe each lazy video
137 lazyVideos.forEach(function(video) {
138 lazyVideoObserver.observe(video);
139 });
140 } else {
141 // Fallback for browsers that don't support IntersectionObserver
142 lazyVideos.forEach(function(video) {
143 loadLazyVideo(video);
144 });
145 }
146 });
147
148 })()
149 </script>
150 ";
151 }
152
153 return $script;
154 }
155
156 function iframe_lazy_load_js($script) {
157
158 if ($this->settings['youtube_lazyloading']) {
159
160 $script .= "
161 <script defer>
162 // document.addEventListener('DOMContentLoaded', function () {
163
164 (function(){
165
166 var options = {
167 root: null, // null means the viewport
168 rootMargin: '0px', // adjust as needed
169 threshold: 0.1 // adjust as needed
170 };
171
172 var bwp_iframe_observer = new IntersectionObserver(function (entries, observer) {
173 entries.forEach(function (entry) {
174 if (entry.isIntersecting) {
175 let item = entry.target;
176 let iframe = item.getAttribute('data-embed');
177
178 let wrapper = document.createElement('div');
179 wrapper.innerHTML = iframe;
180 let iframeElement = wrapper.firstChild;
181
182 // Insert the iframe next to the item
183 item.insertAdjacentElement('afterend', iframeElement);
184
185 // Remove the original item
186 item.remove();
187
188 bwp_iframe_observer.unobserve(item);
189 }
190 });
191 }, options);
192
193 let yt_em = document.querySelectorAll('.berqwp-lazy-youtube');
194 yt_em.forEach(function (item) {
195 bwp_iframe_observer.observe(item);
196 });
197
198 })()
199 // });
200 </script>
201 ";
202 }
203
204 return $script;
205 }
206
207 static function url_to_path($url)
208 {
209
210 if (strpos($url, '?') !== false) {
211 $url = explode('?', $url)[0];
212 }
213
214 if (strpos($url, '#') !== false) {
215 $url = explode('#', $url)[0];
216 }
217
218 // Handle relative URLs
219 if (strpos($url, '//') === 0) {
220 return realpath(ABSPATH . ltrim($url, '/'));
221 }
222
223 $content_url = content_url();
224 $content_dir = WP_CONTENT_DIR;
225
226 if (strpos($url, $content_url) === 0) {
227 // var_dump(realpath(
228 // str_replace($content_url, $content_dir, $url)
229 // ));
230 return str_replace($content_url, $content_dir, $url);
231 }
232
233 // Fallback for site root files
234 $site_url = get_site_url('/');
235
236 if (strpos($url, $site_url) === 0) {
237 return realpath(
238 str_replace($site_url, ABSPATH, $url)
239 );
240 }
241
242 return false;
243 }
244
245 function img_lazy_load_js($script) {
246
247 if ($this->settings['img_lazyloading']) {
248
249 $script .= "
250 <script async>
251 (function(){
252
253 document.addEventListener('DOMContentLoaded', function () {
254 var berq_img_lazy_options = {
255 root: null, // null means the viewport
256 rootMargin: '200px',
257 threshold: 0
258 };
259
260 var img_observer = new IntersectionObserver(function (entries, observer) {
261 entries.forEach(function (entry) {
262 if (entry.isIntersecting) {
263 let img = entry.target;
264 let imgSrc = img.getAttribute('data-berqwpsrc');
265 let imgSrcset = img.getAttribute('data-berqwp-srcset');
266
267 // Set the actual image source from data-berqwpsrc
268 if (imgSrc !== null) {
269 img.src = imgSrc;
270 }
271
272 if (imgSrcset !== null) {
273 img.srcset = imgSrcset;
274 }
275
276 if (img.getAttribute('data-srcset') !== null) {
277 img.srcset = img.getAttribute('data-srcset');
278 }
279
280 // You might want to remove the data-src attribute after loading
281 img.removeAttribute('data-berqwpsrc');
282 img.removeAttribute('data-berqwp-srcset');
283
284 img_observer.unobserve(img);
285 }
286 });
287 }, berq_img_lazy_options);
288
289 function berqwp_lazyload_images() {
290 let lazyImages = document.querySelectorAll('img[data-berqwpsrc]');
291 lazy_img_int = 1000;
292
293 lazyImages.forEach(function (img) {
294 img_observer.observe(img);
295 });
296 }
297
298 function berqwp_lazyload_source() {
299 let lazyImages = document.querySelectorAll('source[data-berqwp-srcset]');
300 lazy_img_int = 1000;
301
302 lazyImages.forEach(function (img) {
303 img_observer.observe(img);
304 });
305 }
306
307 berqwp_lazyload_images();
308 setInterval(berqwp_lazyload_images, 1000);
309 setInterval(berqwp_lazyload_source, 1000);
310
311 });
312
313 })()
314 </script>
315 ";
316 }
317
318 return $script;
319 }
320
321 function dynamic_css_loading_script($script) {
322
323 if ($this->settings['css_optimization'] == 'asynchronous' || $this->settings['css_optimization'] == 'delay') {
324
325 $script .= "
326 <script defer>
327 function berqwp_init_css() {
328 // Get all link tags containing data-berqwp-style-href
329 let berqwp_linkTags = document.querySelectorAll('link[data-berqwp-style-href]');
330
331 // Iterate through each link tag
332 berqwp_linkTags.forEach(function (linkTag, index) {
333 // Set the href attribute of each link tag
334 linkTag.setAttribute('href', linkTag.getAttribute('data-berqwp-style-href'));
335
336 });
337
338 document
339 .querySelectorAll('style[type=\"text/berqwp-style\"]')
340 .forEach(style => {
341 style.type = 'text/css';
342 });
343
344
345 }
346 </script>
347 ";
348
349 if ($this->settings['css_optimization'] == 'asynchronous') {
350 $script .= "
351 <script defer>
352 requestIdleCallback(() => {
353 berqwp_init_css();
354 });
355 </script>
356 ";
357 }
358
359 if ($this->settings['css_optimization'] == 'delay') {
360 $script .= "
361 <script defer>
362 (function() {
363
364 function berqwp_css_handleUserInteraction(event) {
365
366
367 berqwp_init_css();
368
369
370 // After running the function, remove all event listeners to ensure it runs only once
371 for (let eventType of berqwp_js_interactionEventTypes) {
372 window.removeEventListener(eventType, berqwp_css_handleUserInteraction);
373 }
374 }
375
376 let berqwp_js_interactionEventTypes = ['click', 'mousemove', 'keydown', 'touchstart', 'scroll', 'berqwpLoadJS', 'berqwp_interaction_event'];
377
378 for (let eventType of berqwp_js_interactionEventTypes) {
379 window.addEventListener(eventType, berqwp_css_handleUserInteraction, { passive: false });
380 }
381
382 })()
383 </script>
384 ";
385 }
386 }
387
388 return $script;
389 }
390
391 function dynamic_js_loading_script($script) {
392
393 if ($this->settings['js_optimization'] == 'asynchronous' || $this->settings['js_optimization'] == 'delay') {
394
395 $script .= "
396 <script id='berqwp-preloader' defer>
397 class berqwpPreloader {
398 jobId = 0;
399 pending = new Map();
400
401 _preloadAssets(assets, asType) {
402 const seen = new Set();
403 const unique = assets.filter(({ url }) => {
404 if (seen.has(url)) return false;
405 seen.add(url);
406 return true;
407 });
408
409 const id = ++this.jobId;
410
411 return new Promise((resolve) => {
412 if (unique.length === 0) {
413 console.log('[berqwp] empty — resolving immediately'); // Is it bailing here?
414 resolve({ status: 'done', failed: [] });
415 return;
416 }
417
418 this.pending.set(id, resolve);
419
420 let completed = 0;
421 const failed = [];
422
423 const onSettle = () => {
424 completed++;
425 if (completed === unique.length) {
426 this.pending.delete(id);
427 resolve({ status: 'done', failed });
428 }
429 };
430
431 unique.forEach(({ url, crossOrigin }) => {
432 const alreadyPreloaded = document.querySelector(`link[rel=\"preload\"][as=\"\${asType}\"][href=\"\${url}\"]`);
433 const alreadyScript = asType === 'script' && document.querySelector(`script[src=\"\${url}\"]:not([type=\"text/bwp-script\"])`);
434 const alreadyStyle = asType === 'style' && document.querySelector(`link[rel=\"stylesheet\"][href=\"\${url}\"]`);
435
436 if (alreadyPreloaded || alreadyScript || alreadyStyle) {
437 return onSettle();
438 }
439
440 const link = document.createElement('link');
441 link.rel = 'preload';
442 link.as = asType;
443 link.href = url;
444
445 if (crossOrigin) {
446 link.crossOrigin = crossOrigin;
447 }
448
449 link.onload = () => {
450 console.log('[berqwp] preloaded:', url); // Is onload firing?
451 onSettle();
452 };
453 link.onerror = () => {
454 console.warn('[berqwp] failed:', url); // Or is it erroring?
455 failed.push(url);
456 onSettle();
457 };
458
459 document.head.appendChild(link);
460 });
461 });
462 }
463
464 preload(assets) {
465 const normalized = assets.map(a =>
466 typeof a === 'string' ? { url: a, crossOrigin: null } : a
467 );
468 return this._preloadAssets(normalized, 'script');
469 }
470
471 preloadScripts() {
472 const elements = [...document.querySelectorAll('script[type=\"text/bwp-script\"]')];
473 const assets = elements
474 .filter(el => el.getAttribute('src'))
475 .map(el => ({
476 url: el.getAttribute('src'),
477 crossOrigin: el.getAttribute('crossorigin') || null
478 }));
479
480 return this._preloadAssets(assets, 'script');
481 }
482
483 preloadStyles() {
484 const elements = [...document.querySelectorAll('link[data-berqwp-style-href]')];
485 const assets = elements
486 .filter(el => el.getAttribute('data-berqwp-style-href'))
487 .map(el => ({
488 url: el.getAttribute('data-berqwp-style-href'),
489 crossOrigin: el.getAttribute('crossorigin') || null
490 }));
491
492 return this._preloadAssets(assets, 'style');
493 }
494
495 }
496
497 (function() {
498 window.bwpPreloader = new berqwpPreloader();
499 })()
500 </script>
501 <script defer>
502 (function(){
503 // Select all inline script tags with type=\"text/bwp-script\"
504 const inlineScripts = document.querySelectorAll('script[type=\"text/bwp-script\"]');
505
506 inlineScripts.forEach((script) => {
507 // Get the content of the inline script
508 const scriptContent = script.innerHTML;
509
510 if (!scriptContent) {
511 return;
512 }
513
514 if (script.getAttribute('data-type') == 'module') {
515 return;
516 }
517
518 // Create a Blob from the script content
519 const blob = new Blob([scriptContent], { type: 'application/javascript' });
520
521 // Create a URL for the Blob
522 const scriptURL = URL.createObjectURL(blob);
523
524 // Create a new external script tag
525 const newScript = document.createElement('script');
526 newScript.src = scriptURL;
527 newScript.type = 'text/bwp-script'; // Or 'text/bwp-script', but 'application/javascript' is more typical for JS
528 newScript.setAttribute('data-type', script.getAttribute('data-type'));
529
530 // Copy other attributes if necessary
531 Array.from(script.attributes).forEach(function(attr) {
532 if (attr.name !== 'type' && attr.name !== 'src') {
533 newScript.setAttribute(attr.name, attr.value);
534 }
535 });
536
537 // Replace the original inline script with the new external script tag
538 script.parentNode.replaceChild(newScript, script);
539 });
540
541 })();
542
543 </script>
544 <script data-mode='4' defer>
545 var berq_click = null;
546 function berqwpLoadJs() {
547
548 (function () {
549 'use strict';
550 const VERSION = '3.0';
551 let activated = false;
552 const pendingScripts = new Map();
553 let completedCount = 0;
554
555 // Custom Event System
556 const createTrackerEvent = (name, detail = {}) =>
557 new CustomEvent(`bwp:\${name}`, {
558 detail: {
559 version: VERSION,
560 timestamp: performance.now(),
561 ...detail
562 }
563 });
564
565 // Event Polyfills
566 (function () {
567 const originalAddEventListener = EventTarget.prototype.addEventListener;
568 const readyState = document.readyState;
569 const simulatedEvents = new WeakMap();
570
571 function createEvent(type) {
572 const event = new Event(type, { bubbles: false, cancelable: false });
573 event.isSimulated = true;
574 return event;
575 }
576
577 EventTarget.prototype.addEventListener = function (type, listener, options) {
578
579 if (typeof listener === 'function') {
580 if (type === 'DOMContentLoaded') {
581 if (readyState !== 'loading') {
582 setTimeout(() => listener.call(this, createEvent(type)), 0);
583 return;
584 }
585 } else if ((this === window || this instanceof HTMLScriptElement) && type === 'load') {
586 if (readyState === 'complete') {
587 setTimeout(() => listener.call(this, createEvent(type)), 0);
588 return;
589 }
590 }
591 }
592
593
594 originalAddEventListener.call(this, type, listener, options);
595 };
596
597 ['onDOMContentLoaded', 'onload'].forEach(prop => {
598 const target = prop === 'onload' ? window : document;
599 const descriptor = Object.getOwnPropertyDescriptor(target.constructor.prototype, prop.toLowerCase()) || {};
600
601 Object.defineProperty(target, prop, {
602 set(fn) {
603 if (typeof fn === 'function') {
604 const eventType = prop.replace('on', '').toLowerCase();
605 if ((eventType === 'domcontentloaded' && readyState !== 'loading') ||
606 (eventType === 'load' && readyState === 'complete')) {
607 setTimeout(() => fn.call(target, createEvent(eventType)), 0);
608 } else {
609 target.addEventListener(eventType, fn);
610 }
611 }
612 return descriptor.set ? descriptor.set.call(this, fn) : undefined;
613 },
614 get() {
615 return descriptor.get ? descriptor.get.call(this) : undefined;
616 },
617 configurable: true
618 });
619 });
620 })();
621
622 // Activation System
623 function activate() {
624 if (activated) return;
625 activated = true;
626 document.dispatchEvent(createTrackerEvent('activation'));
627
628 // Remove listeners
629 ['mousemove', 'click', 'scroll', 'keydown'].forEach(evt => {
630 window.removeEventListener(evt, activate, true);
631 });
632
633 processScripts();
634 setupObserver();
635 }
636
637 function processScripts() {
638 const scripts = Array.from(document.querySelectorAll('script[type=\"text/bwp-script\"]:not([data-earlyberqwp])'));
639 scripts.forEach(convertScript);
640 }
641
642 function setupObserver() {
643 new MutationObserver(mutations => {
644 mutations.forEach(mutation => {
645 mutation.addedNodes.forEach(node => {
646 if (node.tagName === 'SCRIPT' && node.type === 'text/bwp-script') {
647 convertScript(node);
648 }
649 });
650 });
651 }).observe(document, { childList: true, subtree: true });
652 }
653
654 // Script Conversion
655 function convertScript(original) {
656 if (original.dataset.processed) return;
657 original.dataset.processed = 'true';
658
659 const script = document.createElement('script');
660 const attrs = Array.from(original.attributes).filter(a => a.name !== 'type');
661 const id = `script-\${performance.now()}-\${Math.random().toString(36).slice(2)}`;
662
663 script.type = original.getAttribute('data-type');
664 try {
665 attrs.forEach(attr => script.setAttribute(attr.name, attr.value));
666 } catch (e) {
667 }
668 if (original.textContent) script.textContent = original.textContent;
669
670 pendingScripts.set(id, {
671 element: script,
672 src: script.src,
673 isExternal: !!script.src
674 });
675
676 document.dispatchEvent(createTrackerEvent('script-loading', {
677 scriptId: id,
678 element: script
679 }));
680
681 script.addEventListener('load', () => handleCompletion(id, true));
682 script.addEventListener('error', () => handleCompletion(id, false));
683
684 if (original.textContent) {
685 handleCompletion(id, true);
686 }
687
688 // script.async = false;
689 original.replaceWith(script);
690 }
691
692 function handleCompletion(id, success) {
693 const record = pendingScripts.get(id);
694 if (!record) return;
695
696 pendingScripts.delete(id);
697 completedCount++;
698
699 document.dispatchEvent(createTrackerEvent('script-complete', {
700 scriptId: id,
701 success,
702 duration: performance.now() - record.element.startTime
703 }));
704
705 if (pendingScripts.size === 0) {
706 document.dispatchEvent(createTrackerEvent('scripts-loaded', {
707 total: completedCount,
708 failed: completedCount - Array.from(pendingScripts.values())
709 .filter(r => r.success).length
710 }));
711 }
712 }
713
714 document.addEventListener('bwp:scripts-loaded', (e) => {
715 console.log('scripts loaded.')
716 window.dispatchEvent(new Event('berqwp_after_delay_js_loaded'));
717
718 });
719
720 // Initialization
721 activate();
722
723 window.addEventListener('berqwp_after_delay_js_loaded', function() {
724 let event = new Event('DOMContentLoaded', {
725 bubbles: true,
726 cancelable: true
727 });
728 document.dispatchEvent(event);
729 document.dispatchEvent(new Event('readystatechange'));
730 window.dispatchEvent(new Event('load'));
731
732
733 // Create a new resize event
734 var resizeEvent = new Event('resize');
735
736 // Dispatch the resize event
737 window.dispatchEvent(resizeEvent);
738
739
740 if (berq_click) {
741 setTimeout(function() {
742 console.log(berq_click);
743 const clickEvent = new MouseEvent('click', {
744 bubbles: true,
745 cancelable: false,
746 view: window
747 });
748 berq_click.dispatchEvent(clickEvent);
749 berq_click = null;
750 }, 500)
751 }
752
753 });
754 })();
755 }
756 </script>
757 ";
758
759 if ($this->settings['js_optimization'] == 'asynchronous') {
760 $script .= "
761 <script defer>
762 requestIdleCallback(() => {
763 berqwpLoadJs();
764 });
765 </script>
766 ";
767 }
768
769 if ($this->settings['js_optimization'] == 'delay') {
770 $script .= "
771 <script defer>
772 (function() {
773
774 let berqwpScriptTags = document.querySelectorAll('script[type=\"text/bwp-script\"]');
775 let jsUrls = Array.from(berqwpScriptTags).map(scriptTag => scriptTag.getAttribute('src')).filter(url => url);
776
777 function preloadJS(jsURLs) {
778 return window.bwpPreloader.preloadScripts();
779 }
780
781 let bwp_js_initialized = false;
782 function bwp_js_init(jsUrls) {
783 if (bwp_js_initialized) {
784 return;
785 }
786 bwp_js_initialized = true;
787
788 preloadJS(jsUrls)
789 .then(() => {
790 console.log('Preloading completed, invoking berqwpLoadJs...');
791 if (typeof berqwpLoadJs === 'function') {
792 berqwpLoadJs();
793 } else {
794 console.error('berqwpLoadJs is not defined or not a function.');
795 }
796 })
797 .catch(error => {
798 console.error('bwp_js_init failed during preloadJS:', error);
799 });
800 }
801
802 function berqwp_js_handleUserInteraction(event) {
803
804 if (event.type === 'click' || event.type === 'touchstart') {
805 berq_click = event.target;
806
807 // Traverse up the DOM to find the closest <a> ancestor
808 if (berq_click.closest('a')) {
809 berq_click = berq_click.closest('a');
810 }
811
812 // If click is done on a link
813 if (berq_click.tagName === 'A' && berq_click.href && /^https?:\/\//.test(berq_click.href)) {
814 console.log('Skipping JavaScript execution, a link was clicked.')
815 return;
816 }
817
818 console.log(event.type)
819
820 }
821
822 bwp_js_init(jsUrls);
823
824
825 // After running the function, remove all event listeners to ensure it runs only once
826 for (let eventType of berqwp_js_interactionEventTypes) {
827 window.removeEventListener(eventType, berqwp_js_handleUserInteraction);
828 }
829 }
830
831 let berqwp_js_interactionEventTypes = ['click', 'mousemove', 'keydown', 'touchstart', 'scroll', 'berqwpLoadJS', 'berqwp_interaction_event'];
832
833 for (let eventType of berqwp_js_interactionEventTypes) {
834 window.addEventListener(eventType, berqwp_js_handleUserInteraction, { passive: false });
835 }
836
837 })()
838 </script>
839 ";
840 }
841 }
842
843 return $script;
844 }
845
846 function optimize_js($buffer) {
847 $dom = HtmlDomParser::str_get_html($buffer);
848 $js_excludes = $this->settings['exclude_js'];
849 $js_excludes = array_map(function ($kw) {
850 return sanitize_text_field(trim($kw));
851 }, $js_excludes);
852
853 foreach ($dom->find('script') as $element) {
854 $script_src = $element->src;
855 $outerhtml = $element->outertext;
856
857 if (strpos($outerhtml, 'document.write(') !== false) {
858 continue;
859 }
860
861 if (strpos($outerhtml, '$zoho.salesiq = ') !== false) {
862 continue;
863 }
864
865 if (strpos($outerhtml, 'CRLeadStar.init') !== false) {
866 continue;
867 }
868
869 if (!empty($element->type) && $element->type !== 'text/javascript' && $element->type !== 'application/javascript' && $element->type !== 'module') {
870 continue;
871 }
872
873 if ($element->hasAttribute('data-berqwp-exclude')) {
874 continue;
875 }
876
877 // exclude script tags
878 if (!empty($js_excludes)) {
879 foreach ($js_excludes as $exclude_kw) {
880 if (strpos($outerhtml, $exclude_kw) !== false) {
881
882 if ($this->settings['defer_excluded_js'] && !$element->hasAttribute('async')) {
883 $element->setAttribute('defer', 'defer');
884 }
885
886 continue 2;
887 }
888 }
889 }
890
891 if ($this->settings['js_optimization'] == 'defer') {
892
893 if (!$element->hasAttribute('async')) {
894 $element->setAttribute('defer', 'defer');
895 }
896
897 continue;
898 }
899
900 if (empty($element->type) || $element->type == 'text/javascript') {
901 $element->setAttribute('data-type', 'text/javascript');
902 $element->type = 'text/bwp-script';
903 } else {
904 $element->setAttribute('data-type', esc_attr($element->type));
905 $element->type = 'text/bwp-script';
906 }
907
908 }
909
910 return (string) $dom;
911 }
912
913 function lazy_load_videos($buffer) {
914 $dom = HtmlDomParser::str_get_html($buffer);
915
916 foreach ($dom->find('video') as $element) {
917 $outerhtml = $element->outertext;
918 $parent = $element->parent();
919
920 if (strpos($outerhtml, 'video-js') !== false) {
921 continue;
922 }
923
924 if (strpos($outerhtml, 'mg_self-hosted-video') !== false) {
925 continue;
926 }
927
928 if (!empty($element->src)) {
929 $original_src = $element->src;
930 // $video->attr['data-berqwpsrc'] = $original_src;
931 $element->setAttribute('data-berqwpsrc', $original_src);
932 $element->removeAttribute('src');
933 }
934
935 // Handle each <source> element within the video tag
936 foreach ($element->find('source') as $source) {
937 if (!empty($source->src)) {
938 $original_src = $source->src;
939 // $source->attr['data-berqwpsrc'] = $original_src;
940 $source->setAttribute('data-berqwpsrc', $original_src);
941 $source->removeAttribute('src');
942 }
943 unset($source);
944 }
945
946 $class = $element->getAttribute('class');
947 $class .= ' berqwp-lazy-video';
948 $element->setAttribute('class', $class);
949 $element->setAttribute('preload', 'none');
950 // $element->attr[' preload'] = 'none';
951 }
952
953 return (string) $dom;
954 }
955
956 function lazy_load_images($buffer) {
957 $dom = HtmlDomParser::str_get_html($buffer);
958 $image_excludes = $this->settings['exclude_img_lazy_load'];
959 $image_excludes = array_map(function ($kw) {
960 return sanitize_text_field(trim($kw));
961 }, $image_excludes);
962
963 foreach ($dom->find('img') as $element) {
964
965 if (!$element->hasAttribute('src') || empty($element->src)) {
966 continue;
967 }
968
969 $img_src = $element->src;
970 $outerhtml = $element->outertext;
971 $img_path = self::url_to_path($img_src);
972
973 // exclude lazy load
974 if (!empty($image_excludes)) {
975 foreach ($image_excludes as $exclude_kw) {
976 if (strpos($outerhtml, $exclude_kw) !== false) {
977 continue 2;
978 }
979 }
980 }
981
982 if (strpos($img_src, 'data:') === 0) {
983 continue;
984 }
985
986 if (strpos($img_src, ';base64,') !== false) {
987 continue;
988 }
989
990 if (strpos($outerhtml, 'rs-lazyload') !== false) {
991 continue;
992 }
993
994 if (strpos($outerhtml, 'mfn-lazy') !== false) {
995 continue;
996 }
997
998 if (strpos($outerhtml, 'data-dbsrc=') !== false) {
999 continue;
1000 }
1001
1002 if (strpos($outerhtml, 'data-orig-src=') !== false) {
1003 continue;
1004 }
1005
1006 if (strpos($outerhtml, 'facebook.com') !== false) {
1007 continue;
1008 }
1009
1010 if (strpos($img_src, '${') !== false) {
1011 continue;
1012 }
1013
1014 // Skip smush lazy loading
1015 if (strpos($img_src, '--smush-placeholder-width') !== false) {
1016 continue;
1017 }
1018
1019 // wpbackery background image
1020 if (strpos($img_src, 'class="background-image"') !== false) {
1021 continue;
1022 }
1023
1024 // lscache lazy load
1025 if (strpos($img_src, 'class="lazyload"') !== false) {
1026 continue;
1027 }
1028
1029 // salient lazy load
1030 if (strpos($img_src, 'data-nectar-img-src') !== false) {
1031 continue;
1032 }
1033
1034 $width = $element->width;
1035 $height = $element->height;
1036
1037 if (!empty($img_path) && file_exists($img_path) && !$element->hasAttribute('width') && !$element->hasAttribute('height')) {
1038 list($width, $height) = getimagesize($img_path);
1039 }
1040
1041 $element->removeAttribute('src');
1042 $element->setAttribute('data-berqwpsrc', $img_src);
1043
1044 if ($element->hasAttribute('srcset')) {
1045 $srcset = $element->srcset;
1046 $element->removeAttribute('srcset');
1047 $element->setAttribute('data-berqwp-srcset', $srcset);
1048 }
1049
1050 if (!empty($width) && !empty($height)) {
1051 $svg = '<svg width="' . $width . '" height="' . $height . '" xmlns="http://www.w3.org/2000/svg" version="1.1">';
1052 $svg .= '<rect width="100%" height="100%" fill="none" />';
1053 $svg .= '</svg>';
1054
1055 $base64Svg = base64_encode($svg);
1056
1057 // Create data URI
1058 $ph_webp_url = 'data:image/svg+xml;base64,' . $base64Svg;
1059 } else {
1060 $ph_webp_url = 'data:image/gif;placeholder=MjQxOjM1NQ==-1;base64,R0lGODlhAQABAIABAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAICTAEAOw==';
1061 }
1062
1063 $element->setAttribute('src', $ph_webp_url);
1064 $element->setAttribute('decoding', 'async');
1065
1066 }
1067
1068 return (string) $dom;
1069 }
1070
1071 private function extract_template_scripts( $buffer ) {
1072 $placeholders = [];
1073
1074 $pattern = '/<script(?=[^>]*\btype=["\'](?:text\/template|text\/x-template|text\/x-handlebars-template|text\/x-handlebars|text\/ng-template)["\'])[^>]*>.*?<\/script>/is';
1075
1076 $result = preg_match_all( $pattern, $buffer, $matches );
1077
1078 if ( $result === false || $result === 0 ) {
1079 return [ $buffer, $placeholders ];
1080 }
1081
1082 foreach ( $matches[0] as $index => $script_block ) {
1083 $placeholder = '<!--BERQWP_TEMPLATE_' . $index . '-->';
1084 $placeholders[ $placeholder ] = $script_block;
1085 $buffer = str_replace( $script_block, $placeholder, $buffer );
1086 }
1087
1088 return [ $buffer, $placeholders ];
1089 }
1090
1091 private function restore_template_scripts( $buffer, $placeholders ) {
1092 if ( empty( $placeholders ) ) {
1093 return $buffer;
1094 }
1095
1096 foreach ( $placeholders as $placeholder => $original_block ) {
1097 $buffer = str_replace( $placeholder, $original_block, $buffer );
1098 }
1099
1100 return $buffer;
1101 }
1102
1103 function lazy_load_iframes($buffer) {
1104 $dom = HtmlDomParser::str_get_html($buffer);
1105
1106 foreach ($dom->find('iframe') as $element) {
1107 $outerhtml = $element->outertext;
1108 $outerhtml = apply_filters('photon_before_iframe_optimization', $outerhtml);
1109 $isyoutube = false;
1110
1111 if ($this->settings['preload_yt_poster']) {
1112
1113 if (preg_match('/youtube\.com\/embed\/([a-zA-Z0-9_-]+)/', $element->src, $matches)) {
1114 $videoId = $matches[1];
1115 $isyoutube = true;
1116
1117 // Generate the thumbnail URL
1118 $thumbnailUrl = "https://img.youtube.com/vi/{$videoId}/hqdefault.jpg";
1119 $highresthumbnailUrl = "https://img.youtube.com/vi/{$videoId}/maxresdefault.jpg";
1120
1121
1122 if (strpos($element->src, 'autoplay=1') !== false && strpos($element->src, 'mute=') === false) {
1123 $element->setAttribute('src', $element->src . "&mute=1");
1124 }
1125
1126 // Generate lazy-load HTML content for srcdoc
1127 $srcdocContent = <<<HTML
1128 <style>
1129 *{padding:0;margin:0;overflow:hidden}
1130 .play-button {
1131 width: 70px;
1132 position: absolute;
1133 top: 50%;
1134 left: 50%;
1135 transform: translate(-50%, -50%);
1136 cursor: pointer;
1137 }
1138 </style>
1139 <a href='{$element->src}' style='position: absolute; width: 100%; height: 100%; background: url({$thumbnailUrl}) no-repeat center center; background-size: cover;'>
1140 <picture>
1141 <source srcset='{$highresthumbnailUrl}' media='(min-width: 999px)'>
1142 <img src='{$thumbnailUrl}' style='position: absolute; width: 100%; height: 100%;object-fit: cover;'>
1143 </picture>
1144 <div class='play-button'>
1145 <svg height='100%' version='1.1' viewBox='0 0 68 48' width='100%'>><path class='ytp-large-play-button-bg' d='M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z' fill='#f00'></path><path d='M 45,24 27,14 27,34' fill='#fff'></path></svg>
1146 </div>
1147 </a>
1148 HTML;
1149
1150 // Update the iframe attributes
1151 $element->setAttribute('srcdoc', $srcdocContent);
1152
1153 $element->setAttribute('src', $element->src); // Remove immediate src loading
1154 }
1155
1156 if ($isyoutube && $element->hasAttribute('data-berqWPexclude')) {
1157 continue;
1158 }
1159
1160 }
1161
1162 if (strpos($outerhtml, 'youtube.com') !== false && !$element->hasAttribute('referrerpolicy')) {
1163 $element->setAttribute('referrerpolicy', 'strict-origin-when-cross-origin');
1164 }
1165
1166 if ($element->hasAttribute('data-berqWPexclude')) {
1167 continue;
1168 }
1169
1170 $class = $element->getAttribute('class');
1171
1172 if (!empty($class)) {
1173 $classes = explode(' ', $class ?? '');
1174 $classes = array_filter($classes, fn($c) => $c !== 'lazyload');
1175 $class = trim(implode(' ', $classes));
1176 $element->setAttribute('class', $class);
1177 }
1178
1179 if ($element->hasAttribute('data-src')) {
1180 $element->setAttribute('rm-src', $element->src);
1181 $element->removeAttribute('src');
1182 }
1183
1184 $outerhtml = esc_attr($element->outertext);
1185
1186 $element->outertext = '<div class="berqwp-lazy-youtube" data-embed="' . $outerhtml . '"></div>';
1187
1188 }
1189
1190 return (string) $dom;
1191 }
1192
1193 function store_cache($buffer) {
1194 // Define the cache directory
1195 $cache_directory = bwp_get_cache_dir();
1196 $url = $this->page_url;
1197
1198 // Create the cache directory if it doesn't exist
1199 if (!file_exists($cache_directory)) {
1200 mkdir($cache_directory, 0755, true);
1201 }
1202
1203 $cache = new Cache(null, bwp_get_cache_dir());
1204 $cache->store_cache($url, $buffer);
1205
1206
1207 do_action('berqwp_stored_page_cache', $this->page_slug);
1208
1209 global $berq_log;
1210 $berq_log->info("Stored cache for $url from PageOptimizer class");
1211 }
1212
1213 function preload_images($buffer) {
1214 $dom = HtmlDomParser::str_get_html($buffer);
1215 $count = 0;
1216 $candidates = [];
1217
1218 foreach ($dom->find('img') as $element) {
1219
1220 if ($count >= 9) {
1221 break;
1222 }
1223
1224 if (!$element->hasAttribute('src') || empty($element->src)) {
1225 continue;
1226 }
1227
1228 $img_src = $element->src;
1229 $outerhtml = $element->outertext;
1230 $img_path = self::url_to_path($img_src);
1231
1232 // exclude lazy load
1233 // if (!empty($image_excludes)) {
1234 // foreach ($image_excludes as $exclude_kw) {
1235 // if (strpos($outerhtml, $exclude_kw) !== false) {
1236 // continue 2;
1237 // }
1238 // }
1239 // }
1240
1241 if (strpos($img_src, 'data:') === 0) {
1242 continue;
1243 }
1244
1245 if (strpos($img_src, ';base64,') !== false) {
1246 continue;
1247 }
1248
1249 if (strpos($outerhtml, 'rs-lazyload') !== false) {
1250 continue;
1251 }
1252
1253 if (strpos($outerhtml, 'mfn-lazy') !== false) {
1254 continue;
1255 }
1256
1257 if (strpos($outerhtml, 'data-dbsrc=') !== false) {
1258 continue;
1259 }
1260
1261 if (strpos($outerhtml, 'data-orig-src=') !== false) {
1262 continue;
1263 }
1264
1265 if (strpos($outerhtml, 'facebook.com') !== false) {
1266 continue;
1267 }
1268
1269 $width = $element->width;
1270 $height = $element->height;
1271
1272 if (!empty($img_path) && file_exists($img_path) && !$element->hasAttribute('width') && !$element->hasAttribute('height')) {
1273 list($width, $height) = getimagesize($img_path);
1274 }
1275
1276 $candidate = [
1277 'width' => $width,
1278 'height' => $height,
1279 'area' => (int) $width * (int) $height,
1280 'src' => $img_src,
1281 ];
1282
1283 if ($element->hasAttribute('loading')) {
1284 $candidate['loading'] = $element->loading;
1285 }
1286
1287 if ($element->hasAttribute('srcset')) {
1288 $candidate['srcset'] = $element->srcset;
1289 }
1290
1291 if ($element->hasAttribute('sizes')) {
1292 $candidate['sizes'] = $element->sizes;
1293 }
1294
1295 $candidates[] = $candidate;
1296 $count++;
1297
1298
1299 }
1300
1301 $buffer = (string) $dom;
1302
1303 if (!empty($candidates)) {
1304
1305 $processed = [];
1306
1307 // unique src
1308 $candidates = array_filter($candidates, function ($item) use (&$processed) {
1309 if (in_array($item['src'], $processed)) {
1310 return false;
1311 }
1312
1313 $processed[] = $item['src'];
1314
1315 return true;
1316 });
1317
1318 usort($candidates, function ($a, $b) {
1319 return $b['area'] <=> $a['area']; // DESC
1320 });
1321
1322 $candidates = array_slice($candidates, 0, 2);
1323 $prelaod_html = '';
1324
1325 foreach ($candidates as $preload_img) {
1326 $prelaod_html .= '<link rel="preload" as="image" ';
1327 $prelaod_html .= ' fetchpriority="high" ';
1328 $prelaod_html .= 'href="'.$preload_img['src'].'" ';
1329
1330 if (!empty($preload_img['srcset'])) {
1331 $prelaod_html .= 'imagesrcset="'.$preload_img['srcset'].'" ';
1332 }
1333
1334 if (!empty($preload_img['sizes'])) {
1335 $prelaod_html .= 'imagesizes="'.$preload_img['sizes'].'" ';
1336 }
1337
1338 $prelaod_html .= '>'.PHP_EOL;
1339 }
1340
1341 $buffer = berqwp_prependHtmlToHead($buffer, $prelaod_html);
1342
1343 }
1344
1345
1346
1347 return $buffer;
1348 }
1349
1350 function preload_stylesheet($buffer) {
1351 $dom = HtmlDomParser::str_get_html($buffer);
1352
1353 foreach ($dom->find('link') as $element) {
1354
1355 if ($element->rel == 'stylesheet'){
1356 $element->setAttribute('rel', 'preload');
1357 $element->setAttribute('as', 'style');
1358 $element->removeAttribute('media');
1359 // $element->setAttribute('media', 'print');
1360 // $element->setAttribute('onload', "this.media='all'");
1361 $element->setAttribute('onload', "this.rel='stylesheet'");
1362 }
1363
1364 }
1365
1366 return (string) $dom;
1367
1368 }
1369
1370 function delay_styles($buffer) {
1371
1372 if ( !empty($this->settings['css_optimization']) && $this->settings['css_optimization'] !== 'delay' && $this->settings['css_optimization'] !== 'asynchronous' ) {
1373 return $buffer;
1374 }
1375
1376 $dom = HtmlDomParser::str_get_html($buffer);
1377
1378 $style_excludes = array_map(function ($kw) {
1379 return sanitize_text_field(trim($kw));
1380 }, $this->settings['exclude_css']);
1381
1382 foreach ($dom->find('link') as $element) {
1383
1384 $outerhtml = $element->outertext;
1385
1386 if ($element->rel == 'stylesheet' || $element->as == 'style'){
1387
1388 if (!$element->hasAttribute('href')) {
1389 continue;
1390 }
1391
1392 if ($element->hasAttribute('data-berqwp-exclude')) {
1393 continue;
1394 }
1395
1396 // exclude style
1397 if (!empty($style_excludes)) {
1398 foreach ($style_excludes as $exclude_kw) {
1399 if (!empty($exclude_kw) && strpos($outerhtml, $exclude_kw) !== false) {
1400 continue;
1401 }
1402 }
1403 }
1404
1405 $element->setAttribute('rel', 'stylesheet');
1406 $element->setAttribute('as', 'style');
1407 $element->setAttribute('data-berqwp-style-href', $element->href);
1408 $element->removeAttribute('href');
1409
1410 }
1411
1412 }
1413
1414 foreach ($dom->find('style') as $element) {
1415
1416 $outerhtml = $element->outertext;
1417
1418 if ($element->hasAttribute('data-berqwp-exclude')) {
1419 continue;
1420 }
1421
1422 // exclude style
1423 if (!empty($style_excludes)) {
1424 foreach ($style_excludes as $exclude_kw) {
1425 if (!empty($exclude_kw) && strpos($outerhtml, $exclude_kw) !== false) {
1426 continue;
1427 }
1428 }
1429 }
1430
1431 $element->setAttribute('type', 'text/berqwp-style');
1432 }
1433
1434 return (string) $dom;
1435
1436 }
1437
1438 function buffer_end($buffer) {
1439
1440 // $buffer = file_get_contents('/Users/hamzamairaj/Local Sites/plugin-berqwp/app/public/wp-content/plugins/searchpro/inc/photon/page.html');
1441
1442 if (empty($buffer)) {
1443 return $buffer;
1444 }
1445
1446 $buffer = $buffer.'<!-- Optimized with BerqWP\'s instant cache. --->';
1447
1448 $script = "
1449 <script defer>
1450 var comment = document.createComment(' This website is optimized using the BerqWP plugin. @".time()." ');
1451 document.documentElement.insertBefore(comment, document.documentElement.firstChild);
1452
1453 function isMobileDevice() {
1454 return /Mobi|Android|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i.test(navigator.userAgent);
1455 }
1456
1457 function astraHeaderClass() {
1458 if (isMobileDevice() && window.screen.width <= 999) {
1459 if ((document.body.classList.contains('theme-astra') || document.body.classList.contains('ast-page-builder-template')) && !document.body.classList.contains('ast-header-break-point')) {
1460 document.body.classList.add('ast-header-break-point');
1461 }
1462 }
1463 }
1464
1465 function divimobilemenu() {
1466 const divimenuele = document.querySelector('#et_mobile_nav_menu .mobile_menu_bar.mobile_menu_bar_toggle');
1467
1468 if (isMobileDevice() && divimenuele) {
1469 divimenuele.innerHTML = '<div class=\"dipi_hamburger hamburger hamburger--spring\"> <div class=\"hamburger-box\"> <div class=\"hamburger-inner\"></div> </div> </div>';
1470 }
1471 }
1472
1473 divimobilemenu();
1474 astraHeaderClass();
1475 window.addEventListener('resize', function() {
1476 astraHeaderClass();
1477 });
1478
1479 window.dispatchEvent(new Event('berqwp_js_initialized'));
1480 </script>
1481
1482 ";
1483
1484 [ $buffer, $template_placeholders ] = $this->extract_template_scripts( $buffer );
1485
1486 $buffer = $this->preload_images($buffer);
1487 // $buffer = $this->preload_stylesheet($buffer);
1488
1489 if ($this->settings['img_lazyloading']) {
1490 $buffer = $this->lazy_load_images($buffer);
1491 }
1492
1493 if ($this->settings['js_optimization'] == 'auto') {
1494 if ($this->settings['opt_mode'] == 'basic') {
1495 $this->settings['js_optimization'] = 'defer';
1496 }
1497
1498 if ($this->settings['opt_mode'] == 'medium') {
1499 $this->settings['js_optimization'] = 'asynchronous';
1500 }
1501
1502 if ($this->settings['opt_mode'] == 'blaze') {
1503 $this->settings['js_optimization'] = 'delay';
1504 }
1505
1506 if ($this->settings['opt_mode'] == 'aggressive') {
1507 $this->settings['js_optimization'] = 'delay';
1508 }
1509 }
1510
1511 if ($this->settings['js_optimization'] !== 'disable') {
1512 $buffer = $this->optimize_js($buffer);
1513 }
1514
1515 if ($this->settings['lazy_load_videos']) {
1516 $buffer = $this->lazy_load_videos($buffer);
1517 }
1518
1519 if ($this->settings['youtube_lazyloading']) {
1520 $buffer = $this->lazy_load_iframes($buffer);
1521 }
1522
1523 if ($this->settings['css_optimization'] == 'auto') {
1524 if ($this->settings['opt_mode'] == 'basic') {
1525 $this->settings['css_optimization'] = 'disable';
1526 }
1527
1528 if ($this->settings['opt_mode'] == 'medium') {
1529 $this->settings['css_optimization'] = 'asynchronous';
1530 }
1531
1532 if ($this->settings['opt_mode'] == 'blaze') {
1533 $this->settings['css_optimization'] = 'asynchronous';
1534 }
1535
1536 if ($this->settings['opt_mode'] == 'aggressive') {
1537 $this->settings['css_optimization'] = 'delay';
1538 }
1539 }
1540
1541 $enable_used_css = (bool) apply_filters(
1542 'berqwp_local_used_css',
1543 apply_filters('berqwp_local_critical_css', (bool) get_option('berqwp_enable_used_css'))
1544 );
1545
1546 if ($enable_used_css && $this->settings['css_optimization'] !== 'disable') {
1547 $critical_css = new berqUsedCSS(get_option('home'));
1548 $critical_css->forceInclude($this->settings['force_include_critical_css']);
1549 $critical_css = $critical_css->process_css($buffer);
1550 $critical_css = sprintf('<style data-berqwp-exclude id="berqwp-used-css">%s</style>', $critical_css);
1551
1552 $buffer = berqwp_prependHtmlToHead($buffer, $critical_css);
1553 $buffer = $this->delay_styles($buffer);
1554
1555 }
1556
1557
1558 $buffer = $this->restore_template_scripts( $buffer, $template_placeholders );
1559
1560 $script = apply_filters('berqwp_photon_before_closing_body', $script);
1561 $buffer = berqwp_appendHtmlToBody($buffer, $script);
1562
1563 $buffer = apply_filters( 'berqwp_cache_buffer', $buffer );
1564 $this->store_cache($buffer);
1565
1566 return $buffer;
1567 }
1568 }
1569