REDROOM
PHP 8.2.31
Path:
Logout
Edit File
Size: 89.70 KB
Close
/home/nshryvcy/blissfulnepal.com/wp-content/plugins/airlift/buffer/optimizer.php
Text
Base64
<?php require_once dirname(__FILE__) . '/helper.php'; require_once dirname(__FILE__) . '/../wp_db.php'; if (!class_exists('ALOptimizer')) : class ALOptimizer { public static $airlift_stats_table = 'airlift_stats'; public static $airlift_config_table = 'airlift_config'; public $config; public $db; public $image_config; public $alinfo; public function __construct($config, $alinfo) { $this->config = $config; $this->db = new ALWPDb(); $this->alinfo = $alinfo; } public function addOptimizationInfo($template_config_id, $page_audits) { $values = array( "path" => ALCacheHelper::getRequestURIBase(), "query_string" => ALCacheHelper::getQueryString(), "time" => time(), "page_audits" => serialize($page_audits), "template_config_id" => $template_config_id ); $this->db->replaceIntoBVTable(self::$airlift_stats_table, $values); } public function getHtmlDataToReplace($template_config) { if (array_key_exists('html_contents_to_replace', $template_config) && !empty($template_config['html_contents_to_replace'])) { return $template_config['html_contents_to_replace']; } return array(); } public function matchingUrlConfig($rows) { global $wp; $reversedRows = array_reverse($rows); foreach($reversedRows as $row) { if (home_url($wp->request) == $row['url']) { return ['id' => $row['id']]; } } return false; } public function getTemplateforSimilarPosts($configs) { $post_id = get_the_ID(); foreach ($configs as $row) { if (!isset($row['similar_post_ids'])) continue; $similar_post_ids = json_decode($row['similar_post_ids']); if (!is_array($similar_post_ids)) continue; foreach ($similar_post_ids as $similar_post_id) { if ($post_id === $similar_post_id) { $output = ['id' => $row['id']]; if (array_key_exists("enable_used_css_on_similar_posts", $this->config) && true == $this->config["enable_used_css_on_similar_posts"]) { $output["apply_used_css_on_similar_posts"] = true; } return $output; } } } return false; } public function isAggregatedStyleTagsMd5Matching($buffer, $config) { if (array_key_exists('agg_style_tags_md5', $config)) { $matched_style_tags = preg_match_all('/<style\b[^>]*>(.*?)<\/style>/is', $buffer, $style_tags); $matched_link_tags = preg_match_all('/<link[^>]*rel\s*=\s*["\']\s*stylesheet\s*["\'][^>]*>/iU', $buffer, $link_tags); $agg_style_tags_md5 = $config['agg_style_tags_md5']; $agg_css_hash = []; if ($matched_style_tags) { foreach ($style_tags[0] as $style) { $agg_css_hash[] = array($style, strpos($buffer, $style)); } } if ($matched_link_tags) { foreach ($link_tags[0] as $link) { $agg_css_hash[] = array($link, strpos($buffer, $link)); } } if(empty($agg_css_hash)) { return true; } usort($agg_css_hash, function($a, $b) { return $a[1] <=> $b[1]; }); $agg_css_array = []; foreach ($agg_css_hash as $css) { $agg_css_array[] = $css[0]; } $agg_css_array = implode($agg_css_array); $aggStyleMd5Hash = md5($agg_css_array); return ($agg_style_tags_md5 == $aggStyleMd5Hash); } return false; } public function isAggregatedScriptTagsMd5Matching($buffer, $config) { if (array_key_exists('agg_script_tags_md5', $config)) { $matched_script_tags = preg_match_all('/<script[^>]*>[\s\S]*?<\/script>/is', $buffer, $scripts); if (!$matched_script_tags) { return true; } $agg_script_string = implode($scripts[0]); $aggScriptMd5Hash = md5($agg_script_string); $agg_script_tags_md5 = $config['agg_script_tags_md5']; return $aggScriptMd5Hash == $agg_script_tags_md5; } return false; } public function getDefaultConfig($configs) { if (isset($this->config['disable_default_template_optimization']) && !$this->config['disable_default_template_optimization']) { foreach ($configs as $row) { if (isset($row['category_value']) && $row['category_value'] === 'home' && isset($row['id'])) { return ['id' => $row['id'], 'is_default_config' => true]; } } } return false; } public function findTemplateConfig() { $table = $this->db->getBVTable(self::$airlift_config_table); $sql = "SELECT id, url, similar_post_ids, category, category_value FROM $table"; $rows = $this->db->getResult($sql, ARRAY_A); if (!is_array($rows) || empty($rows)) { return false; } $match = $this->getMatchedConfigId($rows); if (!$match || !isset($match['id'])) { return null; } $id = intval($match['id']); $sql = "SELECT * FROM $table WHERE id = $id LIMIT 1"; $result = $this->db->getResult($sql, ARRAY_A); if (!$result || !isset($result[0])) { return null; } $config = json_decode($result[0]['config'], true); if (!is_array($config)) { return null; } if (!empty($match['url_matched'])) { $config['url_matched'] = true; } if (!empty($match['is_default_config'])) { $config['is_default_config'] = true; } if (!empty($match['apply_used_css_on_similar_posts'])) { $config['apply_used_css_on_similar_posts'] = true; } return $config; } private function getMatchedConfigId($rows) { $_config = $this->matchingUrlConfig($rows); if (is_array($_config) && isset($_config['id'])) { return ['id' => $_config['id'], 'url_matched' => true]; } if (array_key_exists("apply_non_template_optimization", $this->config) && false === $this->config["apply_non_template_optimization"]) { return null; } $_config = $this->getTemplateforSimilarPosts($rows); if (is_array($_config) && isset($_config['id'])) { $out = ['id' => $_config['id']]; if (!empty($_config['apply_used_css_on_similar_posts'])) { $out['apply_used_css_on_similar_posts'] = true; } return $out; } $_config = $this->getDefaultConfig($rows); if (is_array($_config) && isset($_config['id'])) { return ['id' => $_config['id'], 'is_default_config' => true]; } return null; } public function moveMetaTagsToTop($buffer) { // Use config-driven regexes so behavior can be tuned from server $head_block_pattern = isset($this->config['extract_head_block_regex']) ? $this->config['extract_head_block_regex'] : '~<head\b[^>]*>(.*?)<\/head>~is'; $opening_head_tag_pattern = isset($this->config['extract_opening_head_tag_regex']) ? $this->config['extract_opening_head_tag_regex'] : '/<head\b[^>]*>/iU'; $meta_pattern = isset($this->config['extract_meta_tag_regex']) ? $this->config['extract_meta_tag_regex'] : '/<meta\b[^>]*>/i'; if (!preg_match($head_block_pattern, $buffer, $head_matches)) { return $buffer; // No head block found } $full_head_block = $head_matches[0]; // Full <head>...</head> $head_opening_tag = ''; $head_content = $head_matches[1]; // Content inside head tags // Extract the opening head tag if (preg_match($opening_head_tag_pattern, $full_head_block, $head_tag_matches)) { $head_opening_tag = $head_tag_matches[0]; } // Find all meta tags within the head content only if (!preg_match_all($meta_pattern, $head_content, $meta_matches)) { return $buffer; // No meta tags in head } // Remove meta tags from head content $head_content_without_metas = ALHelper::safePregReplace($meta_pattern, '', $head_content); // Create new head content with meta tags at the top $meta_tags = implode("\n", $meta_matches[0]); $new_head_content = "\n" . $meta_tags . "\n" . $head_content_without_metas; // Reconstruct the head block $new_head_block = $head_opening_tag . $new_head_content . '</head>'; // Replace the original head block with the new one using exact string replace $buffer = ALHelper::safeStrReplace($full_head_block, $new_head_block, $buffer); return $buffer; } public function extractAndMatchJavascripts($buffer, $js_config_hash) { if (!isset($this->config['extract_js_regex'])) { return false; } if (array_key_exists("js_config", $js_config_hash) && is_array($js_config_hash['js_config'])) { $js_config = $js_config_hash["js_config"]; } else { return $buffer; } $extract_js_regex = $this->config['extract_js_regex']; $result = preg_match_all($extract_js_regex, $buffer, $script_matches); if ($result === false) { return false; } $extracted_scripts = $script_matches[0]; $remaining_scripts = array(); $regex_matched_scripts = array(); $all_matched_configs_are_delayed = true; // Start with true, set to false if any non-delayed config found foreach ($extracted_scripts as $script) { $stripped_script = preg_replace('/\s+/', '', $script); if (preg_match('~<script.*?type=[\'"](.*?)[\'"]~is', $script, $type_value)) { if (isset($js_config_hash['js_types_to_ignore']) && is_array($js_config_hash['js_types_to_ignore'])) { if (!empty($type_value[1]) && in_array($type_value[1], $js_config_hash['js_types_to_ignore'])) { continue; } } } if (isset($js_config_hash['js_attributes_to_ignore']) && is_array($js_config_hash['js_attributes_to_ignore'])) { foreach ($js_config_hash['js_attributes_to_ignore'] as $excluded_attribute) { if (strpos($script, $excluded_attribute) !== false) { continue 2; } } } $match_found = false; foreach ($js_config as $config) { $search_for = isset($config['search_for']) ? $config['search_for'] : ''; $stripped_search_for = preg_replace('/\s+/', '', $search_for); $search_for_regex = isset($config['search_for_regex']) ? $config['search_for_regex'] : ''; if (!empty($stripped_search_for) && !empty($stripped_script)) { if (strpos($stripped_script, $stripped_search_for) !== false) { $match_found = true; // Check if this matching config is delayed if (!isset($config['is_delayed']) || $config['is_delayed'] !== true) { $all_matched_configs_are_delayed = false; } break; } } else if (!empty($search_for) && strpos($script, $search_for) !== false) { $match_found = true; // Check if this matching config is delayed if (!isset($config['is_delayed']) || $config['is_delayed'] !== true) { $all_matched_configs_are_delayed = false; } break; } if (!empty($search_for_regex) && preg_match($search_for_regex, $script)) { $match_found = true; // Check if this matching config is delayed if (!isset($config['is_delayed']) || $config['is_delayed'] !== true) { $all_matched_configs_are_delayed = false; } $regex_matched_scripts[] = $script; break; } } if (!$match_found) { $remaining_scripts[] = $script; } } return array( 'remaining_scripts' => $remaining_scripts, 'regex_matched_scripts' => $regex_matched_scripts, 'all_matched_configs_are_delayed' => $all_matched_configs_are_delayed ); } public function extractHeadTag($buffer) { $match_present = ALHelper::safePregMatch('/<head\b[^>]*>/iU', $buffer, $head_tags); if ($match_present) { return $head_tags[0]; } return "<head>"; } public function removeCommentsPreserveScripts($buffer) { $script_pattern = '/<script[^>]*>([\s\S]*?)<\/script>/i'; preg_match_all($script_pattern, $buffer, $script_matches); $temp_buffer = $buffer; $placeholder_map = []; foreach ($script_matches[0] as $index => $script) { $placeholder = "SCRIPT_PLACEHOLDER_" . wp_rand(1000000, 9999999) . "_"; $placeholder_map[$placeholder] = $script; $temp_buffer = ALHelper::safeStrReplace($script, $placeholder, $temp_buffer); } $comment_regex = isset($this->config['comment_removal_regex']) ? $this->config['comment_removal_regex'] : '/<!--(.*)-->/Uis'; $temp_buffer = ALHelper::safePregReplace($comment_regex, '', $temp_buffer); foreach ($placeholder_map as $placeholder => $script) { $temp_buffer = ALHelper::safeStrReplace($placeholder, $script, $temp_buffer); } return $temp_buffer; } /** * Preserve script tags by replacing them with placeholders * Returns the modified buffer and a placeholder map for restoration */ public function preserveScriptTags($buffer) { $script_pattern = '/<script[^>]*>[\s\S]*?<\/script>/i'; preg_match_all($script_pattern, $buffer, $script_matches); $placeholder_map = []; $temp_buffer = $buffer; foreach ($script_matches[0] as $index => $script) { $placeholder = "BV_SCRIPT_PRESERVE_" . wp_rand(1000000, 9999999) . "_" . $index . "_"; $placeholder_map[$placeholder] = $script; $temp_buffer = ALHelper::safeStrReplace($script, $placeholder, $temp_buffer); } return array( 'buffer' => $temp_buffer, 'placeholder_map' => $placeholder_map ); } /** * Restore script tags from placeholders */ public function restoreScriptTags($buffer, $placeholder_map) { foreach ($placeholder_map as $placeholder => $script) { $buffer = ALHelper::safeStrReplace($placeholder, $script, $buffer); } return $buffer; } public function cleanBuffer($regexes, $buffer) { $cleaned_buffer = $buffer; if (isset($regexes) && is_array($regexes)) { foreach ($regexes as $regex) { $cleaned_buffer = ALHelper::safePregReplace($regex, '', $cleaned_buffer); } } return $cleaned_buffer; } public function extractAndReplaceStyles($buffer, $css_config, $modify_buffer = true) { if (!isset($this->config['extract_css_regex'])) { return array( 'remaining_styles' => array(), 'modified_buffer' => $buffer, 'regex_matched_styles' => array() ); } // Preserve script tags to avoid matching link/style tags inside JavaScript strings $preserved = $this->preserveScriptTags($buffer); $temp_buffer = $preserved['buffer']; $placeholder_map = $preserved['placeholder_map']; $extract_css_regex = $this->config['extract_css_regex']; preg_match_all($extract_css_regex, $temp_buffer, $style_matches); $extracted_styles = $style_matches[0]; $remaining_styles = array(); $modified_buffer = $temp_buffer; $regex_matched_styles = array(); foreach ($extracted_styles as $style) { $stripped_style = preg_replace('/\s+/', '', $style); $match_found = false; $replacement = ''; foreach ($css_config as $config) { $search_for = isset($config['search_for']) ? $config['search_for'] : ''; $stripped_search_for = preg_replace('/\s+/', '', $search_for); $search_for_regex = isset($config['search_for_regex']) ? $config['search_for_regex'] : ''; if (!empty($stripped_search_for) && !empty($stripped_style)) { if (strpos($stripped_style, $stripped_search_for) !== false) { $match_found = true; $replacement = isset($config['replace_with']) ? $config['replace_with'] : ''; break; } } else if (!empty($search_for) && strpos($style, $search_for) !== false) { $match_found = true; $replacement = isset($config['replace_with']) ? $config['replace_with'] : ''; break; } if (!empty($search_for_regex) && preg_match($search_for_regex, $style)) { $match_found = true; $replacement = isset($config['replace_with']) ? $config['replace_with'] : ''; $regex_matched_styles[] = $style; break; } } if ($match_found) { if ($modify_buffer) { $modified_buffer = ALHelper::safeStrReplace($style, $replacement, $modified_buffer); } } else { $remaining_styles[] = $style; } } // Restore script tags before returning $modified_buffer = $this->restoreScriptTags($modified_buffer, $placeholder_map); return array( 'remaining_styles' => $remaining_styles, 'modified_buffer' => $modified_buffer, 'regex_matched_styles' => $regex_matched_styles ); } public function optimizeBuffer($buffer) { $template_config = $this->findTemplateConfig(); if (is_array($template_config) && !empty($template_config)) { if (!isset($template_config["version"]) || $template_config["version"] !== ALInfo::AL_CONF_VERSION) { return $buffer; } $buffer = $this->removeCommentsPreserveScripts($buffer); $buffer = ALHelper::safePregReplace( '#<noscript>.*?</noscript>#ism', '', $buffer ); $html_data_to_replace = $this->getHtmlDataToReplace($template_config); $url_matched = null; $page_audits = array(); $is_default_config = false; if (array_key_exists('is_default_config', $template_config) && true == $template_config['is_default_config']) { $is_default_config = true; } $apply_used_css_on_similar_posts = null; if (array_key_exists('url_matched', $template_config) && $template_config['url_matched']) { $url_matched = $template_config['url_matched']; } if (array_key_exists('apply_used_css_on_similar_posts', $template_config) && $template_config['apply_used_css_on_similar_posts']) { $apply_used_css_on_similar_posts = $template_config['apply_used_css_on_similar_posts']; } if (array_key_exists("web_worker_scripts", $template_config)) { $buffer = $this->addJavascripts($buffer, $template_config["web_worker_scripts"]); } if (array_key_exists("javascript", $template_config) && is_array($template_config['javascript'])) { $this->processJavascriptOptimization($buffer, $template_config, $is_default_config, $page_audits); } if (array_key_exists("javascripts_to_add_before_delay", $template_config)) { $buffer = $this->addJavascripts($buffer, $template_config["javascripts_to_add_before_delay"]); } if ($this->shouldApplyUsedCss($template_config, $url_matched, $apply_used_css_on_similar_posts)) { $this->processUsedCssOptimization($buffer, $template_config, $page_audits); } elseif ($this->shouldApplyCriticalCss($template_config)) { $this->processCriticalCssOptimization($buffer, $template_config, $page_audits); if (array_key_exists("critical_css_replace_rules", $template_config) && is_array($template_config['critical_css_replace_rules'])) { $buffer = $this->doSearchAndReplace($buffer, $template_config['critical_css_replace_rules']); } } else { if ($this->isDefaultConfig($template_config)) { if (array_key_exists("stylesheet", $template_config) && true === $template_config['can_defer_other_pages_stylesheets']) { $buffer = $this->handleStylesheet($buffer, $template_config["stylesheet"]); if ($this->shouldApplyImageReplacementRules($template_config)) { $buffer = $this->handleImagesInInlineStyle($buffer, $template_config['image_replace_rules']); } } } } if (array_key_exists("dyn_images", $html_data_to_replace) && is_array($html_data_to_replace["dyn_images"])) { $buffer = $this->doSearchAndReplace($buffer, $html_data_to_replace['dyn_images']); } if (array_key_exists("preloaded_assets", $html_data_to_replace) && is_array($html_data_to_replace["preloaded_assets"])) { $buffer = $this->doSearchAndReplace($buffer, $html_data_to_replace["preloaded_assets"]); } if (array_key_exists("font", $template_config)) { $buffer = $this->handleFonts($buffer, $template_config["font"]); } if (array_key_exists("image", $template_config)) { $this->image_config = $template_config["image"]; $buffer = $this->handleImages($buffer, $template_config["image"], $url_matched); } if (array_key_exists("iframe", $template_config)) { $buffer = $this->handleIframes($buffer, $template_config["iframe"]); } if (array_key_exists("video", $template_config) && is_array($template_config["video"])) { $buffer = $this->doSearchAndReplace($buffer, $template_config["video"]); } if (array_key_exists("meta", $html_data_to_replace) && is_array($html_data_to_replace["meta"])) { $buffer = $this->doSearchAndReplace($buffer, $html_data_to_replace['meta']); } if (array_key_exists("search_and_replace_in_html", $this->config) && is_array($this->config['search_and_replace_in_html'])) { $buffer = $this->doSearchAndReplace($buffer, $this->config['search_and_replace_in_html']); } if (array_key_exists("id", $template_config)) { if ($this->alinfo->hasValidDBVersion()) { $this->addOptimizationInfo($template_config["id"], $page_audits); } } $buffer = $this->moveMetaTagsToTop($buffer); if (array_key_exists("footer_message", $template_config)) { $currentTime = gmdate("Y-m-d H:i:s"); $buffer = $buffer . '<!-- ' . $template_config["footer_message"] . ', Cached Timestamp: ' . $currentTime . ' UTC -->'; } } else { if (array_key_exists("footer_message", $this->config)) { $currentTime = gmdate("Y-m-d H:i:s"); $buffer = $buffer . '<!-- ' . $this->config["footer_message"] . ', Cached Timestamp: ' . $currentTime . ' UTC -->'; } } return $buffer; } private function getLinkAttributes($linkTag) { if (!is_string($linkTag) || trim($linkTag) === '') { return []; } if (!isset($this->config['extract_css_attr_regex'])) { return []; } $attr_regex = $this->config['extract_css_attr_regex']; // Reuse existing regex $attributes = []; $ignoredAttributes = isset($this->config['strict_link_attrs']) ? $this->config['strict_link_attrs'] : array(); if (preg_match('/<link\b(?<attrs>[^>]*?)(?:\s*\/)?>/i', $linkTag, $matches)) { $attrString = $matches['attrs']; if (preg_match_all($attr_regex, $attrString, $matches, PREG_SET_ORDER) && is_array($matches) && !empty($matches)) { foreach ($matches as $match) { $name = $match['name']; if (in_array($name, $ignoredAttributes)) { continue; } $value = !empty($match['value1']) ? $match['value1'] : (!empty($match['value2']) ? $match['value2'] : (!empty($match['value3']) ? $match['value3'] : (!empty($match['value4']) ? $match['value4'] : ''))); $attributes[$name] = $value; } } } return $attributes; } public function modifyExternalStylesheets($buffer, $css_config) { if (!isset($this->config['extract_css_regex'])) { return $buffer; } // Preserve script tags to avoid matching link tags inside JavaScript strings $preserved = $this->preserveScriptTags($buffer); $temp_buffer = $preserved['buffer']; $placeholder_map = $preserved['placeholder_map']; $styleAttrs = array(); $matched = preg_match_all($this->config['extract_css_regex'], $temp_buffer, $matches); if ($matched) { foreach ($matches[0] as $link_tag) { $attributes = $this->getLinkAttributes($link_tag); if (isset($attributes['id']) && strpos($attributes['id'], 'bv-') === 0) { continue; } $original_href = isset($attributes['href']) ? $attributes['href'] : ''; $new_href = $original_href; $should_remove = false; $stripped_link_tag = preg_replace('/\s+/', '', $link_tag); foreach ($css_config as $config) { $search_for = isset($config['search_for']) ? $config['search_for'] : ''; $stripped_search_for = preg_replace('/\s+/', '', $search_for); $search_for_regex = isset($config['search_for_regex']) ? $config['search_for_regex'] : ''; $replace_with = isset($config['replace_with']) ? $config['replace_with'] : ''; // Check if this link should be removed (replace_with is empty string) $is_match = false; if (!empty($stripped_search_for) && !empty($stripped_link_tag)) { if (strpos($stripped_link_tag, $stripped_search_for) !== false) { $is_match = true; } } else if (!empty($search_for) && strpos($link_tag, $search_for) !== false) { $is_match = true; } else if (!empty($search_for_regex) && preg_match($search_for_regex, $link_tag)) { $is_match = true; } if ($is_match) { if ($replace_with === '') { $should_remove = true; } else { $replace_attributes = $this->getLinkAttributes($replace_with); $new_href = isset($replace_attributes['href']) ? $replace_attributes['href'] : $original_href; } break; } } // If should_remove is true, remove the link tag entirely if ($should_remove) { $temp_buffer = str_replace($link_tag, '', $temp_buffer); continue; } $template_id = ALCacheHelper::randString(20); $template_tag = '<template id="' . $template_id . '"></template>'; $attributes['href'] = $new_href; $styleAttrs[] = array( "attrs" => $attributes, "bv_unique_id" => $template_id ); $temp_buffer = str_replace($link_tag, $template_tag, $temp_buffer); } } // Restore script tags before further processing $buffer = $this->restoreScriptTags($temp_buffer, $placeholder_map); if (!empty($styleAttrs)) { $head_tag = $this->extractHeadTag($buffer); $styleAttrsJson = json_encode($styleAttrs); $styleAttrsScript = "<script id=\"bv-dl-styles-list\" data-cfasync=\"false\" bv-exclude=\"true\">\nvar linkStyleAttrs = $styleAttrsJson;\n</script>"; $buffer = ALHelper::safePregReplace( '/' . preg_quote($head_tag, '/') . '/', $head_tag . "\n" . $styleAttrsScript, $buffer, 1 ); } return $buffer; } public function modifyInlineStyles($buffer) { if (!isset($this->config['extract_style_regex'])) { return $buffer; } // Preserve script tags to avoid matching style tags inside JavaScript strings $preserved = $this->preserveScriptTags($buffer); $temp_buffer = $preserved['buffer']; $placeholder_map = $preserved['placeholder_map']; $res = preg_match_all($this->config['extract_style_regex'], $temp_buffer, $style_matches); if (!$res) { return $buffer; } foreach ($style_matches[0] as $style) { preg_match('~(<style\b[^>]*>)(.*?)<\/style>~is', $style, $style_attrs); $opening_tag = $style_attrs[1]; $content = $style_attrs[2]; $opening_tag = preg_replace('/\stype=(?:"[^"]*"|\'[^\']*\'|[^\s>]+)/i', '', $opening_tag); // we should send type capturing regex from rails side $opening_tag = str_replace('>', ' type="bv_inline_delayed_css">', $opening_tag); $new_style = $opening_tag . $content . '</style>'; $temp_buffer = str_replace($style, $new_style, $temp_buffer); } // Restore script tags before returning return $this->restoreScriptTags($temp_buffer, $placeholder_map); } public function handleIframes($buffer, $iframe_config) { if (!array_key_exists("has_iframe", $iframe_config) || $iframe_config["has_iframe"] != TRUE) { return $buffer; } $iframes_matched = preg_match_all('#<iframe(?<attrs>\s.+)>.*</iframe>#iUs', $buffer, $iframes, PREG_SET_ORDER); if (!$iframes_matched) { return $buffer; } foreach($iframes as $iframe) { $src_matched = ALHelper::safePregMatch('#\ssrc\s*=\s*(\'|")(?<src>.*)\1#iUs', $iframe['attrs'], $attrs); if (!$src_matched) { continue; } if ($this->hasExcludedIframeAttrs($iframe)){ continue; } $iframe['src'] = trim($attrs['src']); if ($iframe['src'] === '') { continue; } if (esc_url($iframe['src']) === '') { continue; } $lazyloaded_iframe = $iframe[0]; $lazyloaded_iframe = $this->lazyloadIframe($iframe, $lazyloaded_iframe); if (preg_match('#<iframe[^>]*>#is', $lazyloaded_iframe, $opening_tag_match)) { $original_opening_tag = $opening_tag_match[0]; $modified_opening_tag = $this->addBvClass($original_opening_tag, 'bv-lazyload-iframe'); $lazyloaded_iframe = ALHelper::safeStrReplace($original_opening_tag, $modified_opening_tag, $lazyloaded_iframe); } else { // Fallback to old method if regex fails (should not happen) $lazyloaded_iframe = $this->addBvClass($lazyloaded_iframe, 'bv-lazyload-iframe'); } $lazyloaded_iframe .= '<noscript>' . $iframe[0] . '</noscript>'; $buffer = ALHelper::safeStrReplace($iframe[0], $lazyloaded_iframe, $buffer); } return $buffer; } public function lazyloadIframe($iframe, $lazyloaded_iframe) { $parsed_url = parse_url($iframe['src'], PHP_URL_HOST); if ($parsed_url !== false && isset($this->config['add_iframe_host']) && $this->config['add_iframe_host'] == TRUE) { $hostname = ''; if (!empty($parsed_url)) { $hostname = $parsed_url; } $final_attrs = ALHelper::safeStrReplace($iframe['src'], 'about:blank;' . $hostname, $iframe['attrs']); } else { $final_attrs = ALHelper::safeStrReplace($iframe['src'], 'about:blank', $iframe['attrs']); } $lazyloaded_iframe = ALHelper::safeStrReplace($iframe['attrs'], $final_attrs . ' bv-data-src="' . esc_url($iframe['src']) . '" ', $lazyloaded_iframe); return $lazyloaded_iframe; } public function doSearchAndReplace($buffer, $search_and_replace_info) { foreach ($search_and_replace_info as $config) { if (array_key_exists("search_for", $config) && array_key_exists("replace_with", $config)) { if ($config["search_for"] === '</body>') { $buffer = $this->replaceLastBodyTag($buffer, $config['replace_with']); } else { $buffer = ALHelper::safeStrReplace($config["search_for"], $config["replace_with"], $buffer); } } } return $buffer; } public function addCriticalCssV2($buffer, $critical_css_v2) { if (ALHelper::safePregMatch('/<head\b[^>]*>/iU', $buffer, $matches)) { $head_tag = $matches[0]; $replacement = $head_tag . "\n" . $critical_css_v2 . "\n"; $buffer = ALHelper::safePregReplace('/' . preg_quote($head_tag, '/') . '/', $replacement, $buffer, 1); } return $buffer; } public function addCriticalCss($buffer, $critical_css) { if (ALHelper::safePregMatch('/<head\b[^>]*>/iU', $buffer, $matches)) { $head_tag = $matches[0]; $replacement = $head_tag . "\n<style id='bv-critical-css'>" . $critical_css . "</style>\n"; $buffer = ALHelper::safePregReplace( '/' . preg_quote($head_tag, '/') . '/', $replacement, $buffer, 1 ); } return $buffer; } public function addUsedCss($buffer, $used_css) { if (ALHelper::safePregMatch('/<head\b[^>]*>/iU', $buffer, $matches)) { $head_tag = $matches[0]; $replacement = $head_tag . "\n" . $used_css . "\n"; $buffer = ALHelper::safePregReplace( '/' . preg_quote($head_tag, '/') . '/', $replacement, $buffer, 1 ); } return $buffer; } public function preloadImages($buffer, $preload_images) { $head_matched = ALHelper::safePregMatch('/<head\b[^>]*>/iU', $buffer, $matches); if ($head_matched) { $head_tag = $matches[0]; foreach ($preload_images as $image_to_preload) { $replacement = $head_tag . "\n" . $image_to_preload; $buffer = ALHelper::safePregReplace( '/' . preg_quote($head_tag, '/') . '/', $replacement, $buffer, 1 ); } } return $buffer; } private function hasEmbeddedImage($image) { return strpos($image, 'data:image') !== false; } public function hasNoActionAttribute($element) { if (!array_key_exists('no_action_attributes', $this->config) && !is_array($this->config['no_action_attributes'])) { return false; } foreach ($this->config['no_action_attributes'] as $excluded_attr) { if (strpos($element, $excluded_attr) !== false) { return true; } } return false; } private function hasIgnoredAttribute($image) { return strpos($image, 'data:image') !== false; } public function handleUsedCss($buffer, $used_css, $used_css_replace_rules) { if (isset($used_css) && !empty($used_css)) { $buffer = $this->addUsedCss($buffer, $used_css); $buffer = $this->doSearchAndReplace($buffer, $used_css_replace_rules); } return $buffer; } public function handleImagesInInlineStyle($buffer, $image_replace_rules) { if (!isset($image_replace_rules) || empty($image_replace_rules) || !isset($this->config['extract_style_regex'])) { return $buffer; } $res = preg_match_all($this->config['extract_style_regex'], $buffer, $style_matches); if (!$res) { return $buffer; } foreach ($style_matches[0] as $original_style) { $modified_style = $original_style; foreach ($image_replace_rules as $config) { if (array_key_exists("search_for", $config) && array_key_exists("replace_with", $config)) { $modified_style = ALHelper::safeStrReplace($config["search_for"], $config["replace_with"], $modified_style); } } if ($modified_style !== $original_style) { $buffer = ALHelper::safeStrReplace($original_style, $modified_style, $buffer); } } return $buffer; } /** * Determines if Used CSS should be applied based on configuration and matching conditions */ private function shouldApplyUsedCss($template_config, $url_matched, $apply_used_css_on_similar_posts) { return array_key_exists('used_css', $template_config) && ($url_matched || $apply_used_css_on_similar_posts); } private function isDefaultConfig($template_config) { return array_key_exists('is_default_config', $template_config) && true === $template_config['is_default_config']; } /** * Determines if Critical CSS optimization should be applied */ private function shouldApplyCriticalCss($template_config) { if ($this->isDefaultConfig($template_config)) { if (empty($template_config['can_apply_critical_css_on_other_pages']) || false === $template_config['can_apply_critical_css_on_other_pages']) { return false; } } elseif (!array_key_exists('url_matched', $template_config) || false === $template_config['url_matched']) { if (empty($template_config['apply_critical_css_on_similar_posts']) || false === $template_config['apply_critical_css_on_similar_posts']) { return false; } } return (array_key_exists('critical_css', $template_config) || array_key_exists('critical_css_v2', $template_config)) && array_key_exists("stylesheet", $template_config); } /** * Determines if stylesheets should be delayed based on configuration */ private function shouldDelayStylesheets($template_config) { return array_key_exists('delay_stylesheets', $template_config) && $template_config['delay_stylesheets']; } /** * Determines if delay should only occur on exact match */ private function shouldDelayOnlyOnExactMatch($template_config, $remaining_styles) { return array_key_exists('delay_only_on_exact_match', $template_config) && $template_config['delay_only_on_exact_match'] && !empty($remaining_styles); } /** * Determines which Critical CSS version to use (v2 takes precedence) */ private function getCriticalCssContent($template_config) { if (!empty($template_config["critical_css_v2"])) { return ['version' => 'v2', 'content' => $template_config["critical_css_v2"]]; } return ['version' => 'v1', 'content' => $template_config["critical_css"]]; } /** * Determines if image replacement rules should be applied */ private function shouldApplyImageReplacementRules($template_config) { return array_key_exists('image_replace_rules', $template_config); } /** * Process Used CSS optimization path */ private function processUsedCssOptimization(&$buffer, $template_config, &$page_audits) { $result = $this->extractAndReplaceStyles($buffer, $template_config['used_css_replace_rules']); $buffer = $result['modified_buffer']; if (!empty($result['remaining_styles']) || !empty($result['regex_matched_styles'])) { $page_audits['regex_matched_styles'] = $result['regex_matched_styles']; $page_audits['non_matching_stylesheets'] = $result['remaining_styles']; } $buffer = $this->handleUsedCss($buffer, $template_config['used_css'], $template_config['used_css_replace_rules']); if ($this->shouldApplyImageReplacementRules($template_config)) { $buffer = $this->handleImagesInInlineStyle($buffer, $template_config['image_replace_rules']); } } /** * Process Critical CSS optimization path */ private function processCriticalCssOptimization(&$buffer, $template_config, &$page_audits) { $result = $this->extractAndReplaceStyles($buffer, $template_config['stylesheet'], false); if (!empty($result['remaining_styles']) || !empty($result['regex_matched_styles'])) { $page_audits['regex_matched_styles'] = $result['regex_matched_styles']; $page_audits['non_matching_stylesheets'] = $result['remaining_styles']; } if ($this->shouldDelayStylesheets($template_config)) { $this->processDelayedStylesheetsPath($buffer, $template_config, $result['remaining_styles']); } else { $this->processNonDelayedStylesheetsPath($buffer, $template_config); } } /** * Process the delayed stylesheets optimization path */ private function processDelayedStylesheetsPath(&$buffer, $template_config, $remaining_styles) { $should_modify_styles = true; if ($this->shouldDelayOnlyOnExactMatch($template_config, $remaining_styles)) { $should_modify_styles = false; } if ($should_modify_styles) { $buffer = $this->modifyInlineStyles($buffer); $buffer = $this->modifyExternalStylesheets($buffer, $template_config['stylesheet']); } if ($this->shouldApplyImageReplacementRules($template_config)) { $buffer = $this->handleImagesInInlineStyle($buffer, $template_config['image_replace_rules']); } $this->applyCriticalCss($buffer, $template_config); } /** * Process the non-delayed stylesheets optimization path */ private function processNonDelayedStylesheetsPath(&$buffer, $template_config) { $this->applyCriticalCss($buffer, $template_config); $buffer = $this->handleStylesheet($buffer, $template_config["stylesheet"]); if ($this->shouldApplyImageReplacementRules($template_config)) { $buffer = $this->handleImagesInInlineStyle($buffer, $template_config['image_replace_rules']); } } /** * Apply the appropriate Critical CSS version */ private function applyCriticalCss(&$buffer, $template_config) { $critical_css = $this->getCriticalCssContent($template_config); if ($critical_css['version'] === 'v2') { $buffer = $this->addCriticalCssV2($buffer, $critical_css['content']); } else { $buffer = $this->addCriticalCss($buffer, $critical_css['content']); } } private function processJavascriptOptimization(&$buffer, $template_config, $is_default_config, &$page_audits) { $result = $this->extractAndMatchJavascripts($buffer, $template_config['javascript']); if ($result === false) { return; } $remaining_scripts = $result['remaining_scripts']; $regex_matched_scripts = $result['regex_matched_scripts']; if (is_array($remaining_scripts)) { $should_process_with_remaining = $this->shouldProcessWithRemainingScripts($is_default_config, $remaining_scripts, $result['all_matched_configs_are_delayed']); if (empty($remaining_scripts) || $should_process_with_remaining) { if (array_key_exists("javascripts_to_add_after_delay", $template_config)) { $buffer = $this->addJavascripts($buffer, $template_config["javascripts_to_add_after_delay"]); } // Handle matched scripts normally and delay remaining scripts if needed $buffer = $this->handleJavascript($buffer, $template_config["javascript"], $remaining_scripts, $should_process_with_remaining); } if (!empty($remaining_scripts) || !empty($regex_matched_scripts)) { $page_audits['regex_matched_scripts'] = $regex_matched_scripts; $page_audits['non_matching_scripts'] = $remaining_scripts; } } } private function shouldProcessWithRemainingScripts($is_default_config, $remaining_scripts, $all_matched_configs_are_delayed) { $should_process_with_remaining = false; // Check if we should process even with remaining scripts if ($is_default_config) { // For default configs, only check the default config setting if (array_key_exists("delay_all_js_on_non_exact_match_for_default_config", $this->config) && $this->config["delay_all_js_on_non_exact_match_for_default_config"] === true) { $should_process_with_remaining = true; } } else { // For template configs, check the template config setting if (array_key_exists("delay_all_js_on_non_exact_match_for_template_config", $this->config) && $this->config["delay_all_js_on_non_exact_match_for_template_config"] === true) { $should_process_with_remaining = true; } } // Additional safety check: only process if ALL matching configs have is_delayed = true if ($should_process_with_remaining && !empty($remaining_scripts)) { if (!$all_matched_configs_are_delayed) { $should_process_with_remaining = false; } } // Check for patterns that should skip optimization entirely in non-exact match scenarios if ($should_process_with_remaining && !empty($remaining_scripts)) { if ($this->hasSkipOptimizationPatterns($remaining_scripts)) { $should_process_with_remaining = false; } } return $should_process_with_remaining; } private function hasSkipOptimizationPatterns($remaining_scripts) { if (!isset($this->config['skip_optimization_patterns_on_non_exact_match']) || !is_array($this->config['skip_optimization_patterns_on_non_exact_match'])) { return false; } foreach ($remaining_scripts as $remaining_script) { foreach ($this->config['skip_optimization_patterns_on_non_exact_match'] as $pattern) { if (strpos($remaining_script, $pattern) !== false) { return true; } } } return false; } public function handleJavascript($buffer, $js_config_hash, $remaining_scripts = [], $should_process_with_remaining = false) { if (!is_array($js_config_hash) || empty($js_config_hash)) { return $buffer; } if (array_key_exists("js_config", $js_config_hash) && is_array($js_config_hash['js_config'])) { $js_config = $js_config_hash["js_config"]; } else { return $buffer; } $scriptAttrs = array(); $deferredScriptAttrs = array(); // NEW: Separate array for scripts with existing defer $script_index = 0; usort($js_config, function($a, $b) { return ($a['aggregation_priority'] ?? PHP_INT_MAX) - ($b['aggregation_priority'] ?? PHP_INT_MAX); }); $extract_js_regex = $this->config['extract_js_regex']; preg_match_all($extract_js_regex, $buffer, $script_matches); $extracted_scripts = $script_matches[0]; if ($should_process_with_remaining && !empty($remaining_scripts)) { // Non-exact match processing - delay ALL scripts with smart config matching $result = $this->processNonExactMatchScripts($extracted_scripts, $js_config, $js_config_hash, $buffer, $script_index); $scriptAttrs = $result[0]; $deferredScriptAttrs = $result[1]; } else { // Exact match processing - original logic $result = $this->processExactMatchScripts($extracted_scripts, $js_config, $buffer, $script_index); $scriptAttrs = $result[0]; $deferredScriptAttrs = $result[1]; } if (!empty($deferredScriptAttrs) && is_array($deferredScriptAttrs) && isset($deferredScriptAttrs[0]) && is_array($deferredScriptAttrs[0]) && isset($deferredScriptAttrs[0]['attrs']) && is_array($deferredScriptAttrs[0]['attrs'])) { $deferredScriptAttrs[0]['attrs']['is_first_defer_element'] = true; } // Combine normal scripts first, then deferred scripts to maintain execution order $scriptAttrs = array_merge($scriptAttrs, $deferredScriptAttrs); // Add any additional script infos to delay if (isset($js_config_hash['js_infos_to_delay']) && is_array($js_config_hash['js_infos_to_delay'])) { $scriptAttrs = array_merge($scriptAttrs, $js_config_hash['js_infos_to_delay']); } if (!empty($scriptAttrs)) { $buffer = $this->addScriptAttrsToBuffer($buffer, $scriptAttrs); } return $buffer; } private function hasExistingDeferAttribute($script) { $attributes = $this->getScriptAttributes($script); return array_key_exists('defer', $attributes); } private function processNonExactMatchScripts($extracted_scripts, $js_config, $js_config_hash, &$buffer, &$script_index) { $nonAggScriptAttrs = array(); $deferredScriptAttrs = array(); // NEW: Separate array for deferred scripts foreach ($extracted_scripts as $script) { // Skip scripts that should be ignored based on type if ($this->shouldSkipScriptByType($script, $js_config_hash)) { continue; } // Skip scripts that have ignored attributes if ($this->shouldSkipScriptByAttributes($script, $js_config_hash)) { continue; } $template_id = ALCacheHelper::randString(20); // Try to find matching config, otherwise use default delay config $delay_config = $this->createDelayConfigForScript($script, $js_config); // Apply forced attributes for delayed scripts $delay_config = $this->applyForcedDelayAttributes($delay_config); $new_script = $this->processDelayedScript($script, $delay_config, $script_index, $template_id); $scriptAttrEntry = $this->createScriptAttrsEntry($delay_config, $script, $script_index, $template_id); // NEW: Check if script has existing defer attribute if ($this->hasExistingDeferAttribute($script)) { $deferredScriptAttrs[] = $scriptAttrEntry; } else { $nonAggScriptAttrs[] = $scriptAttrEntry; } $buffer = str_replace($script, $new_script, $buffer); $script_index++; } return array($nonAggScriptAttrs, $deferredScriptAttrs); } private function processExactMatchScripts($extracted_scripts, $js_config, &$buffer, &$script_index) { $nonAggScriptAttrs = array(); $deferredScriptAttrs = array(); // NEW: Separate array for deferred scripts $aggScriptAttrs = array(); $defer_scripts = array(); $delay_scripts = array(); foreach ($extracted_scripts as $script) { $matching_config = $this->findMatchingConfig($script, $js_config); if ($matching_config) { $this->processMatchedScript($script, $matching_config, $buffer, $defer_scripts, $delay_scripts, $nonAggScriptAttrs, $deferredScriptAttrs, $script_index); } } // Process aggregated scripts $this->processAggregatedScripts($buffer, $defer_scripts, $delay_scripts, $aggScriptAttrs, $script_index); // Merge: aggregated scripts, normal scripts, then deferred scripts return array(array_merge($aggScriptAttrs, $nonAggScriptAttrs), $deferredScriptAttrs); } private function shouldSkipScriptByType($script, $js_config_hash) { if (preg_match('~<script.*?type=[\'"](.*?)[\'"]~is', $script, $type_value)) { if (isset($js_config_hash['js_types_to_ignore']) && is_array($js_config_hash['js_types_to_ignore'])) { if (!empty($type_value[1]) && in_array($type_value[1], $js_config_hash['js_types_to_ignore'])) { return true; } } } return false; } private function shouldSkipScriptByAttributes($script, $js_config_hash) { if (isset($js_config_hash['js_attributes_to_ignore']) && is_array($js_config_hash['js_attributes_to_ignore'])) { foreach ($js_config_hash['js_attributes_to_ignore'] as $excluded_attribute) { if (strpos($script, $excluded_attribute) !== false) { return true; } } } return false; } private function findMatchingConfig($script, $js_config) { $stripped_script = preg_replace('/\s+/', '', $script); foreach ($js_config as $config) { $search_for = isset($config['search_for']) ? $config['search_for'] : ''; $stripped_search_for = preg_replace('/\s+/', '', $search_for); $search_for_regex = isset($config['search_for_regex']) ? $config['search_for_regex'] : ''; if (!empty($stripped_search_for) && !empty($stripped_script)) { if (strpos($stripped_script, $stripped_search_for) !== false) { return $config; } } else if (!empty($search_for) && strpos($script, $search_for) !== false) { return $config; } if (!empty($search_for_regex) && preg_match($search_for_regex, $script)) { return $config; } } return null; } private function createDelayConfigForScript($script, $js_config) { // First try to find a matching config for this script $matching_config = $this->findMatchingConfig($script, $js_config); if ($matching_config && !(isset($matching_config['is_aggregated']) && $matching_config['is_aggregated'])) { // Use the matching config but force delayed mode $delay_config = $matching_config; $delay_config['is_delayed'] = true; } else { $attributes = array(); $scriptAttributes = $this->getScriptAttributes($script); $ignoredAttributes = isset($this->config['strict_js_attrs']) ? $this->config['strict_js_attrs'] : array(); foreach ($scriptAttributes as $attrName => $attrValue) { if (in_array($attrName, $ignoredAttributes)) { continue; } $attributes[$attrName] = $attrValue; } // Create default delay config $delay_config = array( 'is_delayed' => true, 'is_inline' => $this->isInlineScript($script), 'attributes' => $attributes ); } return $delay_config; } private function applyForcedDelayAttributes($delay_config) { if (isset($this->config['forced_delay_js_attrs']) && is_array($this->config['forced_delay_js_attrs'])) { if (!isset($delay_config['attributes'])) { $delay_config['attributes'] = array(); } $delay_config['attributes'] = array_merge($delay_config['attributes'], $this->config['forced_delay_js_attrs']); } return $delay_config; } private function processMatchedScript($script, $matching_config, &$buffer, &$defer_scripts, &$delay_scripts, &$nonAggScriptAttrs, &$deferredScriptAttrs, &$script_index) { $is_delay = isset($matching_config['is_delayed']) ? $matching_config['is_delayed'] : false; $is_defer = isset($matching_config['is_defer']) ? $matching_config['is_defer'] : false; $is_aggregated = isset($matching_config['is_aggregated']) ? $matching_config['is_aggregated'] : false; $final_source_url = isset($matching_config['final_source_url']) ? $matching_config['final_source_url'] : ''; $has_existing_defer = $this->hasExistingDeferAttribute($script); if ($is_delay && $is_aggregated) { if (!isset($delay_scripts[$final_source_url])) { $delay_scripts[$final_source_url] = array(); } $delay_scripts[$final_source_url][] = $matching_config; $buffer = str_replace($script, '', $buffer); } elseif ($is_delay) { $template_id = ALCacheHelper::randString(20); $new_script = $this->processDelayedScript($script, $matching_config, $script_index, $template_id); $scriptAttrEntry = $this->createScriptAttrsEntry($matching_config, $script, $script_index, $template_id); // NEW: Separate based on existing defer attribute if ($has_existing_defer) { $deferredScriptAttrs[] = $scriptAttrEntry; } else { $nonAggScriptAttrs[] = $scriptAttrEntry; } $buffer = str_replace($script, $new_script, $buffer); } elseif ($is_defer && $is_aggregated) { if (!isset($defer_scripts[$final_source_url])) { $defer_scripts[$final_source_url] = array(); } $defer_scripts[$final_source_url][] = $matching_config; $buffer = str_replace($script, '', $buffer); } else { $new_script = $this->processNonDelayedScript($script, $matching_config); $buffer = str_replace($script, $new_script, $buffer); } $script_index++; } private function processAggregatedScripts(&$buffer, $defer_scripts, $delay_scripts, &$aggScriptAttrs, &$script_index) { // Sort and process defer_scripts in descending order (higher priority first) uasort($defer_scripts, function($a, $b) { $priorityA = isset($a[0]['aggregation_priority']) ? $a[0]['aggregation_priority'] : PHP_INT_MAX; $priorityB = isset($b[0]['aggregation_priority']) ? $b[0]['aggregation_priority'] : PHP_INT_MAX; if ($priorityA === $priorityB) { return 0; } return ($priorityB < $priorityA) ? -1 : 1; // Descending order (higher priority first) }); $head_tag = $this->extractHeadTag($buffer); foreach ($defer_scripts as $final_source_url => $configs) { $aggregated_script = $this->createAggregatedScript($final_source_url, $configs); $buffer = ALHelper::safePregReplace( '/' . preg_quote($head_tag, '/') . '/', $head_tag . "\n" . $aggregated_script, $buffer, 1 ); } // Sort and process delay_scripts in descending order (higher priority first) uasort($delay_scripts, function($a, $b) { $priorityA = isset($a[0]['aggregation_priority']) ? $a[0]['aggregation_priority'] : PHP_INT_MAX; $priorityB = isset($b[0]['aggregation_priority']) ? $b[0]['aggregation_priority'] : PHP_INT_MAX; if ($priorityA === $priorityB) { return 0; } return ($priorityB < $priorityA) ? -1 : 1; // Descending order (higher priority first) }); foreach ($delay_scripts as $final_source_url => $configs) { $template_id = ALCacheHelper::randString(20); $template = "<template id=\"$template_id\"></template>"; $buffer = ALHelper::safePregReplace( '/' . preg_quote($head_tag, '/') . '/', $head_tag . "\n" . $template, $buffer, 1 ); $aggScriptAttrs[] = $this->createScriptAttrsEntry($configs[0], $template, $script_index, $template_id); $script_index++; } // Reverse aggScriptAttrs to match DOM order (ascending priority) $aggScriptAttrs = array_reverse($aggScriptAttrs); } private function addScriptAttrsToBuffer($buffer, $scriptAttrs) { $head_tag = $this->extractHeadTag($buffer); $scriptAttrsJson = json_encode($scriptAttrs); $scriptAttrsScript = "<script id=\"bv-dl-scripts-list\" data-cfasync=\"false\" bv-exclude=\"true\">\nvar scriptAttrs = $scriptAttrsJson;\n</script>"; return ALHelper::safePregReplace( '/' . preg_quote($head_tag, '/') . '/', $head_tag . "\n" . $scriptAttrsScript, $buffer, 1 ); } private function createAggregatedScript($final_source_url, $configs) { $attributes = array(); foreach ($configs as $config) { if (isset($config['attributes'])) { $attributes = array_merge($attributes, $config['attributes']); } } $new_script = '<script'; foreach ($attributes as $attr => $value) { if ($attr === 'src') { continue; } // If value is boolean false, skip the attribute entirely if ($value === false) { continue; } // For all other values (including boolean true), use normal behavior with quotes $new_script .= ' ' . $attr . '="' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '"'; } // Only add defer if it's not already in attributes if (!isset($attributes['defer'])) { $new_script .= ' defer'; } $new_script .= ' src="' . htmlspecialchars($final_source_url, ENT_QUOTES, 'UTF-8') . '"></script>'; return $new_script; } private function processDelayedScript($script, $config, $script_index, $template_id) { $is_inline = isset($config['is_inline']) ? $config['is_inline'] : false; if ($is_inline) { return $this->processDelayedInlineScript($script, $config, $template_id); } else { return $this->processDelayedExternalScript($script, $template_id); } } private function processDelayedInlineScript($script, $config, $template_id) { $inline_js_content_regex = '/<script[^>]*>([\s\S]*?)<\/script>/i'; if (isset($this->config['inline_js_content_regex'])) { $inline_js_content_regex = $this->config['inline_js_content_regex']; } if (preg_match($inline_js_content_regex, $script, $matches)) { $content = trim($matches[1]); } else { return $script; } $new_script = '<script type="bv_inline_delayed_js" bv_unique_id="' . $template_id . '"'; $attributes = isset($config['attributes']) ? $config['attributes'] : array(); foreach ($attributes as $attr => $value) { if ($attr === 'type') { continue; } $new_script .= ' ' . $attr . '="' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '"'; } $new_script .= '>' . $content . '</script>'; return $new_script; } private function processDelayedExternalScript($script, $template_id) { return '<template id="' . $template_id . '"></template>'; } private function processNonDelayedScript($script, $config) { $is_inline = isset($config['is_inline']) ? $config['is_inline'] : false; $is_async = isset($config['is_async']) ? $config['is_async'] : false; $is_defer = isset($config['is_defer']) ? $config['is_defer'] : false; $final_source_url = isset($config['final_source_url']) ? $config['final_source_url'] : ''; $attributes = isset($config['attributes']) ? $config['attributes'] : array(); $scriptAttributes = $this->getScriptAttributes($script); $ignoredAttributes = isset($this->config['strict_js_attrs']) ? $this->config['strict_js_attrs'] : array(); foreach ($scriptAttributes as $attrName => $attrValue) { if (in_array($attrName, $ignoredAttributes)) { continue; } $attributes[$attrName] = $attrValue; } if ($is_inline) { return $this->processNonDelayedInlineScript($script, $is_async, $is_defer, $attributes); } else { return $this->processNonDelayedExternalScript($script, $is_async, $is_defer, $final_source_url, $attributes); } } private function processNonDelayedInlineScript($script, $is_async, $is_defer, $attributes) { $inline_js_content_regex = '/<script[^>]*>([\s\S]*?)<\/script>/i'; if (isset($this->config['inline_js_content_regex'])) { $inline_js_content_regex = $this->config['inline_js_content_regex']; } if (preg_match($inline_js_content_regex, $script, $matches)) { $content = trim($matches[1]); } else { return $script; } if ($is_async || $is_defer) { $encoded_content = base64_encode($content); // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript $new_script = '<script src="data:text/javascript;base64,' . $encoded_content . '"'; } else { $new_script = '<script'; } foreach ($attributes as $attr => $value) { if ($attr === 'src') { continue; } $new_script .= ' ' . $attr . '="' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '"'; } if ($is_async) { $new_script .= ' async'; } if ($is_defer) { $new_script .= ' defer'; } if ($is_async || $is_defer) { $new_script .= '></script>'; } else { $new_script .= '>' . $content . '</script>'; } return $new_script; } private function processNonDelayedExternalScript($script, $is_async, $is_defer, $final_source_url, $attributes) { if (empty($final_source_url) && preg_match('/src=["\'](.*?)["\']/i', $script, $matches)) { $final_source_url = $matches[1]; } $new_script = '<script'; foreach ($attributes as $attr => $value) { if ($attr === 'src') { continue; } $new_script .= ' ' . $attr . '="' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '"'; } if ($is_async) { $new_script .= ' async'; } if ($is_defer) { $new_script .= ' defer'; } $new_script .= ' src="' . htmlspecialchars($final_source_url, ENT_QUOTES, 'UTF-8') . '"></script>'; return $new_script; } private function getScriptAttributes($scriptTag) { if (!is_string($scriptTag) || trim($scriptTag) === '') { return []; } if (!isset($this->config['extract_js_attr_regex'])) { return []; } $attr_regex = $this->config['extract_js_attr_regex']; $attributes = []; if (preg_match('/<script\b(?<attrs>[^>]*)>/i', $scriptTag, $matches)) { $attrString = $matches['attrs']; if (preg_match_all($attr_regex, $attrString, $matches, PREG_SET_ORDER) && is_array($matches) && !empty($matches)) { foreach ($matches as $match) { $name = $match['name']; $value = !empty($match['value1']) ? $match['value1'] : (!empty($match['value2']) ? $match['value2'] : (!empty($match['value3']) ? $match['value3'] : (!empty($match['value4']) ? $match['value4'] : ''))); $attributes[$name] = $value; } } } return $attributes; } private function createScriptAttrsEntry($config, $script, $script_index, $bv_unique_id) { $is_inline = isset($config['is_inline']) ? $config['is_inline'] : false; $attributes = isset($config['attributes']) ? $config['attributes'] : array(); $scriptAttributes = $this->getScriptAttributes($script); $ignoredAttributes = isset($this->config['strict_js_attrs']) ? $this->config['strict_js_attrs'] : array(); foreach ($scriptAttributes as $attrName => $attrValue) { if (in_array($attrName, $ignoredAttributes)) { continue; } $attributes[$attrName] = $attrValue; } $entry = array( "attrs" => $attributes, "bv_unique_id" => $bv_unique_id, "reference" => $script_index ); $entry['attrs']['bv_inline_delayed'] = $is_inline; if (!$is_inline) { $final_source_url = isset($config['final_source_url']) ? $config['final_source_url'] : ''; // If no final_source_url from config, try to get src from already extracted attributes if (empty($final_source_url) && isset($attributes['src']) && !empty($attributes['src'])) { $final_source_url = $attributes['src']; } $decoded_url = html_entity_decode($final_source_url); $entry['attrs']['src'] = $decoded_url; } return $entry; } private function replaceLastBodyTag($buffer, $replaceWith) { $parts = explode('</body>', $buffer); if (count($parts) > 1) { $last_part = array_pop($parts); return implode('</body>', $parts) . $replaceWith . $last_part; } else { return ALHelper::safeStrReplace('</body>', $replaceWith, $buffer); } } public function addJavascripts($buffer, $js_to_add_config) { if (!is_array($js_to_add_config) || empty($js_to_add_config)) { return $buffer; } foreach ($js_to_add_config as $config) { if (is_array($config) && !empty($config)) { if (array_key_exists("search_for", $config) && array_key_exists("replace_with", $config)) { if ($config['search_for'] === '</body>') { $buffer = $this->replaceLastBodyTag($buffer, $config['replace_with']); } else if(strpos($buffer, $config['search_for']) !== false) { $buffer = ALHelper::safePregReplace( '/' . preg_quote($config["search_for"], '/') . '/', $config["replace_with"], $buffer, 1 ); } else if(array_key_exists("tag_name", $config)) { if (ALHelper::safePregMatch('/<'.$config["tag_name"].'\b[^>]*>/iU', $buffer, $matches)) { $buffer = ALHelper::safePregReplace( '/' . preg_quote($matches[0], '/') . '/', $config['replace_with'], $buffer, 1 ); } } } } } return $buffer; } public function handleStylesheet($buffer, $style_config) { if (!is_array($style_config) || empty($style_config)) { return $buffer; } $buffer = $this->doSearchAndReplace($buffer, $style_config); return $buffer; } public function handleFonts($buffer, $font_config) { if (!is_array($font_config) || empty($font_config)) { return $buffer; } foreach ($font_config as $config) { if (is_array($config) && !empty($config)) { if (array_key_exists("search_for", $config) && array_key_exists("replace_with", $config)) { $buffer = ALHelper::safeStrReplace($config["search_for"], $config["replace_with"], $buffer); } } } return $buffer; } public function sanitize_cdn_url($url) { $url = sanitize_text_field($url); if (!$url || !ALHelper::safePregMatch('@^https?://.+\.[^.]+@i', $url)) { // Not an URL. return ''; } return trailingslashit($url); } public function getParsedUrlInfo() { $info = array(); $info['scheme'] = (ALHelper::getRawParam('SERVER', 'HTTPS')) ? "https://" : "http://"; $info['path'] = dirname(ALHelper::getRawParam('SERVER', 'REQUEST_URI')); $info['host'] = ALHelper::getRawParam('SERVER', 'HTTP_HOST'); return $info; } public function hasExcludedSource($url) { if (!array_key_exists('image_excluded_src_attributes', $this->config) && !is_array($this->config['image_excluded_src_attributes'])) { return false; } foreach ($this->config['image_excluded_src_attributes'] as $attr) { if (strpos($url, $attr) !== false) { return true; } } return false; } public function hasExcludedIframeAttrs($iframe) { if (!array_key_exists('iframe_excluded_attrs', $this->config) && !is_array($this->config['iframe_excluded_attrs'])) { return false; } foreach ($this->config['iframe_excluded_attrs'] as $excluded_attr) { if (strpos($iframe[0], $excluded_attr) !== false) { return true; } } return false; } public function hasExcludedAttributes($element) { if (!array_key_exists('image_excluded_attributes', $this->config) && !is_array($this->config['image_excluded_attributes'])) { return false; } foreach ($this->config['image_excluded_attributes'] as $excluded_attr) { if (strpos($element, $excluded_attr) !== false) { return true; } } return false; } public function canLazyload($image, $image_config) { if ($this->hasExcludedAttributes($image)) { return false; } if (!ALHelper::safePregMatch('#\ssrc\s*=\s*(\'|")(?<src>.*)\1#Uis', $image, $attrs)) { return false; } $img_src = trim($attrs['src']); if ( '' === $img_src || strpos($img_src, 'data:image/svg+xml') === 0 ) { return false; } if (isset($image_config['images_not_to_lazyload']) && is_array($image_config['images_not_to_lazyload'])) { foreach ($image_config['images_not_to_lazyload'] as $image_not_to_lazyload) { $image_not_to_lazyload = $this->getImageCdnUrl($image_not_to_lazyload, $image_config['replace_urls']); if (strpos($img_src, $image_not_to_lazyload) !== false) { return false; } } } return $image; } public function url_relative_to_full($url, $scheme, $host, $path) { $final_url = ""; if (strpos($url, './') === 0 && !empty($path)) { $final_url = $scheme . $host . rtrim($path, '/') . '/' . ltrim($url, './'); } elseif (strpos($url, '/') === 0 && strpos($url, '//') !== 0) { $final_url = $scheme . $host . $url; } else { $final_url = $url; } return $final_url; } public function replace_host_with_cdn($replace_urls, $url){ foreach($replace_urls as $replace_url) { if (array_key_exists("search_for", $replace_url) && array_key_exists("replace_with", $replace_url)) { if (strpos($url, $replace_url["search_for"]) !== false) { return ALHelper::safeStrReplace($replace_url["search_for"], $replace_url["replace_with"], $url); } } } $new_url = $this->convertThirdPartyUrlToCdnUrl($url); return $new_url; } public function replace_host_with_site_urls($replace_urls, $url, $host){ foreach($replace_urls as $replace_url) { if (array_key_exists("search_for", $replace_url) && array_key_exists("replace_with", $replace_url)) { if (strpos($url, $replace_url["search_for"]) === 0) { return ALHelper::safeStrReplace($replace_url["search_for"], $replace_url["replace_with"] . $host, $url); } } } $new_url = $this->convertThirdPartyUrlToSiteUrl($url); return $new_url; } public function isThirdPartyUrl($url) { $current_domain = ALHelper::getRawParam('SERVER', 'HTTP_HOST'); $image_domain = parse_url($url, PHP_URL_HOST); return ($image_domain !== $current_domain); } public function convertThirdPartyUrlToCdnUrl($image_url) { if ($this->isThirdPartyUrl($image_url)) { $image_domain = parse_url($image_url, PHP_URL_HOST); $cdn_host = parse_url($this->config['cdn_url'], PHP_URL_HOST); return ALHelper::safeStrReplace($image_domain, $cdn_host, $image_url); } return $image_url; } public function convertThirdPartyUrlToSiteUrl($image_url) { if ($this->isThirdPartyUrl($image_url)) { $img_url_info = parse_url($image_url); $new_img_url = $this->config['image_url_path'] . $img_url_info['host'] . '/' . $img_url_info['path']; return $new_img_url; } return $image_url; } public function imageUrlHostParam($image_url) { $parsed_url = parse_url($image_url); $host_param = "bv_host=" . $parsed_url['host']; return $host_param; } private function getResizedImagePaths($image_url) { if (!isset($this->config['resized_image_postfix_names']) || !is_array($this->config['resized_image_postfix_names'])) { return []; } $img_path_info = pathinfo($image_url); $img_url_info = parse_url($image_url); if (!$img_path_info || !$img_url_info) { return []; } $upload_dir = wp_upload_dir(); $image_basepath = $upload_dir['basedir'] . '/al_opt_content/IMAGE/' . $img_url_info['host'] . '/' . $img_url_info['path']; if (empty($img_path_info['extension'])) { return []; } $extension = $img_path_info['extension']; $resized_images = []; foreach ($this->config['resized_image_postfix_names'] as $postfix) { $original = $image_basepath . '.' . $postfix . '.' . $extension; $webp = $original . '.bv.webp'; $resized_images[$postfix] = [ 'original' => $original, 'webp' => $webp ]; } return ['basepath' => $image_basepath, 'variants' => $resized_images]; } public function isOptimizedImagePresent($image_url) { $resized = $this->getResizedImagePaths($image_url); if (empty($resized)) { return false; } if (!file_exists($resized['basepath']) || !file_exists($resized['basepath'] . '.bv.webp')) { return false; } foreach ($resized['variants'] as $paths) { if (!file_exists($paths['webp'])) { return false; } } return true; } private function appendResizedImageInfos($image_url, $new_image_url) { $resized = $this->getResizedImagePaths($image_url); if (empty($resized)) { return $new_image_url; } $image_info_parts = []; foreach ($resized['variants'] as $postfix => $paths) { if (file_exists($paths['webp'])) { $size = getimagesize($paths['webp']); if ($size) { $image_info_parts[] = $postfix . ':' . $size[0] . '*' . $size[1]; } } } if (!empty($image_info_parts)) { $resized_info_str = implode(';', $image_info_parts); $resized_info_param = 'bv-resized-infos=' . urlencode($resized_info_str); $separator = (parse_url($new_image_url, PHP_URL_QUERY)) ? '&' : '?'; $new_image_url .= $separator . $resized_info_param; } return $new_image_url; } public function getImageCdnUrl($image_url, $replace_urls) { if (isset($this->config['image_excluded_src_attributes']) && is_array($this->config['image_excluded_src_attributes'])) { foreach ($this->config['image_excluded_src_attributes'] as $attrs) { if (strpos($image_url, $attrs) !== false) { return $image_url; } } } $parsed_url_info = $this->getParsedUrlInfo(); $image_url = $this->url_relative_to_full($image_url, $parsed_url_info['scheme'], $parsed_url_info['host'], $parsed_url_info['path']); if ($this->image_config['copied_to_site']) { if ($this->isOptimizedImagePresent($image_url)) { $host_param = $this->imageUrlHostParam($image_url); $parsed_url = parse_url($image_url); $new_image_url = $this->replace_host_with_site_urls($replace_urls, $image_url, $parsed_url['host']); $query_string = parse_url($image_url, PHP_URL_QUERY); $new_image_url = $new_image_url . ($query_string ? "&" . $host_param : "?" . $host_param); $new_image_url = $this->appendResizedImageInfos($image_url, $new_image_url); return $new_image_url; } } else { $host_param = $this->imageUrlHostParam($image_url); $parsed_url = parse_url($image_url); $new_image_url = $this->replace_host_with_cdn($replace_urls, $image_url); $query_string = parse_url($image_url, PHP_URL_QUERY); $new_image_url = $new_image_url . ($query_string ? "&" . $host_param : "?" . $host_param); $new_image_url = $this->appendResizedImageInfos($image_url, $new_image_url); return $new_image_url; } return $image_url; } public function getUrlsFromAttributes($image_tag) { $image_urls = array(); if (array_key_exists('image_src_attributes', $this->config) && is_array($this->config['image_src_attributes'])) { $attrs = implode('|', $this->config['image_src_attributes']); if (preg_match_all('#\s(' . $attrs . ')\s*=\s*(\'|")(?<values>.*?)\2#is', $image_tag, $matches)) { foreach ($matches['values'] as $values) { $values = trim($values); if ($this->hasEmbeddedImage($values)) continue; $values = explode(',', $values); foreach ($values as $src) { $src = preg_split('/\s+/', trim($src)); array_push($image_urls, $src[0]); } } } return $image_urls; } return array(); } public function replaceSrcAndSrcsetImages($element, $replace_urls, $class_name = "") { $urls_to_handle = array(); $image_urls = $this->getUrlsFromAttributes($element); $image_urls = array_unique($image_urls, SORT_REGULAR); if (empty($image_urls)) { return $element; } if (!empty($class_name)) $element = $this->addBvClass($element, $class_name); foreach ($image_urls as $url) { array_push($urls_to_handle, $url); } foreach ($urls_to_handle as $url) { $final_url = $this->getImageCdnUrl($url, $replace_urls); if ($final_url) $element = ALHelper::safeStrReplace($url, $final_url, $element); } return $element; } public function handlePictureTags($buffer, $image_config, $url_matched) { if (!preg_match_all('#<picture(?:.*)?>(?<sources>.*)</picture>#iUs', $buffer, $pictures, PREG_SET_ORDER)) { return $buffer; } $index = 0; foreach ($pictures as $picture) { $index += 1; $updated_picture = NULL; if (isset($image_config['picture_tags_not_to_lazyload']) && is_array($image_config['picture_tags_not_to_lazyload']) && $url_matched) { if (in_array($index, $image_config['picture_tags_not_to_lazyload'])) { $updated_picture = ALHelper::safeStrReplace('<picture', '<picture data-bvlazyload="false"', $picture[0]); $buffer = ALHelper::safeStrReplace($picture[0], $updated_picture, $buffer); } } if (is_null($updated_picture) && $this->hasExcludedAttributes($picture[0])){ $updated_picture = ALHelper::safeStrReplace('<picture', '<picture data-bvlazyload="false"', $picture[0]); $buffer = ALHelper::safeStrReplace($picture[0], $updated_picture, $buffer); } } if (!preg_match_all('#<picture(?:.*)?>(?<sources>.*)</picture>#iUs', $buffer, $pictures, PREG_SET_ORDER)) { return $buffer; } $pictures = array_unique($pictures, SORT_REGULAR); foreach ($pictures as $picture) { $lazyload_picture = isset($this->config["lazyload_existing_picture_tags"]) ? $this->config["lazyload_existing_picture_tags"] : false; $updated_picture = $picture[0]; if (strpos($picture[0], 'data-bvlazyload') !== false) { $lazyload_picture = false; } if ($lazyload_picture) { $updated_picture = $this->addLazyloadClassToPicture($picture[0]); } if (preg_match_all('#<source(?<atts>\s.+)>#iUs', $picture['sources'], $sources, PREG_SET_ORDER)) { $sources = array_unique($sources, SORT_REGULAR); foreach ($sources as $source) { $final_source = $source; $final_source[0] = $this->replaceSrcAndSrcsetImages($final_source[0], $image_config['replace_urls']); if ($lazyload_picture) { $final_source[0] = ALHelper::safePregReplace('/([\s"\'])srcset/i', '\1bv-data-srcset', $final_source[0]); } $updated_picture = ALHelper::safeStrReplace($source[0], $final_source[0], $updated_picture); } } if (preg_match_all('#<img\s.+\s?/?>#Uis', $picture[0], $image, PREG_SET_ORDER)) { $final_image = $image[0]; $final_image[0] = $this->replaceSrcAndSrcsetImages($final_image[0], $image_config['replace_urls']); if ($lazyload_picture) { $final_image[0] = $this->lazyloadImage($final_image[0], $image_config, false); } $updated_picture = ALHelper::safeStrReplace($image[0][0], $final_image[0], $updated_picture); } $buffer = ALHelper::safeStrReplace($picture[0], $updated_picture, $buffer); } return $buffer; } public function imageHeight($image_tag) { if (ALHelper::safePregMatch('#\sheight\s*=\s*(\'|")(?<height>.*?)(\'|")#iUs', $image_tag, $matches)) { return absint($matches['height']); } return 0; } public function imageWidth($image_tag) { if (ALHelper::safePregMatch('#\swidth\s*=\s*(\'|")(?<width>.*?)(\'|")#iUs', $image_tag, $matches)) { return absint($matches['width']); } return 0; } public function getSvgSource($width = 0, $height = 0) { $new_src = ALHelper::safeStrReplace( ' ', '%20', "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 $width $height'%3E%3C/svg%3E" ); return $new_src; } public function getCleanUrl($url) { $clean_url = html_entity_decode($url, ENT_QUOTES|ENT_HTML5); $clean_url = strip_tags($clean_url, '\'" '); $clean_url = trim($clean_url, "\"' "); $clean_url = esc_url($clean_url); return $clean_url; } public function lazyloadTagImages($buffer, $image_config) { if (isset($image_config['html_tag_bg_style_lazyload'])) { $html_tags_with_styles = preg_match_all('#<(?<tag>div|figure|section|span|li|a|article|label)\s+(?<before>[^>]+[\'"\s])?style\s*=\s*([\'"])(?<styles>.*?)\3(?<after>[^>]*)>#is', $buffer, $html_elements, PREG_SET_ORDER); if($html_tags_with_styles) { foreach($html_elements as $html_element) { $tag_with_bg_img = ALHelper::safePregMatch('#background-image\s*:.*(?<attr>\s*url\s*\((?<url>[^)]+)\))\s*;?#is', $html_element['styles'], $url); if ($tag_with_bg_img) { if ($this->hasExcludedAttributes($html_element[0])) { continue; } if (isset($image_config['images_not_to_lazyload']) && is_array($image_config['images_not_to_lazyload'])) { foreach ( $image_config['images_not_to_lazyload'] as $image_not_to_lazyload) { $image_not_to_lazyload = $this->getImageCdnUrl($image_not_to_lazyload, $image_config['replace_urls']); if (strpos($url['url'], $image_not_to_lazyload) !== false) { continue 2; } } } $url['orig_url'] = $url['url']; $url['url'] = $this->getCleanUrl($url['url']); $lazy_tag = $this->addBvClass($html_element[0], 'bv-lazyload-bg-style'); $img_size = false; if (!empty($url['url'])) { $img_size = @getimagesize($url['url']); } if (isset($img_size) && is_array($img_size)) { $svg_url = $this->getSvgSource($img_size[0], $img_size[1]); } else { $svg_url = $this->getSvgSource(0, 0); } $new_styles = ALHelper::safeStrReplace($url['orig_url'], esc_attr($svg_url), $html_element['styles']); $lazy_tag = ALHelper::safeStrReplace($html_element['styles'], $new_styles, $lazy_tag); $lazy_tag = ALHelper::safeStrReplace('<' . $html_element['tag'], '<' . $html_element['tag'] . ' bv-data-style="' . esc_attr($html_element['styles']) . '" ', $lazy_tag); $buffer = ALHelper::safeStrReplace($html_element[0], $lazy_tag, $buffer); } } } } return $buffer; } public function addLazyloadClassToPicture($element) { ALHelper::safePregMatch('#<picture.*>#Uis', $element, $open_tag); $new_open_tag = $open_tag[0]; $lazyload_class = "bv-lazyload-picture"; $element_with_class = ALHelper::safePregMatch('#\sclass\s*=\s*(?<classes>["\'].*?["\']|[^\s]+)#is', $open_tag[0], $class_attr); if (!$element_with_class || empty($class_attr)) { $new_open_tag = ALHelper::safeStrReplace('<picture', '<picture class="' . $lazyload_class . '"', $new_open_tag); } else { $classes = trim($class_attr['classes'], '\'" '); if (empty($classes)) { $new_open_tag = ALHelper::safeStrReplace($class_attr[0], ' class="' . $lazyload_class . '" ', $new_open_tag); } else { $classes .= ' ' . $lazyload_class; $new_open_tag = ALHelper::safeStrReplace($class_attr[0], ' class="' . $classes . '" ', $new_open_tag); } } return ALHelper::safeStrReplace($open_tag[0], $new_open_tag, $element); } public function addBvClass($element, $lazyload_class) { $element_with_class = ALHelper::safePregMatch('#\sclass\s*=\s*(?<classes>["\'].*?["\']|[^\s]+)#is', $element, $class_attr); if (!$element_with_class || empty($class_attr)) { return ALHelper::safePregReplace('#<(img|div|figure|section|li|span|a|iframe)([^>]*)>#is', '<\1 class="' . $lazyload_class . '"\2>', $element); } $classes = trim($class_attr['classes'], '\'" '); if (empty($classes)) { return ALHelper::safeStrReplace($class_attr[0], ' class="' . $lazyload_class . '" ', $element); } $classes .= ' ' . $lazyload_class; return ALHelper::safeStrReplace($class_attr[0], ' class="' . $classes . '" ', $element); } public function lazyloadImage($image, $image_config, $add_bv_class = true) { $final_image = $image; if ($this->canLazyload($image, $image_config)) { if ($add_bv_class) { $final_image = $this->addBvClass($image, 'bv-lazyload-tag-img'); } if (ALHelper::safePregMatch('#\ssrc\s*=\s*(\'|")(?<src>.*)\1#Uis', $image, $matches)) { if (!empty($matches['src'])) { $width = $this->imageWidth($image); $height = $this->imageHeight($image); $final_image = ALHelper::safeStrReplace('<img', '<img bv-data-src="' . esc_attr($matches['src']) . '" ', $final_image); $final_image = ALHelper::safeStrReplace($matches[0], ' src="' . $this->getSvgSource($width, $height) . '"', $final_image); } } if (ALHelper::safePregMatch('#\ssrcset\s*=\s*(\'|")(?<srcset>.*)\1#Uis', $image, $srcset)) { if (!empty($srcset['srcset'])) { $final_image = ALHelper::safeStrReplace($srcset[0], ' bv-data-srcset="' . $srcset['srcset'] . '" ', $final_image); } } } return $final_image; } public function get_src_image_url($image_tag) { if (ALHelper::safePregMatch('#\ssrc\s*=\s*(\'|")(?<src>.*)\1#Uis', $image_tag, $attrs)) { return trim($attrs['src']); } return ""; } public function normalizeImageUrlForViewportMatch($url) { $clean_url = $this->getCleanUrl($url); if ($clean_url === '') { return ''; } $url_parts = explode('?', $clean_url, 2); if (!empty($url_parts[0])) { return $url_parts[0]; } return $clean_url; } public function getMatchedViewportImageUrl($image, $image_config) { if (!isset($image_config['viewport_images_info']) || !is_array($image_config['viewport_images_info'])) { return false; } $img_src = $this->get_src_image_url($image); if (empty($img_src)) { return false; } $normalized_img_src = $this->normalizeImageUrlForViewportMatch($img_src); if ($normalized_img_src === '' || strpos($normalized_img_src, 'data:image') === 0) { return false; } foreach ($image_config['viewport_images_info'] as $device => $viewport_images) { if (!is_array($viewport_images)) { continue; } foreach ($viewport_images as $viewport_image_url => $info) { $normalized_viewport_image_url = $this->normalizeImageUrlForViewportMatch($viewport_image_url); if ($normalized_viewport_image_url === '') { continue; } if ($normalized_img_src === $normalized_viewport_image_url) { return $viewport_image_url; } } } return false; } public function isViewportImage($image, $image_config) { return (bool) $this->getMatchedViewportImageUrl($image, $image_config); } private function getViewportImageHydrationAttrs($image_url, $image_config) { foreach ($image_config['viewport_images_info'] as $device => $viewport_images) { if (!is_array($viewport_images) || !isset($viewport_images[$image_url]) || !is_array($viewport_images[$image_url])) { continue; } $attrs_to_add = isset($viewport_images[$image_url]['attrs_to_add']) ? $viewport_images[$image_url]['attrs_to_add'] : false; if (!$attrs_to_add || !is_array($attrs_to_add)) { continue; } return $attrs_to_add; } return array(); } public function hydrateViewportImage($image, $image_config) { if (empty($image)) { return $image; } if (strpos($image, 'bv-data-mobile-src=') !== false || strpos($image, 'bv-data-large-src=') !== false) { return $this->addBvClass($image, 'bv-viewport-img'); } ALHelper::safePregMatch('#\ssrc\s*=\s*(\'|")(?<src>.*)\1#Uis', $image, $matches); $src = trim($matches['src']); if ($src === '' || strpos($src, 'data:image') === 0) { return $image; } $matched_viewport_image_url = $this->getMatchedViewportImageUrl($image, $image_config); if (!$matched_viewport_image_url) { return $image; } $viewport_hydration_attrs = $this->getViewportImageHydrationAttrs($matched_viewport_image_url, $image_config); if (empty($viewport_hydration_attrs)) { return $image; } $final_image = $this->addBvClass($image, 'bv-viewport-img'); $width = $this->imageWidth($final_image); $height = $this->imageHeight($final_image); $attr_string = ''; foreach ($viewport_hydration_attrs as $attr_name => $attr_value) { if (empty($attr_value)) { continue; } $attr_string .= ' ' . $attr_name . '="' . esc_attr($attr_value) . '"'; } if ($attr_string === '') { return $image; } $final_image = ALHelper::safeStrReplace('<img', '<img' . $attr_string . ' ', $final_image); $final_image = ALHelper::safeStrReplace($matches[0], ' src="' . $this->getSvgSource($width, $height) . '"', $final_image); if (ALHelper::safePregMatch('#\ssrcset\s*=\s*(\'|")(?<srcset>.*)\1#Uis', $final_image, $srcset)) { $final_image = ALHelper::safeStrReplace($srcset[0], '', $final_image); } return $final_image; } private function addImageAttr($image, $image_config) { $img_src = $this->get_src_image_url($image); if (!empty($img_src)) { if (isset($image_config['viewport_images_info']['mobile'][$img_src])) { $height = $image_config['viewport_images_info']['mobile'][$img_src]['height']; $width = $image_config['viewport_images_info']['mobile'][$img_src]['width']; if (isset($height) && isset($width)) { if (ALHelper::safePregMatch('#\sstyle\s*=\s*(?<styles>("|\').*\2)#is', $image, $style)) { $new_style = trim($style['styles'], '\'" '); if (strpos($new_style, "min-width") === false && strpos($new_style, "min-height") === false) { $new_style = "min-width:$width" . "px;" . " min-height:$height" . "px; " . $new_style; $image = ALHelper::safeStrReplace($style[0], 'style="' . $new_style . '"', $image); } } else { $image = ALHelper::safeStrReplace('<img', '<img style="min-height:' . $height . 'px; min-width:' . $width . 'px;"', $image); } } } } return $image; } public function handleImgTags($buffer, $image_config) { if (isset($image_config['picture_tags']) && is_array($image_config['picture_tags'])) { $buffer = $this->doSearchAndReplace($buffer, $image_config['picture_tags']); } $img_buffer_sanitizer_regexes = $this->config['img_buffer_sanitizer_regexes']; if (isset($img_buffer_sanitizer_regexes) && is_array($img_buffer_sanitizer_regexes)) { $cleaned_buffer = $this->cleanBuffer($this->config['img_buffer_sanitizer_regexes'], $buffer); } else { $cleaned_buffer = $buffer; } $html_without_picture_tag = preg_replace('/<picture[^>]*>.*?<\/picture\s*>/mis', '', $cleaned_buffer); if (preg_match_all('#<img\s.+\s?/?>#Uis', $html_without_picture_tag, $images, PREG_SET_ORDER)) { foreach ($images as $image) { if ($this->hasNoActionAttribute($image[0])) continue; $final_image = $image; $lazyload_image = true; $is_viewport_hydration_image = false; if (array_key_exists('replace_images', $image_config) && !empty($image_config['replace_images'])) { $final_image[0] = $this->doSearchAndReplace($final_image[0], $image_config['replace_images']); if ($final_image[0] !== $image[0]) $lazyload_image = false; } if (array_key_exists('viewport_images_info', $image_config) && array_key_exists('add_image_attributes', $image_config) && $image_config['add_image_attributes']) { $final_image[0] = $this->addImageAttr($final_image[0], $image_config); } if (isset($image_config['use_viewport_image_hydration']) && $image_config['use_viewport_image_hydration'] && $this->isViewportImage($final_image[0], $image_config)) { $final_image[0] = $this->hydrateViewportImage($final_image[0], $image_config); $lazyload_image = false; $is_viewport_hydration_image = true; } if (!$is_viewport_hydration_image) { $final_image[0] = $this->replaceSrcAndSrcsetImages($final_image[0], $image_config['replace_urls'], 'bv-tag-attr-replace'); if ($lazyload_image && $this->config["lazyload_non_viewport_img_tags"]) { $final_image[0] = $this->lazyloadImage($final_image[0], $image_config); } } $buffer = ALHelper::safeStrReplace($image[0], $final_image[0], $buffer); } } return $buffer; } public function replaceStyleAttributeImages($element, $replace_urls, $tag) { if (ALHelper::safePregMatch('#\sstyle\s*=\s*(\'|").*?\1#is', $element, $styles)) { if (ALHelper::safePregMatch('#background-image\s*:.*(?<attr>\s*url\s*\((?<url>[^)]+)\))\s*;?#is', $styles[0], $url)) { $element = $this->addBvClass($element, 'bv-style-attr-replace'); $url['url'] = $this->getCleanUrl($url['url']); $cdn_image_url = $this->getImageCdnUrl($url['url'], $replace_urls); $element = ALHelper::safeStrReplace('<' . $tag, '<' . $tag . ' bv-style-url=' . '"' . $cdn_image_url . '" ', $element); $element = ALHelper::safeStrReplace($url['url'], $cdn_image_url, $element); } } return $element; } public function handleOtherTagImages($buffer, $image_config) { if (preg_match_all('/(<(?<tag_name>div|figure|section|span|li|a|article|label)\s+([^>]+[\'"\s])?(style\s*=\s*([\'\"])(.*?)\4)?([^>]*)>)/',$buffer, $elements, PREG_SET_ORDER)) { foreach ($elements as $element) { $final_element = $element; if ($this->hasEmbeddedImage($element[0])) continue; $final_element[0] = $this->replaceStyleAttributeImages($final_element[0], $image_config['replace_urls'], $element["tag_name"]); $final_element[0] = $this->replaceSrcAndSrcsetImages($final_element[0], $image_config['replace_urls'], 'bv-tag-attr-replace'); $buffer = ALHelper::safeStrReplace($element[0], $final_element[0], $buffer); } } $buffer = $this->lazyloadTagImages($buffer, $image_config); return $buffer; } public function handleImages($buffer, $image_config, $url_matched) { if (!is_array($image_config) || empty($image_config)) { return $buffer; } $buffer = $this->handlePictureTags($buffer, $image_config, $url_matched); $buffer = $this->handleImgTags($buffer, $image_config); if (isset($this->config["optimize_embed_style_tag_images"]) && $this->config["optimize_embed_style_tag_images"]) { $buffer = $this->handleOtherTagImages($buffer, $image_config); } if ($url_matched && isset($image_config['preload_images']) && is_array($image_config['preload_images'])) { $buffer = $this->preloadImages($buffer, $image_config['preload_images']); } return $buffer; } private function isInlineScript($script) { if (preg_match('/<script\b([^>]*)>/i', $script, $matches)) { return !preg_match('/\bsrc\s*=/i', $matches[1]); } return false; } } endif;
Save
Close
Exit & Reset
Text mode: syntax highlighting auto-detects file type.
Directory Contents
Dirs: 0 × Files: 5
Delete Selected
Select All
Select None
Sort:
Name
Size
Modified
Enable drag-to-move
Name
Size
Perms
Modified
Actions
cache.php
20.86 KB
lrw-r--r--
2026-04-02 01:31:44
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
helper.php
1.83 KB
lrw-r--r--
2026-04-02 01:31:44
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
optimizer.php
89.70 KB
lrw-r--r--
2026-04-02 01:31:44
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
third_party_caching.php
561 B
lrw-r--r--
2026-04-02 01:31:44
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
validator.php
10.61 KB
lrw-r--r--
2026-04-02 01:31:44
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
Zip Selected
If ZipArchive is unavailable, a
.tar
will be created (no compression).