Changeset 3412704
- Timestamp:
- 12/06/2025 12:41:22 AM (2 months ago)
- Location:
- jch-optimize/trunk
- Files:
-
- 49 edited
-
jch-optimize.php (modified) (4 diffs)
-
lib/src/Admin/Ajax/Ajax.php (modified) (2 diffs)
-
lib/src/Cdn/Cdn.php (modified) (1 diff)
-
lib/src/Css/Callbacks/AbstractCallback.php (modified) (5 diffs)
-
lib/src/Css/Callbacks/CorrectUrls.php (modified) (8 diffs)
-
lib/src/Css/Callbacks/ExtractCriticalCss.php (modified) (2 diffs)
-
lib/src/Css/Components/CssRule.php (modified) (2 diffs)
-
lib/src/Css/Components/CssSelector.php (modified) (3 diffs)
-
lib/src/Css/Components/CssSelectorList.php (modified) (1 diff)
-
lib/src/Css/Components/CssUrl.php (modified) (1 diff)
-
lib/src/Css/Components/NestingAtRule.php (modified) (4 diffs)
-
lib/src/Css/CssProcessor.php (modified) (4 diffs)
-
lib/src/Css/Parser.php (modified) (2 diffs)
-
lib/src/Css/Xpath/CssSelector.php (modified) (3 diffs)
-
lib/src/Css/Xpath/PseudoClassSelector.php (modified) (1 diff)
-
lib/src/FileInfo.php (modified) (1 diff)
-
lib/src/FileUtils.php (modified) (2 diffs)
-
lib/src/Html/AttributesCollection.php (modified) (2 diffs)
-
lib/src/Html/BuildHtmlElement.php (modified) (7 diffs)
-
lib/src/Html/CacheManager.php (modified) (9 diffs)
-
lib/src/Html/Callbacks/AbstractCallback.php (modified) (1 diff)
-
lib/src/Html/Callbacks/CombineJsCss.php (modified) (2 diffs)
-
lib/src/Html/CssLayout/CssItem.php (modified) (1 diff)
-
lib/src/Html/CssLayout/CssLayoutPlanner.php (modified) (1 diff)
-
lib/src/Html/CssLayout/CssPlacementItem.php (modified) (1 diff)
-
lib/src/Html/CssLayout/CssPlacementPlan.php (modified) (1 diff)
-
lib/src/Html/Elements/BaseElement.php (modified) (1 diff)
-
lib/src/Html/FilesManager.php (modified) (16 diffs)
-
lib/src/Html/HtmlElementBuilder.php (modified) (1 diff)
-
lib/src/Html/HtmlManager.php (modified) (15 diffs)
-
lib/src/Html/HtmlProcessor.php (modified) (8 diffs)
-
lib/src/Html/JsLayout/JsItem.php (modified) (1 diff)
-
lib/src/Html/JsLayout/JsLayoutPlanner.php (modified) (1 diff)
-
lib/src/Html/JsLayout/JsPlacementItem.php (modified) (1 diff)
-
lib/src/Html/Parser.php (modified) (8 diffs)
-
lib/src/Optimize.php (modified) (5 diffs)
-
lib/src/PageCache/PageCache.php (modified) (1 diff)
-
lib/src/Preloads/Http2Preload.php (modified) (1 diff)
-
lib/src/Preloads/Preconnector.php (modified) (4 diffs)
-
lib/src/Preloads/PreloadsCollection.php (modified) (4 diffs)
-
readme.txt (modified) (2 diffs)
-
src/Html/Renderer/Setting.php (modified) (4 diffs)
-
src/Html/TabSettings.php (modified) (2 diffs)
-
src/Plugin/Installer.php (modified) (2 diffs)
-
src/Plugin/Loader.php (modified) (1 diff)
-
src/View/OptimizeImageHtml.php (modified) (3 diffs)
-
tmpl/pagecache_filters.php (modified) (3 diffs)
-
vendor/composer/installed.php (modified) (2 diffs)
-
version.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
jch-optimize/trunk/jch-optimize.php
r3402713 r3412704 5 5 * Plugin URI: http://www.jch-optimize.net/ 6 6 * Description: Boost your WordPress site's performance with JCH Optimize as measured on PageSpeed 7 * Version: 5.1. 07 * Version: 5.1.1 8 8 * Author: Samuel Marshall 9 9 * License: GNU/GPLv3 … … 11 11 * Domain Path: /languages 12 12 * Requires PHP: 8.0 13 * Requires at least: 5.013 * Requires at least: 6.5.0 14 14 */ 15 15 … … 33 33 $message = sprintf( 34 34 __( 35 'JCH Optimize requires at least PHP 8.0. You current version is %s. Please update your PHP version or deactivate the plugin.', 35 'JCH Optimize requires at least PHP 8.0.' 36 . ' Your current version is %s. Please update your PHP version or deactivate the plugin.', 36 37 'jch-optimize' 37 38 ), … … 86 87 { 87 88 $message = __( 88 'An error occurred while trying to initialize the JCH Optimize plugin. Please deactivate the plugin and report all errors to the developer.', 89 'An error occurred while trying to initialize the JCH Optimize plugin.' 90 . ' Please deactivate the plugin and report all errors to the developer.', 89 91 'jch-optimize' 90 92 ); -
jch-optimize/trunk/lib/src/Admin/Ajax/Ajax.php
r3310120 r3412704 23 23 use JchOptimize\Core\Admin\AdminHelper; 24 24 use JchOptimize\Core\Admin\Json; 25 use JchOptimize\Core\Optimize; 25 26 use JchOptimize\Core\Platform\PathsInterface; 26 27 use JchOptimize\Core\Platform\UtilityInterface; … … 56 57 protected function __construct() 57 58 { 58 ini_set('pcre.backtrack_limit', '1000000'); 59 ini_set('pcre.recursion_limit', '1000000'); 59 Optimize::setPcreLimits(); 60 60 61 61 if (!JCH_DEVELOP) { 62 62 error_reporting(0); 63 63 @ini_set('display_errors', 'Off'); 64 }65 66 if (version_compare(PHP_VERSION, '7.0.0', '>=')) {67 ini_set('pcre.jit', '0');68 64 } 69 65 -
jch-optimize/trunk/lib/src/Cdn/Cdn.php
r3310120 r3412704 122 122 $staticFiles1Array = array_merge($staticFiles1Array, $customExtArray); 123 123 124 $this->domains-> attach(new CdnDomain($domain1, $staticFiles1Array, $this->getScheme()));124 $this->domains->offsetSet(new CdnDomain($domain1, $staticFiles1Array, $this->getScheme())); 125 125 } 126 126 -
jch-optimize/trunk/lib/src/Css/Callbacks/AbstractCallback.php
r3402713 r3412704 37 37 38 38 protected array $conditionalAtRules = [ 39 'media' => true,40 'supports' => true,41 'layer' => true,42 'scope' => true,43 'container' => true,44 'document' => true39 'media', 40 'supports', 41 'layer', 42 'scope', 43 'container', 44 'document', 45 45 ]; 46 46 … … 60 60 61 61 private ?CriticalCssDomainProfiler $profiler = null; 62 63 private string $conditionalAtRulesRegex; 62 64 63 65 public function __construct(Container $container, protected Registry $params) … … 66 68 $this->cacheObject = new CacheObject(); 67 69 $this->supportedComponents = $this->supportedCssComponents(); 70 $this->conditionalAtRulesRegex = '^@(?:' . implode('|', $this->conditionalAtRules) . ')'; 68 71 } 69 72 … … 100 103 } 101 104 102 try{105 if (preg_match("#{$this->conditionalAtRulesRegex}#", $matches[0])) { 103 106 $n = 'nesting_at_rule_load'; 104 107 $this->profiler?->start($n); 105 $nestingAtRule = NestingAtRule::load ($matches[0]);108 $nestingAtRule = NestingAtRule::loadFromMatch($matches); 106 109 $this->profiler?->stop($n); 107 110 108 if (isset($this->conditionalAtRules[$nestingAtRule->getIdentifier()])) { 109 $this->incrementRecursion(); 110 $processedContent = $this->getParser()->processMatchesWithCallback( 111 $nestingAtRule->getCssRuleList(), 112 $this 113 ); 114 $this->decrementRecursion($nestingAtRule); 115 116 return $nestingAtRule->setCssRuleList($processedContent)->render(); 117 } 118 } catch (InvalidArgumentException) { 111 $this->incrementRecursion(); 112 $processedContent = $this->getParser()->processMatchesWithCallback( 113 $nestingAtRule->getCssRuleList(), 114 $this 115 ); 116 $this->decrementRecursion($nestingAtRule); 117 118 return $nestingAtRule->setCssRuleList($processedContent)->render(); 119 119 } 120 120 … … 123 123 $d = 'component_load'; 124 124 $this->profiler?->start($d); 125 $cssComponent = $component::load($matches[0]); 125 if (method_exists($component, 'loadFromMatch')) { 126 $cssComponent = $component::loadFromMatch($matches); 127 } else { 128 $cssComponent = $component::load($matches[0]); 129 } 126 130 $this->profiler?->stop($d); 127 131 } catch (InvalidArgumentException) { -
jch-optimize/trunk/lib/src/Css/Callbacks/CorrectUrls.php
r3402713 r3412704 103 103 { 104 104 if ( 105 $originalUri->getScheme() !== 'data' 106 && $originalUri->getPath() != '' 107 && $originalUri->getPath() != '/' 108 ) { 109 $imageUri = $this->resolveImageToFileUrl($originalUri); 110 $paths = $this->getContainer()->get(PathsInterface::class); 111 112 if (UriComparator::existsLocally($imageUri, $this->cdn, $paths)) { 113 $this->cacheImageForAdminUsers($imageUri); 114 $imageUri = $this->cdn->loadCdnResource($imageUri); 115 } elseif ($this->params->get('pro_preconnect_domains_enable', '0')) { 116 $this->prefetchExternalDomains($imageUri); 117 } 118 119 if ($this->context == 'css-rule') { 120 $imageUri = $this->applyFeatureHelpers($imageUri); 121 } 122 123 $this->addHttpPreloadsToCacheObject($imageUri); 124 125 return $imageUri; 126 } 127 128 return $originalUri; 105 $originalUri->getScheme() === 'data' 106 || trim($originalUri->getPath(), " \n\r\t\v\x00/") === '' 107 ) { 108 return $originalUri; 109 } 110 111 $imageUri = $this->resolveImageToFileUrl($originalUri); 112 $paths = $this->getContainer()->get(PathsInterface::class); 113 114 if (UriComparator::existsLocally($imageUri, $this->cdn, $paths)) { 115 $this->cacheImageForAdminUsers($imageUri); 116 $imageUri = $this->cdn->loadCdnResource($imageUri); 117 } elseif ($this->params->get('pro_preconnect_domains_enable', '0')) { 118 $this->prefetchExternalDomains($imageUri); 119 } 120 121 if ($this->context == 'css-rule') { 122 $imageUri = $this->applyFeatureHelpers($imageUri); 123 } 124 125 $this->addHttpPreloadsToCacheObject($imageUri); 126 127 return $imageUri; 129 128 } 130 129 … … 134 133 $cssFileUri = $this->getCssInfo()->hasUri() ? $this->getCssInfo()->getUri() : new Uri(); 135 134 $cssFileUri = UriResolver::resolve(SystemUri::currentUri(), $cssFileUri); 135 136 136 return UriResolver::resolve($cssFileUri, $originalUri); 137 137 } … … 165 165 $responsiveImages = $this->getContainer()->get(ResponsiveImages::class) 166 166 /** @see ResponsiveImages::getResponsiveImages() */ 167 ->getResponsiveImages($imageUri);167 ->getResponsiveImages($imageUri); 168 168 } 169 169 … … 184 184 $this->getContainer()->get(LCPImages::class) 185 185 /** @see LCPImages::prepareBackgroundLcpImages() */ 186 ->prepareBackgroundLcpImages($imageUri, $this->cacheObject);186 ->prepareBackgroundLcpImages($imageUri, $this->cacheObject); 187 187 } 188 188 … … 200 200 $this->getContainer()->get(LazyLoadExtended::class) 201 201 /** @see LazyLoadExtended::handleCssBgImages() */ 202 ->handleCssBgImages(203 $this,204 $cssRule,205 $lazyLoaded206 );202 ->handleCssBgImages( 203 $this, 204 $cssRule, 205 $lazyLoaded 206 ); 207 207 } 208 208 } … … 230 230 [ 231 231 'src' => $imageUri, 232 'as' => $fileType232 'as' => $fileType 233 233 ] 234 234 ]; … … 236 236 $cacheItems = $this->getContainer()->get(ResponsiveImages::class) 237 237 /** @see ResponsiveImages::mergeResponsiveImageCacheItems() */ 238 ->mergeResponsiveImageCacheItems($cacheItems);238 ->mergeResponsiveImageCacheItems($cacheItems); 239 239 } 240 240 foreach ($cacheItems as $cacheItem) { … … 261 261 $this->getContainer()->get(ResponsiveImages::class) 262 262 /** @see ResponsiveImages::makeCssRuleResponsive() */ 263 ->makeCssRuleResponsive($cssRule);263 ->makeCssRuleResponsive($cssRule); 264 264 } 265 265 } -
jch-optimize/trunk/lib/src/Css/Callbacks/ExtractCriticalCss.php
r3402713 r3412704 169 169 $r = 'xpath_render'; 170 170 $profiler?->start($r); 171 $xPath = $cssSelectorXpath->render ();171 $xPath = $cssSelectorXpath->renderFirstPerBranch(); 172 172 $profiler?->stop($r); 173 173 … … 253 253 $profiler = $this->dependencies->getProfiler(); 254 254 $profiler?->start($normal = 'selector_normalize'); 255 $normalized = preg_replace('#\s +#', '', strtolower($selectorList));255 $normalized = preg_replace('#\s*([,>+~])\s*#', '\1', strtolower($selectorList)); 256 256 $profiler?->stop($normal); 257 257 -
jch-optimize/trunk/lib/src/Css/Components/CssRule.php
r3402713 r3412704 52 52 } 53 53 54 $selectorList = $matches['selectorlist']; 55 $declarationList = $matches['declarationlist']; 54 return self::loadFromMatch($matches); 55 } 56 57 public static function loadFromMatch(array $matches): static 58 { 59 // Fallback to old behaviour if groups are missing. 60 if (!isset($matches['selectorList'], $matches['declarationList'])) { 61 return static::load($matches[0]); 62 } 63 64 $selectorList = $matches['selectorList']; 65 $declarationList = $matches['declarationList']; 56 66 57 67 return new static($selectorList, $declarationList); … … 67 77 } 68 78 69 p rivatestatic function cssRuleWithCaptureValueToken(): string79 public static function cssRuleWithCaptureValueToken(): string 70 80 { 71 81 $selectors = self::cssSelectorListToken(); 72 73 return "(?<selectorlist>{$selectors}){(?<declarationlist>.*)}"; 82 $declarations = self::cssDeclarationListToken(); 83 84 return "(?<selectorList>{$selectors}){(?<declarationList>{$declarations})}"; 74 85 } 75 86 -
jch-optimize/trunk/lib/src/Css/Components/CssSelector.php
r3402713 r3412704 190 190 $classes = new ClassCollection(); 191 191 foreach ($this->classes as $class) { 192 $classes-> attach(clone $class);192 $classes->offsetSet(clone $class); 193 193 } 194 194 $this->classes = $classes; … … 198 198 $attributes = new AttributeCollection(); 199 199 foreach ($this->attributes as $attribute) { 200 $attributes-> attach(clone $attribute);200 $attributes->offsetSet(clone $attribute); 201 201 } 202 202 $this->attributes = $attributes; … … 206 206 $pseudoClasses = new PseudoClassCollection(); 207 207 foreach ($this->pseudoClasses as $pseudoSelector) { 208 $pseudoClasses-> attach(clone $pseudoSelector);208 $pseudoClasses->offsetSet(clone $pseudoSelector); 209 209 } 210 210 $this->pseudoClasses = $pseudoClasses; -
jch-optimize/trunk/lib/src/Css/Components/CssSelectorList.php
r3402713 r3412704 67 67 68 68 foreach ($this->selectors as $selector) { 69 $selectors-> attach(clone $selector);69 $selectors->offsetSet(clone $selector); 70 70 } 71 71 -
jch-optimize/trunk/lib/src/Css/Components/CssUrl.php
r3310120 r3412704 80 80 81 81 return "url\(\s*+(?<delimiter>['\"]?)(?<url>" 82 . "(?<=\")(?>[^\"\\\\]++|{$esc})++|(?<=')(?>[^'\\\\]++|{$esc})++|(?>[^)\\\\]++|{$esc}) ++"82 . "(?<=\")(?>[^\"\\\\]++|{$esc})++|(?<=')(?>[^'\\\\]++|{$esc})++|(?>[^)\\\\]++|{$esc})*?" 83 83 . ")['\"]?\s*+\)"; 84 84 } -
jch-optimize/trunk/lib/src/Css/Components/NestingAtRule.php
r3310120 r3412704 4 4 * JCH Optimize - Performs several front-end optimizations for fast downloads 5 5 * 6 * @package jchoptimize/core7 * @author Samuel Marshall <[email protected]>8 * @copyright Copyright (c) 2024 Samuel Marshall / JCH Optimize9 * @license GNU/GPLv3, or later. See LICENSE file6 * @package jchoptimize/core 7 * @author Samuel Marshall <[email protected]> 8 * @copyright Copyright (c) 2024 Samuel Marshall / JCH Optimize 9 * @license GNU/GPLv3, or later. See LICENSE file 10 10 * 11 11 * If LICENSE file missing, see <http://www.gnu.org/licenses/>. … … 50 50 } 51 51 52 return self::loadFromMatch($matches); 53 } 54 55 public static function loadFromMatch(array $matches): static 56 { 57 // If for some reason we didn't get the groups, fall back. 58 if (!isset($matches['identifier'], $matches['rule'], $matches['cssRuleList'])) { 59 return static::load($matches[0]); 60 } 61 52 62 $identifier = $matches['identifier']; 53 $vendor = $matches['vendor'] ;63 $vendor = $matches['vendor'] ?? ''; 54 64 $rule = $matches['rule']; 55 65 $cssRuleList = $matches['cssRuleList']; … … 63 73 } 64 74 65 66 private static function cssNestingAtRuleWithCaptureGroupToken(): string 75 public static function cssNestingAtRuleWithCaptureGroupToken(): string 67 76 { 68 77 $esc = self::cssEscapedString(); … … 73 82 74 83 return "@(?<vendor>(?:-[^-]++-)?)(?<identifier>[a-zA-Z-]++)\s*+" 75 . "(?<rule>(?>[^{}@/\\\\'\";\su]++|{$bc}|{$esc}|{$dqStr}|{$sqStr}|{$url}|[/u]|\s++)*?)\s*+" 76 . "(?P<cssBlock>{(?<cssRuleList>(?>(?:[^{}/\\\\'\"]++|{$bc}|{$esc}|{$dqStr}|{$sqStr}|/)++|(?&cssBlock))*+)})"; 84 . "(?<rule>(?>[^{}@/\\\\'\";\su]++|{$bc}|{$esc}|{$dqStr}|{$sqStr}|{$url}|[/u]|\s++)*?)\s*+" 85 . "(?P<cssBlock>{" 86 . "(?<cssRuleList>(?>(?:[^{}/\\\\'\"]++|{$bc}|{$esc}|{$dqStr}|{$sqStr}|/)++|(?&cssBlock))*+)" 87 . "})"; 77 88 } 78 89 -
jch-optimize/trunk/lib/src/Css/CssProcessor.php
r3402713 r3412704 24 24 use JchOptimize\Core\Css\Callbacks\HandleAtRules; 25 25 use JchOptimize\Core\Css\Callbacks\PostProcessCriticalCss; 26 use JchOptimize\Core\Css\Components\CssRule; 27 use JchOptimize\Core\Css\Components\NestingAtRule; 26 28 use JchOptimize\Core\Css\Sprite\Generator; 27 29 use JchOptimize\Core\Exception; … … 131 133 $oParser = new Parser(); 132 134 $cssRule = new CssSearchObject(); 133 $cssRule->setCssMatch( Parser::cssRuleToken());135 $cssRule->setCssMatch(CssRule::cssRuleWithCaptureValueToken()); 134 136 $oParser->setCssSearchObject($cssRule); 135 137 136 138 $nestedAtRule = new CssSearchObject(); 137 $nestedAtRule->setCssMatch( Parser::cssNestingAtRulesToken());139 $nestedAtRule->setCssMatch(NestingAtRule::cssNestingAtRuleWithCaptureGroupToken()); 138 140 $oParser->setCssSearchObject($nestedAtRule); 139 141 … … 261 263 $oParser = new Parser(); 262 264 $oCssSearchObject = new CssSearchObject(); 263 $oCssSearchObject->setCssMatch( Parser::cssRuleToken());265 $oCssSearchObject->setCssMatch(CssRule::cssRuleWithCaptureValueToken()); 264 266 $oParser->setCssSearchObject($oCssSearchObject); 265 267 266 268 $atRuleSearchObject = new CssSearchObject(); 267 $atRuleSearchObject->setCssMatch( Parser::cssNestingAtRulesToken());269 $atRuleSearchObject->setCssMatch(NestingAtRule::cssNestingAtRuleWithCaptureGroupToken()); 268 270 $oParser->setCssSearchObject($atRuleSearchObject); 269 271 … … 306 308 $parser = new Parser(); 307 309 $atRuleSearchObject = new CssSearchObject(); 308 $atRuleSearchObject->setCssMatch( Parser::cssNestingAtRulesToken());310 $atRuleSearchObject->setCssMatch(NestingAtRule::cssNestingAtRuleWithCaptureGroupToken()); 309 311 $parser->setCssSearchObject($atRuleSearchObject); 310 312 -
jch-optimize/trunk/lib/src/Css/Parser.php
r3310120 r3412704 59 59 60 60 $sProcessedCss = preg_replace_callback( 61 '#' . $regex . '#si x',61 '#' . $regex . '#siJ', 62 62 [$callback, 'processMatches'], 63 63 $sCss … … 82 82 public function replaceMatches(string $css, string $replace): string 83 83 { 84 $processedCss = preg_replace('#' . $this->getCssSearchRegex() . '#i ', $replace, $css);84 $processedCss = preg_replace('#' . $this->getCssSearchRegex() . '#iJ', $replace, $css); 85 85 86 86 self::throwExceptionOnPregError(); -
jch-optimize/trunk/lib/src/Css/Xpath/CssSelector.php
r3402713 r3412704 17 17 18 18 use function in_array; 19 use function preg_match; 19 20 20 21 class CssSelector extends XpathCssSelector … … 23 24 { 24 25 return $this->type 25 || $this->id26 || (int)$this->classes?->count() > 027 || (int)$this->attributes?->count() > 028 || (int)$this->pseudoClasses?->count() > 029 || $this->pseudoElement30 || $this->descendant !== null;26 || $this->id 27 || (int)$this->classes?->count() > 0 28 || (int)$this->attributes?->count() > 0 29 || (int)$this->pseudoClasses?->count() > 0 30 || $this->pseudoElement 31 || $this->descendant !== null; 31 32 } 32 33 … … 59 60 } 60 61 61 public function render(?string $axis = null, ?string $indexPredicate = null): string 62 /** 63 * Render each top-level CSS selector as XPath, optionally restricted to 64 * the first match of each branch by appending [1]. 65 * 66 * Pseudo-selectors inside the branch stay unchanged because we only 67 * post-process the top-level branch string returned by CssSelector::render(). 68 */ 69 public function renderFirstPerBranch(?string $axis = null): string 62 70 { 63 $indexPredicate = $indexPredicate ?? '[1]'; 71 // This will always be your CssSelector subclass, but keep it generic. 72 $xpath = $this->render($axis); 64 73 65 return parent::render() . $indexPredicate; 74 return $this->appendFirstPredicate($xpath); 75 } 76 77 /** 78 * Append [1] to the end of the branch if it’s not already there. 79 * 80 * Because CssSelector::render() always returns something of the form: 81 * <axis>::<node>[pred][pred]... 82 * this "[1]" applies to the whole node-set for that branch. 83 */ 84 private function appendFirstPredicate(string $xpath): string 85 { 86 // Be defensive: don’t double-up if some other caller already did this. 87 if ($xpath === '' || preg_match('/\[\s*1\s*]$/', $xpath)) { 88 return $xpath; 89 } 90 91 return $xpath . '[1]'; 66 92 } 67 93 } -
jch-optimize/trunk/lib/src/Css/Xpath/PseudoClassSelector.php
r3402713 r3412704 97 97 return $rendered !== '' ? $rendered : "[false()]"; 98 98 } 99 100 /**101 * Critical CSS tweak on top of the base :not(...) transform:102 * drop a trailing [1] that can accidentally over-restrict a NOT predicate.103 */104 protected function renderNotSelectorList(): string105 {106 return $this->getSelectorList()?->render('self', '');107 }108 99 } -
jch-optimize/trunk/lib/src/FileInfo.php
r3402713 r3412704 40 40 private ?bool $aboveFold = null; 41 41 42 public function __construct(protected HtmlElementInterface|CssComponents $element) 43 { 42 public function __construct( 43 protected HtmlElementInterface|CssComponents $element, 44 protected ?bool $isSensitive = false 45 ) { 44 46 $this->applyParts($element); 45 47 } -
jch-optimize/trunk/lib/src/FileUtils.php
r3402713 r3412704 62 62 $path = $newUri->getPath(); 63 63 64 if ($path === '/' && ($query = $newUri->getQuery()) !== '') { 65 $queryLen = strlen($query); 66 if ($queryLen > $length - 1) { 67 $query = substr($query, 0, -($queryLen - $length + 4)) . '...'; 68 } 69 70 $path .= '?' . $query; 71 } 72 64 73 if (UriComparator::isCrossOrigin($newUri)) { 65 74 $domain = $newUri->withPort(null)->withPath('')->withQuery('')->withFragment(''); … … 102 111 } 103 112 104 return (string) $uri->withQuery('')->withFragment(''); 113 $query = ''; 114 if ($uri->getPath() === '/' && ($query = $uri->getQuery()) !== '') { 115 $queryLen = strlen($query); 116 $query = substr($query, 0, -($queryLen - 60)); 117 } 118 119 return (string) $uri->withQuery($query)->withFragment(''); 105 120 } 106 121 -
jch-optimize/trunk/lib/src/Html/AttributesCollection.php
r3402713 r3412704 94 94 $attribute = new Attribute($name, $value, $delimiter); 95 95 96 $this-> attach($attribute);96 $this->offsetSet($attribute); 97 97 } 98 98 … … 136 136 137 137 if ($attribute->getName() == $name) { 138 $this-> detach($attribute);138 $this->offsetUnset($attribute); 139 139 140 140 return; -
jch-optimize/trunk/lib/src/Html/BuildHtmlElement.php
r3310120 r3412704 15 15 16 16 use JchOptimize\Core\Exception\PregErrorException; 17 use JchOptimize\Core\Exception\RuntimeException; 17 18 use JchOptimize\Core\Html\Elements\BaseElement; 19 use LogicException; 18 20 19 21 use function preg_match; … … 26 28 protected string $regex = ''; 27 29 28 protected BaseElement $element;30 protected ?BaseElement $element = null; 29 31 30 32 /** … … 34 36 { 35 37 $elementRegex = self::htmlElementWithCaptureValueToken(); 36 preg_match("#^{$elementRegex}$#s", $html, $matches); 38 $result = preg_match("#^{$elementRegex}$#s", $html, $matches); 39 40 if ($result === false) { 41 throw new RuntimeException('Failed to parse HTML string'); 42 } 43 44 $this->buildFromMatch($matches); 45 } 46 47 /** 48 * @throws PregErrorException 49 */ 50 public function buildFromMatch(array $matches): void 51 { 52 if (!isset($matches['name'])) { 53 $this->build($matches[0]); 54 55 return; 56 } 37 57 38 58 $name = strtolower($matches['name']); 39 59 $this->element = HtmlElementBuilder::$name(); 40 60 61 $attrsText = $matches['attributes'] ?? ''; 62 if ($attrsText !== '') { 63 $this->loadAttributesFromText($attrsText); 64 } 65 66 if (!empty($matches['content'])) { 67 $this->loadChildren($matches['content']); 68 } 69 70 if (empty($matches['endTag'])) { 71 $this->element->setOmitClosingTag(true); 72 } 73 } 74 75 private function loadAttributesFromText(string $attributesText): void 76 { 41 77 $attributesRegex = self::htmlAttributeWithCaptureValueToken(); 78 42 79 preg_match_all( 43 80 '#' . $attributesRegex . '#ix', 44 $ matches['attributes'] ?? '',81 $attributesText, 45 82 $attributes, 46 83 PREG_SET_ORDER … … 52 89 $delimiter = $attribute['delimiter'] ?? '"'; 53 90 54 $this->element->attribute($name, $value, $delimiter); 55 } 56 57 if (isset($matches['content'])) { 58 $this->loadChildren($matches['content']); 59 } else { 60 $this->element->setOmitClosingTag(true); 91 $this->element?->attribute($name, $value, $delimiter); 61 92 } 62 93 } 63 94 64 public function getElement(): HtmlElementInterface 95 96 public function getElement(): BaseElement 65 97 { 98 if ($this->element === null) { 99 throw new LogicException('Element not set'); 100 } 101 66 102 return $this->element; 67 103 } … … 73 109 $endTag = Parser::htmlEndTagToken(Parser::htmlGenericElementNameToken()); 74 110 75 return "<(?<name>{$name})\b( \s++(?<attributes>{$attributes}+))?/?>(?:(?<content>.*){$endTag})?";111 return "<(?<name>{$name})\b(?:\s++(?<attributes>{$attributes}+))?/?>(?:(?<content>.*)(?<endTag>{$endTag}))?"; 76 112 } 77 113 … … 104 140 $lc = Parser::lineCommentToken(); 105 141 //Regular expression literal 106 $rx = '/(?![/*])(?>(?(?=\\\\)\\\\.|\[(?>(?:\\\\.)?[^\]\r\n]*+)+?\])?[^\\\\/\r\n\[]*+)+?/';142 $rx = '/(?![/*])(?>(?(?=\\\\)\\\\.|\[(?>(?:\\\\.)?[^\]\r\n]*+)+?\])?[^\\\\/\r\n\[]*+)+?/'; 107 143 108 144 $htmlElementRegex = "(?:{$voidElement}|{$textElement})"; 109 145 $regex = "(?<string>(?>[^<'\"/`]++|{$bc}|{$lc}|{$rx}|{$dqStr}|{$sqStr}|{$btStr}|/|(?!{$htmlElementRegex})<)++)" 110 . "|(?<element>(?:{$voidElement}|{$textElementMatch}))";146 . "|(?<element>(?:{$voidElement}|{$textElementMatch}))"; 111 147 112 148 preg_match_all( … … 118 154 119 155 foreach ($matches as $match) { 120 if ( !empty($match['element'])) {156 if (isset($match['element'])) { 121 157 $child = HtmlElementBuilder::load($match['element']); 122 $child->setParent($this-> element->getElementName());123 $this-> element->addChild($child);124 } elseif ( !empty($match['string'])) {125 $this-> element->addChild($match['string']);158 $child->setParent($this->getElement()->getElementName()); 159 $this->getElement()->addChild($child); 160 } elseif (isset($match['string'])) { 161 $this->getElement()->addChild($match['string']); 126 162 } 127 163 } -
jch-optimize/trunk/lib/src/Html/CacheManager.php
r3402713 r3412704 130 130 $combinedByGroup = []; 131 131 $cacheObjByGroup = []; 132 $sensitiveCacheObjByOrds = []; 132 133 $belowFoldFontsEl = null; 133 134 $reducedBundleEl = null; … … 136 137 $reduceUnusedCss = $this->params->get('pro_reduce_unused_css', '0'); 137 138 138 $layoutPlanner = $this->cssLayoutPlanner->plan( 139 $this->filesManager->cssTimeLine, 140 $optimizeDelivery, 141 $reduceUnusedCss 142 ); 139 $cssTimeLine = $this->filesManager->cssTimeLine; 140 $layoutPlanner = $this->cssLayoutPlanner->plan($cssTimeLine, $optimizeDelivery, $reduceUnusedCss); 141 142 if ($optimizeDelivery) { 143 foreach ($cssTimeLine as $item) { 144 if ($item->isSensitive) { 145 $fileInfo = new FileInfo(clone $item->node); 146 $cacheObj = $this->getCombinedFiles([$fileInfo], $id, 'css', false, $hit); 147 $this->updateOptimizeCssDelivery($cacheObj, $item->node, $hit, false); 148 $sensitiveCacheObjByOrds[$item->ordinal] = $cacheObj; 149 } 150 } 151 } 143 152 144 153 /** 145 * @var int $cssLinksKey154 * @var int $cssLinksKey 146 155 * @var FileInfo[] $cssInfosArray 147 156 */ … … 195 204 $combinedByGroup, 196 205 $cacheObjByGroup, 206 $sensitiveCacheObjByOrds, 197 207 $belowFoldFontsEl, 198 208 $reducedBundleEl … … 214 224 $combinedByGroup = []; 215 225 /** 216 * @var int $aJsLinksKey226 * @var int $aJsLinksKey 217 227 * @var FileInfo[] $jsInfosArray 218 228 */ … … 278 288 279 289 return $results; 280 } catch (Exception |LaminasCacheExceptionInterface $e) {290 } catch (Exception | LaminasCacheExceptionInterface $e) { 281 291 throw new RuntimeException('Error creating cache files: ' . $e->getMessage()); 282 292 } … … 374 384 375 385 if ( 376 in_array($name, ['id', ' integrity', 'crossorigin', 'referrerpolicy', 'nonce'])386 in_array($name, ['id', 'class', 'nonce']) 377 387 || (str_starts_with('data-', $name)) 378 388 ) { … … 472 482 if ($criticalCssAlreadyExisted) { 473 483 $this->getContainer()->get(CriticalCssDependencies::class) 474 ->addToCriticalCssAggregate($criticalCssObj->getCriticalCss())475 ->addToDynamicCriticalCssAggregate($criticalCssObj->getDynamicCriticalCss())476 ->addToPotentialCriticalCssAtRules($criticalCssObj->getPotentialCriticalCssAtRules());484 ->addToCriticalCssAggregate($criticalCssObj->getCriticalCss()) 485 ->addToDynamicCriticalCssAggregate($criticalCssObj->getDynamicCriticalCss()) 486 ->addToPotentialCriticalCssAtRules($criticalCssObj->getPotentialCriticalCssAtRules()); 477 487 } 478 488 … … 496 506 $cssProcessor->setCssInfos(new FileInfo($element)); 497 507 $this->getContainer()->get(CriticalCssDependencies::class) 498 ->addToPotentialCriticalCssAtRules($cssProcessor->getCacheObj()->getPotentialCriticalCssAtRules());508 ->addToPotentialCriticalCssAtRules($cssProcessor->getCacheObj()->getPotentialCriticalCssAtRules()); 499 509 $cssProcessor->optimizeCssDelivery(); 500 510 … … 516 526 { 517 527 $style = HtmlElementBuilder::style() 518 ->addChild($cssCacheObj->getBelowFoldFontsKeyFrame());528 ->addChild($cssCacheObj->getBelowFoldFontsKeyFrame()); 519 529 $fileInfo = new FileInfo($style); 520 530 $fileInfo->setAlreadyProcessed(true); -
jch-optimize/trunk/lib/src/Html/Callbacks/AbstractCallback.php
r3310120 r3412704 58 58 59 59 try { 60 $element = HtmlElementBuilder::load ($matches[0]);60 $element = HtmlElementBuilder::loadFromMatch($matches); 61 61 } catch (PregErrorException) { 62 62 return $matches[0]; -
jch-optimize/trunk/lib/src/Html/Callbacks/CombineJsCss.php
r3402713 r3412704 178 178 if ($element instanceof Script && $element->hasAttribute('src')) { 179 179 if (Helper::uriInvalid($element->getSrc())) { 180 return $element->render();180 return ''; 181 181 } 182 182 } … … 184 184 if ($element instanceof Link && $element->hasAttribute('href')) { 185 185 if (Helper::uriInvalid($element->getHref())) { 186 return $element->render();186 return ''; 187 187 } 188 188 } -
jch-optimize/trunk/lib/src/Html/CssLayout/CssItem.php
r3402713 r3412704 26 26 public bool $isInline, // <style> vs <link> 27 27 public bool $isMarker, 28 public bool $isSensitive, 28 29 public ?string $media, // media attr, if any 29 30 public Link|Style|null $node // original element -
jch-optimize/trunk/lib/src/Html/CssLayout/CssLayoutPlanner.php
r3402713 r3412704 34 34 isMarker: $item->isMarker, 35 35 groupIndex: $item->groupIndex, 36 isSensitive: $item->isSensitive, 36 37 item: $item, 37 38 ); 38 39 40 // 1) Head-blocking list 39 41 // IEO / removed CSS is handled by FilesManager and never enters the plan. 40 42 // Excluded (non-processed) CSS still participates in "headBlocking" layout. 41 43 if ((!$optimizeDelivery && !$reduceUnusedCss) || $item->isExcluded) { 42 44 $plan->headBlocking[] = $placement; 43 } elseif ($optimizeDelivery && !$reduceUnusedCss && $item->isProcessed) { 45 46 continue; 47 } 48 49 // 2) Sensitive CSS handling 50 if ($item->isSensitive) { 51 // Optimize OFF: they just behave as head-blocking CSS already (above). 52 if ($optimizeDelivery) { 53 // Optimize Delivery ON: 54 // - use them for critical & dynamic-critical 55 $plan->headInlineCritical[] = $placement; 56 $plan->bodyInlineDynamicCritical[] = $placement; 57 58 // Reduce-unused OFF: also load the original file asynchronously. 59 if (!$reduceUnusedCss) { 60 $plan->bodyAsync[] = $placement; 61 } else { 62 $plan->bodySensitiveDynamic[] = $placement; 63 } 64 } 65 66 // Don't fall through into "processed group" logic 67 continue; 68 } 69 70 // 3) Normal (non-sensitive) CSS handling 71 if ($optimizeDelivery && !$reduceUnusedCss && $item->isProcessed) { 44 72 // Optimize CSS Delivery ON, Reduce Unused OFF: 45 73 // - critical CSS in head -
jch-optimize/trunk/lib/src/Html/CssLayout/CssPlacementItem.php
r3402713 r3412704 20 20 public bool $isMarker, 21 21 public ?int $groupIndex, // group index in aCss 22 public bool $isSensitive, 22 23 public ?CssItem $item // original element (for inline/excluded cases) 23 24 ) { -
jch-optimize/trunk/lib/src/Html/CssLayout/CssPlacementPlan.php
r3402713 r3412704 46 46 public array $bodyInlineDynamicCritical = []; 47 47 48 /** 48 /** 49 * Sensitive items that should be loaded dynamically 50 * 51 * @var CssPlacementItem[]; 52 */ 53 public array $bodySensitiveDynamic = []; 54 55 /** 49 56 * Whether we should append a “below the fold fonts” CSS block in the body. 50 57 * The actual element is created in CacheManager and passed to HtmlManager. -
jch-optimize/trunk/lib/src/Html/Elements/BaseElement.php
r3402713 r3412704 265 265 266 266 $newAttribute = new Attribute($name, $value, $delimiter); 267 $attributes-> attach($newAttribute);267 $attributes->offsetSet($newAttribute); 268 268 } 269 269 -
jch-optimize/trunk/lib/src/Html/FilesManager.php
r3402713 r3412704 78 78 79 79 /** 80 * @var bool Flagged when a sensitive CSS file is encountered 81 */ 82 protected bool $cssSensitive = false; 83 84 /** 85 * @var bool Flagged when a sensitive JS file is encountered 86 */ 87 protected bool $jsSensitive = false; 88 /** 80 89 * @var array $aCss Multidimensional array of css files to combine 81 90 */ … … 256 265 } 257 266 258 if ($this->isDuplicated($uri)) { 259 $this->replacement = ''; 260 $this->excludeCssIEO(); 267 if ($this->isSensitiveExternal($link, $uri)) { 268 $this->recordSensitiveCss($link, $media); 269 270 // No PEO/IEO excludes, no adding to $aCss; planner will later: 271 // - use it for critical CSS extraction 272 // - schedule original href async/dynamic 273 return; 261 274 } 262 275 … … 340 353 private function excludeCssPEO(Link|Style $element, ?string $media = null) 341 354 { 342 //if previous file was not excluded increment css index343 if (!$this->cssExcludedPeo && !empty($this->cssReplacements[0])) {344 $this->iIndex_css++;345 }346 347 355 $this->cssExcludedPeo = true; 348 356 $this->sCssExcludeType = 'peo'; … … 357 365 isInline: $element instanceof Style, 358 366 isMarker: $ordinal === 0, 367 isSensitive: false, 359 368 media: $media, 360 369 node: $element … … 386 395 private function updateIndex(): void 387 396 { 388 if (!$this->params->get('combine_files', '0')) { 389 $element = $this->getElement(); 390 391 if ($element instanceof Script) { 392 // Don't increase index if we're in an exclude. Index already incremented. 393 if (!$this->jsExcludedPeo && !empty($this->aJs[$this->iIndex_js] ?? [])) { 394 $this->iIndex_js++; 395 } 396 } elseif ($element instanceof Link || $element instanceof Style) { 397 // Same logic for CSS 398 if (!$this->cssExcludedPeo && !empty($this->aCss[$this->iIndex_css] ?? [])) { 399 $this->iIndex_css++; 400 } 397 $combineFiles = $this->params->get('combine_files', '0'); 398 $element = $this->getElement(); 399 400 if ($element instanceof Script && !empty($this->aJs[$this->iIndex_js] ?? [])) { 401 if (!$combineFiles) { 402 $this->iIndex_js++; 403 } elseif ($this->jsExcludedPeo || $this->jsSensitive) { 404 $this->iIndex_js++; 405 } 406 } elseif ( 407 ($element instanceof Link || $element instanceof Style) 408 && !empty($this->aCss[$this->iIndex_css] ?? []) 409 ) { 410 // Same logic for CSS 411 if (!$combineFiles) { 412 $this->iIndex_css++; 413 } elseif ($this->cssExcludedPeo || $this->cssSensitive) { 414 $this->iIndex_css++; 401 415 } 402 416 } … … 439 453 if ($this->isDuplicated($uri)) { 440 454 $this->replacement = ''; 441 $this->excludeJsIEO($script); 455 return; 456 } 457 458 // --- sensitivity check for external JS --- 459 if ($this->isSensitiveExternal($script, $uri)) { 460 $this->recordSensitiveJs($script); 461 462 // We don't process or exclude it; just keep it in the timeline for the planner. 463 return; 442 464 } 443 465 … … 483 505 isIeo: true, 484 506 isDeferred: Helper::isScriptDeferred($script), 507 isSensitive: false, 485 508 node: $script 486 509 ); … … 506 529 isIeo: false, 507 530 isDeferred: Helper::isScriptDeferred($script), 531 isSensitive: false, 508 532 node: $script 509 533 ); … … 518 542 { 519 543 $content = $script->getChildren()[0]; 544 545 //Exclude all scripts if options set 546 if ( 547 !$this->params->get('inlineScripts', '0') 548 || $this->params->get('excludeAllScripts', '0') 549 ) { 550 $this->excludeJsPEO($script); 551 } 520 552 521 553 if ($this->sectionExcludes !== null) { … … 536 568 } 537 569 538 //Exclude all scripts if options set539 if (540 !$this->params->get('inlineScripts', '0')541 || $this->params->get('excludeAllScripts', '0')542 ) {543 $this->excludeJsPEO($script);544 }545 546 570 $this->maybeProcessScript($script); 547 }548 549 private function responseToPreviousExclude(): void550 {551 //If previous file was excluded PEO, update index552 if ($this->jsExcludedPeo) {553 $this->iIndex_js++;554 }555 571 } 556 572 … … 572 588 if (!$isDeferred) { 573 589 $this->updateIndex(); 574 $this->responseToPreviousExclude();575 $this->jsExcludedPeo = false;576 $this->jsExcludedIeo = false;577 590 $this->aJs[$this->iIndex_js][] = new FileInfo(clone $script); 578 591 $this->jsReplacements[$this->iIndex_js][] = $script; … … 580 593 $groupIndex = $this->iIndex_js; 581 594 } 595 596 //These properties must only be set after index is (maybe) updated 597 $this->jsExcludedPeo = false; 598 $this->jsExcludedIeo = false; 599 $this->cssSensitive = false; 582 600 583 601 $existingGroupIndexes = array_filter( … … 595 613 isIeo: false, 596 614 isDeferred: $isDeferred, 615 isSensitive: false, 597 616 node: !$this->params->get('combine_files', 0) ? clone $script : null 598 617 ); … … 603 622 { 604 623 $this->updateIndex(); 624 //These properties must be set only after index is updated 605 625 $this->cssExcludedPeo = false; 606 626 $this->cssExcludedIeo = false; 627 $this->cssSensitive = false; 628 607 629 $this->aCss[$this->iIndex_css][] = new FileInfo(clone $element); 608 630 $this->cssReplacements[$this->iIndex_css][] = $element; … … 623 645 isInline: false, 624 646 isMarker: $ordinal === 0, 647 isSensitive: false, 625 648 media: $media, 626 649 node: !$this->params->get('combine_files', 0) ? clone $element : null … … 628 651 } 629 652 } 653 654 private function hasSensitiveAttributes(HtmlElementInterface $element): bool 655 { 656 return $element->hasAttribute('integrity') 657 || $element->hasAttribute('crossorigin') 658 || $element->hasAttribute('referrerpolicy'); 659 } 660 661 private function isExternal(UriInterface $uri): bool 662 { 663 $resolvedUri = UriResolver::resolve(SystemUri::currentUri(), $uri); 664 665 $cdn = $this->getContainer()->get(Cdn::class); 666 $path = $this->getContainer()->get(PathsInterface::class); 667 668 return !UriComparator::existsLocally($resolvedUri, $cdn, $path); 669 } 670 671 private function isSensitiveExternal(HtmlElementInterface $element, UriInterface $uri): bool 672 { 673 return $this->hasSensitiveAttributes($element) && $this->isExternal($uri); 674 } 675 676 private function recordSensitiveJs(Script $script): void 677 { 678 $this->jsSensitive = true; 679 $this->replacement = ''; 680 681 $this->jsTimeLine[] = new JsItem( 682 ordinal: count($this->jsTimeLine), 683 groupIndex: null, // not part of any processed group 684 isProcessed: false, 685 isExcluded: false, 686 isGate: false, 687 isIeo: false, 688 isDeferred: Helper::isScriptDeferred($script), 689 isSensitive: true, 690 node: clone $script 691 ); 692 } 693 694 private function recordSensitiveCss(Link $link, ?string $media): void 695 { 696 $this->cssSensitive = true; 697 $this->replacement = ''; 698 $ordinal = count($this->cssTimeLine); 699 700 $this->cssTimeLine[] = new CssItem( 701 ordinal: $ordinal, 702 groupIndex: null, // not part of any processed group 703 isProcessed: false, 704 isExcluded: false, 705 isInline: false, 706 isMarker: $ordinal === 0, 707 isSensitive: true, 708 media: $media, 709 node: clone $link 710 ); 711 } 630 712 } -
jch-optimize/trunk/lib/src/Html/HtmlElementBuilder.php
r3310120 r3412704 82 82 { 83 83 $builder = new BuildHtmlElement(); 84 $builder->build($html); 84 85 85 $builder->build($html); 86 return $builder->getElement(); 87 } 88 89 /** 90 * @throws PregErrorException 91 */ 92 public static function loadFromMatch(array $matches): HtmlElementInterface 93 { 94 $builder = new BuildHtmlElement(); 95 $builder->buildFromMatch($matches); 86 96 87 97 return $builder->getElement(); -
jch-optimize/trunk/lib/src/Html/HtmlManager.php
r3402713 r3412704 107 107 { 108 108 return HtmlElementBuilder::style() 109 ->class('jchoptimize-critical-css')110 ->data('id', $id)111 ->addChild(PHP_EOL . $criticalCss . PHP_EOL)112 ->render();109 ->class('jchoptimize-critical-css') 110 ->data('id', $id) 111 ->addChild(PHP_EOL . $criticalCss . PHP_EOL) 112 ->render(); 113 113 } 114 114 … … 211 211 { 212 212 return ($this->params->get('gzip', 0) && extension_loaded('zlib') && !ini_get('zlib.output_compression') 213 && (ini_get('output_handler') != 'ob_gzhandler'));213 && (ini_get('output_handler') != 'ob_gzhandler')); 214 214 } 215 215 … … 263 263 if ($element instanceof Link) { 264 264 $attr = [ 265 'rel' => 'preload',266 'as' => 'style',265 'rel' => 'preload', 266 'as' => 'style', 267 267 'onload' => 'this.rel=\'stylesheet\'', 268 268 ]; … … 271 271 $attr = [ 272 272 'onload' => "this.media='{$media}'", 273 'media' => 'print'273 'media' => 'print' 274 274 ]; 275 275 } … … 290 290 { 291 291 return HtmlElementBuilder::style() 292 ->class('jchoptimize-dynamic-critical-css')293 ->data('id', $id)294 ->addChild(PHP_EOL . $css . PHP_EOL)295 ->render();292 ->class('jchoptimize-dynamic-critical-css') 293 ->data('id', $id) 294 ->addChild(PHP_EOL . $css . PHP_EOL) 295 ->render(); 296 296 } 297 297 298 298 /** 299 * @param CssPlacementPlan $plan 300 * @param (Link|Style)[] $combinedByGroup 301 * @param CacheObject[] $cacheObjectByGroup 302 * @param Link|null $belowFoldFontsEl 303 * @param Link|null $reducedBundleEl 299 * @param CssPlacementPlan $plan 300 * @param (Link|Style)[] $combinedByGroup 301 * @param CacheObject[] $cacheObjectByGroup 302 * @param CacheObject[] $sensitiveCacheObjByOrds 303 * @param Link|null $belowFoldFontsEl 304 * @param Link|null $reducedBundleEl 304 305 * 305 306 * @return void … … 309 310 array $combinedByGroup, 310 311 array $cacheObjectByGroup, 312 array $sensitiveCacheObjByOrds, 311 313 ?Link $belowFoldFontsEl, 312 314 ?Link $reducedBundleEl … … 372 374 373 375 foreach ($plan->headInlineCritical as $placement) { 376 if ($placement->isSensitive) { 377 $sCacheObj = $sensitiveCacheObjByOrds[$placement->item->ordinal] ?? null; 378 if ($sCacheObj !== null && ($sCss = $sCacheObj->getCriticalCss()) !== '') { 379 $insertion .= "\t" . $this->getCriticalCssHtml($sCss, $sCacheObj->getCriticalCssId()) . PHP_EOL; 380 } 381 continue; 382 } 383 374 384 $cacheObj = $cacheObjectByGroup[$placement->groupIndex] ?? null; 375 385 if ($cacheObj !== null && ($css = $cacheObj->getCriticalCss()) !== '') { … … 386 396 387 397 foreach ($plan->bodyInlineDynamicCritical as $placement) { 398 if ($placement->isSensitive) { 399 $sCacheObj = $sensitiveCacheObjByOrds[$placement->item->ordinal] ?? null; 400 if ($sCacheObj !== null && ($sCss = $sCacheObj->getDynamicCriticalCss()) !== '') { 401 $insertion .= "\t" . $this->getDynamicCriticalCssHtml( 402 $sCss, 403 $sCacheObj->getCriticalCssId() 404 ) . PHP_EOL; 405 } 406 continue; 407 } 408 388 409 $cacheObj = $cacheObjectByGroup[$placement->groupIndex] ?? null; 389 410 if ($cacheObj !== null && ($css = $cacheObj->getDynamicCriticalCss()) !== '') { 390 411 $insertion .= "\t" . $this->getDynamicCriticalCssHtml( 391 $css,392 $cacheObj->getCriticalCssId()393 ) . PHP_EOL;412 $css, 413 $cacheObj->getCriticalCssId() 414 ) . PHP_EOL; 394 415 } 395 416 } … … 408 429 409 430 foreach ($plan->bodyAsync as $placement) { 431 if ($placement->isSensitive) { 432 $el = $placement->item->node; 433 $this->preloadStyleSheet($el, 'low'); 434 $insertion .= "\t" . $el->render() . PHP_EOL; 435 436 continue; 437 } 438 410 439 $cssEl = $combinedByGroup[$placement->groupIndex] ?? null; 411 440 if ($cssEl) { … … 442 471 } 443 472 473 if (!empty($plan->bodySensitiveDynamic)) { 474 $insertion = ''; 475 476 foreach ($plan->bodySensitiveDynamic as $placement) { 477 $el = $placement->item->node->type('jchoptimize-text/css'); 478 $insertion .= "\t" . $el->render() . PHP_EOL; 479 } 480 481 $html = preg_replace( 482 '#' . Parser::htmlClosingBodyTagToken() . '#si', 483 $insertion . '\0', 484 $html, 485 1 486 ); 487 } 488 444 489 $this->processor->setFullHtml($html); 445 490 } … … 512 557 JCH_PRO 513 558 && $this->params->get('pro_reduce_unused_js_enable', '0') 514 && ( 515 ($section === 'body' && $placement->isProcessed && $placement->isDeferable) 516 || ($placement->isDeferred && !$placement->isExcluded) 517 ) 559 && $section === 'body' 560 && ($placement->isDeferable || $placement->isDeferred) 561 && !$placement->isExcluded 518 562 && $dynamicJs instanceof DynamicJs 519 563 ) { … … 522 566 $insertion .= "\t" . (string)$script . PHP_EOL; 523 567 } 568 } elseif ( 569 $this->params->get('loadAsynchronous', '0') 570 && $section === 'body' 571 && $placement->isDeferable && !$placement->isExcluded 572 ) { 573 if ($placement->isProcessed) { 574 $script = $combinedByGroup[$placement->groupIndex] ?? null; 575 } else { 576 $script = $placement->item->node; 577 } 578 579 if ($script) { 580 // Add defer/async if loadAsynchronous & bottom_js logic apply 581 $this->deferScript($script); 582 $insertion .= "\t" . $script->render() . PHP_EOL; 583 } 524 584 } elseif ($placement->isProcessed) { 525 585 $script = $combinedByGroup[$placement->groupIndex] ?? null; 526 586 if ($script) { 527 // Add defer/async if loadAsynchronous & bottom_js logic apply528 if (529 $section === 'body'530 && $this->params->get('loadAsynchronous', '0')531 && $placement->isDeferable532 ) {533 $this->deferScript($script);534 }535 587 $insertion .= "\t" . $script->render() . PHP_EOL; 536 588 } … … 577 629 { 578 630 return HtmlElementBuilder::link() 579 ->rel('modulepreload')580 ->href($url)581 ->fetchpriority('low')582 ->render();631 ->rel('modulepreload') 632 ->href($url) 633 ->fetchpriority('low') 634 ->render(); 583 635 } 584 636 … … 643 695 if ($css !== '') { 644 696 $style = HtmlElementBuilder::style() 645 ->id('jchoptimize-custom-css')646 ->addChild($css)647 ->render();697 ->id('jchoptimize-custom-css') 698 ->addChild($css) 699 ->render(); 648 700 649 701 $this->appendChildToHead($style); … … 666 718 * @template T of Link|Script 667 719 * 668 * @param T $elementThe HTML element to add the data-file attribute to.669 * @param FileInfo $fileInfoThe file information.720 * @param T $element The HTML element to add the data-file attribute to. 721 * @param FileInfo $fileInfo The file information. 670 722 * 671 723 * @return T Returns the same type as the $element input (Link or Script). -
jch-optimize/trunk/lib/src/Html/HtmlProcessor.php
r3402713 r3412704 32 32 use JchOptimize\Core\Html\Elements\Img; 33 33 use JchOptimize\Core\Html\Elements\Source; 34 use JchOptimize\Core\Optimize; 34 35 use JchOptimize\Core\Platform\ProfilerInterface; 35 36 use JchOptimize\Core\Registry; … … 316 317 !JCH_DEBUG ?: $this->profiler->start('LazyLoadImages'); 317 318 318 $html = $this->getBodyHtml(); 319 320 $aboveFoldBody = $this->getAboveFoldHtml($html); 321 $belowFoldHtml = substr($html, strlen($aboveFoldBody)); 322 $fullHtml = $this->getFullHtml(); 323 $aboveFoldHtml = substr($fullHtml, 0, strlen($fullHtml) - strlen($belowFoldHtml)) . $this->regexMarker; 319 $bodyHtml = $this->getBodyHtml(); 320 321 $aboveFoldBody = $this->getAboveFoldHtml($bodyHtml); 322 $aboveFoldBodyLen = strlen($aboveFoldBody); 323 $belowFoldBody = substr($bodyHtml, $aboveFoldBodyLen); 324 325 $headHtml = $this->getHeadHtml(); 324 326 325 327 try { … … 336 338 $http2Callback = $this->getContainer()->get(LazyLoad::class); 337 339 $http2Callback->setLazyLoadArgs($http2Args); 338 $processedAboveFoldHtml = $aboveFoldParser->processMatchesWithCallback( 339 $aboveFoldHtml, 340 $http2Callback 341 ); 340 $processedHeadHtml = $aboveFoldParser->processMatchesWithCallback($headHtml, $http2Callback); 341 $this->setHeadHtml($processedHeadHtml); 342 $processedAboveFoldBody = $aboveFoldParser->processMatchesWithCallback($aboveFoldBody, $http2Callback); 342 343 343 344 $belowFoldParser = new Parser(); … … 353 354 $lazyLoadCallback->setLazyLoadArgs($lazyLoadArgs); 354 355 $processedBelowFoldHtml = $belowFoldParser->processMatchesWithCallback( 355 $belowFold Html,356 $belowFoldBody, 356 357 $lazyLoadCallback 357 358 ); … … 366 367 } 367 368 368 $this->set FullHtml(369 $this->cleanRegexMarker($processedAboveFold Html) . $marker . $processedBelowFoldHtml369 $this->setBodyHtml( 370 $this->cleanRegexMarker($processedAboveFoldBody) . $marker . $processedBelowFoldHtml 370 371 ); 371 372 } catch (PregErrorException $oException) { … … 443 444 if ( 444 445 !$this->params->get('cookielessdomain_enable', '0') || 445 (trim($this->params->get('cookielessdomain', '')) == '' && 446 ( 447 trim($this->params->get('cookielessdomain', '')) == '' && 446 448 trim($this->params->get('pro_cookielessdomain_2', '')) == '' && 447 trim($this->params->get('pro_cookieless_3', '')) == '') 449 trim($this->params->get('pro_cookieless_3', '')) == '' 450 ) 448 451 ) { 449 452 return; … … 687 690 } catch (PregErrorException $e) { 688 691 $this->logger?->error('RemoveScriptsFromHtml failed ' . $e->getMessage()); 692 689 693 return $html; 690 694 } … … 693 697 public function processJavaScriptForConfigureHelper(): array 694 698 { 699 Optimize::setPcreLimits(); 695 700 $dynamicScripts = []; 696 701 -
jch-optimize/trunk/lib/src/Html/JsLayout/JsItem.php
r3402713 r3412704 26 26 public bool $isIeo, // ignore execution order (IEO) 27 27 public bool $isDeferred, // deferred/module etc 28 public bool $isSensitive, 28 29 public ?Script $node // HTML element for this script 29 30 ) { -
jch-optimize/trunk/lib/src/Html/JsLayout/JsLayoutPlanner.php
r3402713 r3412704 57 57 $placement = new JsPlacementItem( 58 58 isProcessed: $item->isProcessed, 59 isDeferable: $item->isProcessed&& $item->ordinal > $lastPEOExcludeOrdinal,59 isDeferable: ($item->isProcessed || $item->isSensitive) && $item->ordinal > $lastPEOExcludeOrdinal, 60 60 isDeferred: $item->isDeferred, 61 61 isExcluded: $item->isExcluded, 62 isSensitive: $item->isSensitive, 62 63 groupIndex: $item->groupIndex, 63 64 item: $item -
jch-optimize/trunk/lib/src/Html/JsLayout/JsPlacementItem.php
r3402713 r3412704 21 21 public bool $isDeferred, 22 22 public bool $isExcluded, 23 public bool $isSensitive, 23 24 public ?int $groupIndex, //For processed items 24 25 public JsItem $item -
jch-optimize/trunk/lib/src/Html/Parser.php
r3310120 r3412704 27 27 use function preg_replace; 28 28 use function preg_replace_callback; 29 use function str_contains;30 29 31 30 defined('_JCH_EXEC') or die('Restricted access'); … … 50 49 public static function htmlBodyElementToken(): string 51 50 { 52 $htmlHead = self::htmlHeadElementToken(); 53 54 return "{$htmlHead}\K.*+$"; 51 $bodyStartTag = self::htmlStartTagToken('body'); 52 $htmlString = self::htmlStringToken(['script', 'style', 'template']); 53 $bodyEndTag = self::htmlEndTagToken('body'); 54 55 return "{$bodyStartTag}{$htmlString}?{$bodyEndTag}"; 55 56 } 56 57 … … 108 109 109 110 $sProcessedHtml = (string)preg_replace_callback( 110 '#' . $regex . '#si ',111 '#' . $regex . '#siJ', 111 112 [$callbackObject, 'processMatches'], 112 113 $html … … 209 210 { 210 211 $regex = $this->getHtmlSearchRegex(); 211 preg_match_all('#' . $regex . '#si ', $sHtml, $aMatches, $flags);212 preg_match_all('#' . $regex . '#siJ', $sHtml, $aMatches, $flags); 212 213 213 214 self::throwExceptionOnPregError(); … … 233 234 { 234 235 $regex = $this->getHtmlSearchRegex(); 235 $result = preg_replace("#{$regex}#si ", "", $html);236 $result = preg_replace("#{$regex}#siJ", "", $html); 236 237 237 238 self::throwExceptionOnPregError(); … … 246 247 247 248 foreach ($attrCriteria as $criterion) { 248 if (str_contains($criterion, '!=')) { 249 list($name, $value) = explode('!=', $criterion); 250 $criterionRegexArray[] = "{$name}\s*+=\s*+(?!\"{$value}\"|'{$value}'|(?<=[=])(?!['\"]){$value}[\s/> ])"; 251 } elseif (str_contains($criterion, '==')) { 252 list($name, $value) = explode('==', $criterion); 253 254 $criterionRegexArray[] = "$name\s*+=\s*+(?:\"{$value}\"|'{$value}'|(?<=[=])(?!['\"]){$value}[\s/> ])"; 255 } elseif (str_contains($criterion, '~=')) { 256 list($name, $value) = explode('~=', $criterion); 257 258 $criterionRegexArray[] = "{$name}\s*+=\s*+(?:\"[^\"]*?(?<=[\" ]){$value}[\" ]|" 259 . "'[^']*?(?<=[' ]){$value}[ ']|(?<==)(?!['\"]){$value}[ />])"; 260 } elseif (str_contains($criterion, '*=')) { 261 list($name, $value) = explode('*=', $criterion); 262 263 $criterionRegexArray[] = "{$name}\s*+=\s*+(?:\"[^\"]*?{$value}|'[^']*?{$value}|(?<==)(?!['\"])[^\s]*?{$value})"; 264 } else { 265 $criterionRegexArray[] = $criterion; 266 } 249 $criterionRegexArray[] = $this->buildCriterionRegex($criterion); 267 250 } 268 251 … … 270 253 271 254 return "(?:$criterionRegexString)"; 255 } 256 257 private function buildCriterionRegex(string $criterion): string 258 { 259 // Find the operator once 260 if (!preg_match('#(==|!=|~=|\*=)#', $criterion, $m)) { 261 // No operator we care about → return as-is 262 return $criterion; 263 } 264 265 $op = $m[1]; 266 267 // Split on the operator (only once) 268 [$name, $value] = explode($op, $criterion, 2); 269 270 return match ($op) { 271 '!=' => $this->buildNotEqualsRegex($name, $value), 272 '==' => $this->buildEqualsRegex($name, $value), 273 '~=' => $this->buildContainsWordRegex($name, $value), 274 '*=' => $this->buildContainsSubstringRegex($name, $value), 275 }; 276 } 277 278 private function buildNotEqualsRegex(string $name, string $value): string 279 { 280 return "{$name}\s*+=\s*+(?!\"{$value}\"|'{$value}'|(?<=[=])(?!['\"]){$value}[\s/> ])"; 281 } 282 283 private function buildEqualsRegex(string $name, string $value): string 284 { 285 return "{$name}\s*+=\s*+(?:\"{$value}\"|'{$value}'|(?<=[=])(?!['\"]){$value}[\s/> ])"; 286 } 287 288 private function buildContainsWordRegex(string $name, string $value): string 289 { 290 return "{$name}\s*+=\s*+(?:\"[^\"]*?(?<=[\" ]){$value}[\" ]" 291 . "|" 292 . "'[^']*?(?<=[' ]){$value}[ ']" 293 . "|" 294 . "(?<==)(?!['\"]){$value}[ />])"; 295 } 296 297 private function buildContainsSubstringRegex(string $name, string $value): string 298 { 299 return "{$name}\s*+=\s*+(?:\"[^\"]*?{$value}|'[^']*?{$value}|(?<==)(?!['\"])[^\s]*?{$value})"; 272 300 } 273 301 … … 299 327 foreach ($names as $name) { 300 328 $matches[] = self::htmlNestedElementToken($name); 301 302 return implode('|', $matches); 303 } 304 } 305 306 $namesRegex = '(?:' . implode('|', $names) . ')'; 307 308 return $elementObject->voidElementOrStartTagOnly ? 309 '(?:' . self::htmlVoidElementToken($namesRegex) . '|' . self::htmlStartTagToken($namesRegex) . ')' : 310 self::htmlElementsToken($names); 329 } 330 331 return implode('|', $matches); 332 } 333 334 return self::htmlElementMatchForBuilder( 335 $names, 336 $elementObject->voidElementOrStartTagOnly ?? false 337 ); 338 } 339 340 // in Parser 341 private static function htmlElementMatchForBuilder(array $names, bool $voidOrStartTagOnly): string 342 { 343 $nameRegex = '(?:' . implode('|', $names) . ')'; 344 $attrsToken = self::htmlAttributesListToken(); 345 $endTagRegex = self::htmlEndTagToken($nameRegex); 346 $contentRegex = self::htmlTextContentToken($nameRegex); 347 348 if ($voidOrStartTagOnly) { 349 // e.g. <link ...>, <meta ...>, <img ...> 350 return "<(?<name>{$nameRegex})\b(?:\s++(?<attributes>{$attrsToken}+))?/?>"; 351 } 352 353 // Non-void element: capture inner content 354 $startTag = "<(?<name>{$nameRegex})\b(?:\s++(?<attributes>{$attrsToken}+))?>"; 355 $content = "(?<content>{$contentRegex})"; 356 $endTag = "(?<endTag>{$endTagRegex})"; 357 358 return $startTag . $content . $endTag; 311 359 } 312 360 -
jch-optimize/trunk/lib/src/Optimize.php
r3310120 r3412704 31 31 use function ini_get; 32 32 use function ini_set; 33 use function preg_replace;34 33 use function version_compare; 35 34 … … 45 44 use ContainerAwareTrait; 46 45 47 private string $jit = '1';46 private string $jit; 48 47 49 48 /** … … 59 58 private UtilityInterface $utility 60 59 ) { 60 $this->jit = ini_get('pcre.jit'); 61 62 self::setPcreLimits(); 63 64 if (version_compare(PHP_VERSION, '8.0', '<')) { 65 throw new Exception\RuntimeException('PHP Version less than 8.0, Exiting plugin...'); 66 } 67 } 68 69 public static function setPcreLimits(): void 70 { 61 71 ini_set('pcre.backtrack_limit', '1000000'); 62 72 ini_set('pcre.recursion_limit', '1000000'); 63 64 if (version_compare(PHP_VERSION, '7.0.0', '>=')) { 65 $this->jit = ini_get('pcre.jit'); 66 ini_set('pcre.jit', '0'); 67 } 68 69 if (version_compare(PHP_VERSION, '7.3', '<')) { 70 throw new Exception\RuntimeException('PHP Version less than 7.3, Exiting plugin...'); 71 } 72 73 $pcre_version = preg_replace('#(^\d++\.\d++).++$#', '$1', PCRE_VERSION); 74 75 if (version_compare($pcre_version, '7.2', '<')) { 76 throw new Exception\RuntimeException('PCRE Version less than 7.2. Exiting plugin...'); 77 } 73 ini_set('pcre.jit', '0'); 78 74 } 79 75 … … 107 103 } 108 104 109 if (version_compare(PHP_VERSION, '7.0.0', '>=')) { 110 ini_set('pcre.jit', (string)$this->jit); 111 } 105 ini_set('pcre.jit', (string)$this->jit); 112 106 113 107 return $optimizedHtml; … … 118 112 * Inline CSS and JS will also be minified if respective parameters are set 119 113 * 120 * @param string$html114 * @param string $html 121 115 * 122 116 * @return string Optimized HTML -
jch-optimize/trunk/lib/src/PageCache/PageCache.php
r3402713 r3412704 399 399 400 400 if ($this->input->server->get('REQUEST_METHOD') == 'POST') { 401 if ($this->params->get('page_cache_exclude_form_users', '1') ) {401 if ($this->params->get('page_cache_exclude_form_users', '1') && !empty($_POST)) { 402 402 $this->hooks->onUserPostForm(); 403 403 -
jch-optimize/trunk/lib/src/Preloads/Http2Preload.php
r3362084 r3412704 314 314 case 'font': 315 315 if ($this->fontPreloads->count() < 2) { 316 $this->fontPreloads-> attach($preload);316 $this->fontPreloads->offsetSet($preload); 317 317 } 318 318 break; 319 319 case 'image': 320 320 if ($this->imagePreloads->count() < 4 || $preload->getFetchPriority() == 'high') { 321 $this->imagePreloads-> attach($preload);321 $this->imagePreloads->offsetSet($preload); 322 322 } 323 323 break; 324 324 case 'style': 325 $this->stylePreloads-> attach($preload);325 $this->stylePreloads->offsetSet($preload); 326 326 break; 327 327 case 'script': 328 $this->scriptPreloads-> attach($preload);328 $this->scriptPreloads->offsetSet($preload); 329 329 break; 330 330 default: 331 $this->otherPreloads-> attach($preload);331 $this->otherPreloads->offsetSet($preload); 332 332 break; 333 333 } -
jch-optimize/trunk/lib/src/Preloads/Preconnector.php
r3362084 r3412704 75 75 //If google fonts were optimized then add the fonts domain to preconnects if necessary 76 76 if ($this->googleFontsOptimized) { 77 $this->preconnects-> attach(77 $this->preconnects->offsetSet( 78 78 new Preconnect(Utils::uriFor('https://fonts.gstatic.com'), 'anonymous') 79 79 ); … … 137 137 } 138 138 139 $this->preconnects-> attach(new Preconnect($uri, $crossorigin));139 $this->preconnects->offsetSet(new Preconnect($uri, $crossorigin)); 140 140 } 141 141 } … … 148 148 149 149 if (Uri::isAbsolute($uri) || Uri::isNetworkPathReference($uri)) { 150 $this->prefetches-> attach(new DnsPrefetch($uri));150 $this->prefetches->offsetSet(new DnsPrefetch($uri)); 151 151 } 152 152 } … … 182 182 $linkObj = HtmlElementBuilder::load($preconnect); 183 183 184 $this->preconnects-> attach(184 $this->preconnects->offsetSet( 185 185 new Preconnect( 186 186 $linkObj->getHref(), -
jch-optimize/trunk/lib/src/Preloads/PreloadsCollection.php
r3362084 r3412704 33 33 * @psalm-suppress ParamNameMismatch 34 34 */ 35 public function attach(object$object, mixed $info = null): void35 public function offsetSet(mixed $object, mixed $info = null): void 36 36 { 37 37 $this->rewind(); … … 53 53 ) { 54 54 if ($newExt == 'woff2') { 55 $this-> detach($preload);55 $this->offsetUnset($preload); 56 56 break; 57 57 } … … 65 65 66 66 if ($newExt == 'woff' && $existingExt == 'ttf') { 67 $this-> detach($preload);67 $this->offsetUnset($preload); 68 68 break; 69 69 } … … 79 79 } 80 80 81 parent::attach($object, $info); 81 parent::offsetSet($object, $info); 82 } 83 84 /** 85 * @deprecated 86 */ 87 public function attach(object $object, mixed $info = null): void 88 { 89 $this->offsetSet($object, $info); 90 } 91 92 public function current(): Preload 93 { 94 return parent::current(); 82 95 } 83 96 } -
jch-optimize/trunk/readme.txt
r3402713 r3412704 3 3 Contributors: codealfa 4 4 Tags: performance, pagespeed, cache, optimize, seo 5 Tested up to: 6. 8.36 Stable tag: 5.1. 05 Tested up to: 6.9 6 Stable tag: 5.1.1 7 7 License: GPLv3 or later 8 Requires at least: 5.08 Requires at least: 6.5.0 9 9 Requires PHP: 8.0 10 10 License URI: https://www.gnu.org/licenses/gpl-3.0.html … … 80 80 81 81 == Changelog == 82 83 = 5.1.1 = 84 * Add icons to Admin menu 85 * Remove unused lazy load settings 86 * Update WordPress required version to 6.5.0 87 * Added additional LCP settings 88 * [HIGH] Fix error on Optimize Image tab 89 * [MEDIUM] Fix bug with handling invalid image URLs 90 * Improve Optimize CSS Delivery 91 * [MEDIUM] Files with integrity attribute were broken 92 * [LOW] Fix PHP 8.5 deprecations 93 82 94 = 5.1.0 = 83 95 * Added support for AVIF images -
jch-optimize/trunk/src/Html/Renderer/Setting.php
r3402713 r3412704 177 177 $options = [ 178 178 '0' => __('Disabled', 'jch-optimize'), 179 '1' => __('On save only', 'jch-optimize'), 179 180 'hourly' => __('Hourly', 'jch-optimize'), 180 181 'twicedaily' => __('Twice Daily', 'jch-optimize'), … … 182 183 ]; 183 184 184 Helper::_('select.pro', __FUNCTION__, 0, $options);185 Helper::_('select.pro', __FUNCTION__, '1', $options); 185 186 } 186 187 … … 580 581 { 581 582 Helper::_('switch', __FUNCTION__, '0'); 582 }583 584 public static function lazyload_autosize(): void585 {586 Helper::_('switch', __FUNCTION__, '1');587 }588 589 public static function pro_lazyload_effects(): void590 {591 Helper::_('switch.pro', __FUNCTION__, '0');592 583 } 593 584 … … 699 690 700 691 public static function pro_lcp_images(): void 692 { 693 Helper::_('multiselect.pro', __FUNCTION__, [], 'lazyload', 'file'); 694 } 695 696 public static function pro_lcp_identifiers(): void 697 { 698 Helper::_('multiselect.pro', __FUNCTION__, [], 'lazyload', 'file'); 699 } 700 701 public static function lcp_images_mobile(): void 702 { 703 Helper::_('multiselect.pro', __FUNCTION__, [], 'lazyload', 'file'); 704 } 705 706 public static function lcp_images_desktop(): void 701 707 { 702 708 Helper::_('multiselect.pro', __FUNCTION__, [], 'lazyload', 'file'); -
jch-optimize/trunk/src/Html/TabSettings.php
r3402713 r3412704 678 678 __('Enable', 'jch-optimize'), 679 679 ], 680 'lazyload_autosize' => [681 __('Autosize images', 'jch-optimize'),682 __(683 'This setting attempts to maintain aspect ratio of images being lazy-loaded to prevent blank spaces under the images after they\'re loaded or distortions in rendering.'684 )685 ],686 'pro_lazyload_effects' => [687 __('Enable effects', 'jch-optimize'),688 __('Enable to use fade-in effects when images are scrolled into view.', 'jch-optimize')689 ],690 680 'pro_lazyload_iframe' => [ 691 681 __('Lazy load iframes', 'jch-optimize'), … … 804 794 __('LCP images', 'jch-optimize'), 805 795 __( 806 'Add your LCP images here to have them preloaded with a high priority on whichever page they appear.' 807 ) 796 'Add your LCP images here to have them preloaded with a high priority on whichever page they appear.', 797 'jch-optimize' 798 ) 799 ], 800 'pro_lcp_identifiers' => [ 801 __('LCP id/class', 'jch-optimize'), 802 __( 803 'Add the id or class of your LCP images here. Ensure the class is only shared by other LCP images, you only need one per page.', 804 'jch-optimize' 805 ) 806 ], 807 'lcp_images_mobile' => [ 808 __('Mobile only', 'jch-optimize'), 809 __('Add images here that will only be preloaded on mobile.', 'jch-optimize') 810 ], 811 'lcp_images_desktop' => [ 812 __('Desktop only', 'jch-optimize'), 813 __('Add images here that will only be preloaded on desktop.', 'jch-optimize') 808 814 ] 809 815 ], -
jch-optimize/trunk/src/Plugin/Installer.php
r3402713 r3412704 103 103 /** 104 104 * Plugin Name: JCH Optimize Mode Switcher 105 * Plugin URI: http ://www.jch-optimize.net/105 * Plugin URI: https://www.jch-optimize.net/ 106 106 * Description: Boost your WordPress site's performance with JCH Optimize as measured on PageSpeed 107 107 * Version: {VERSION} … … 266 266 267 267 if ( 268 file_exists($installedMU) && md5_file( 269 JCH_PLUGIN_DIR . 'mu-plugins/jch-optimize-mode-switcher.php' 270 ) !== md5_file($installedMU) 268 file_exists($installedMU) 269 && md5_file( 270 JCH_PLUGIN_DIR . 'mu-plugins/jch-optimize-mode-switcher.php' 271 ) !== md5_file($installedMU) 271 272 ) { 272 273 $this->installMUPlugin(); -
jch-optimize/trunk/src/Plugin/Loader.php
r3402713 r3412704 164 164 165 165 $reCacheModel = $this->getContainer()->get(ReCache::class); 166 /** @see ReCache::reCache() */ 167 add_action(ReCache::RECACHE_CRON_HOOK, [$reCacheModel, 'reCache']); 168 /** @see ReCache::reSchedule() */ 169 add_action('update_option_jch-optimize_settings', [$reCacheModel, 'reSchedule']); 166 if ($this->params->get('recache_frequency') != '0') { 167 /** @see ReCache::reCache() */ 168 add_action(ReCache::RECACHE_CRON_HOOK, [$reCacheModel, 'reCache']); 169 /** @see ReCache::reSchedule() */ 170 add_action('update_option_jch-optimize_settings', [$reCacheModel, 'reSchedule']); 171 } 170 172 } 171 173 } -
jch-optimize/trunk/src/View/OptimizeImageHtml.php
r3402713 r3412704 55 55 wp_add_inline_script('jch-platform-wordpress', $js, 'before'); 56 56 57 wp_register_script('jch-uuid', JCH_PLUGIN_URL . 'media/uuid/uuidv4.js');58 wp_enqueue_script('jch-uuid');59 60 57 if (JCH_PRO) { 61 wp_register_script_module('jch-optimize-image', JCH_PLUGIN_URL . 'media/core/js/optimize-image.js', [ 62 'jquery', 63 'jch-admin-utility', 64 'jch-platform-wordpress', 65 'jch-bootstrap', 66 'jch-uuid' 67 ], JCH_VERSION, true); 68 58 wp_register_script_module( 59 'jch-optimize-image', 60 JCH_PLUGIN_URL . 'media/core/js/optimize-image.js', 61 [], 62 JCH_VERSION 63 ); 69 64 wp_enqueue_script_module('jch-optimize-image'); 70 65 } 71 66 72 67 wp_enqueue_style('jch-file-tree'); 73 74 68 wp_enqueue_script('jch-file-tree'); 75 69 76 70 $params = json_encode([ 77 'auth'=> [78 'dlid' => $this->params->get('pro_downloadid', ''),79 'secret' => '11e603aa',80 ],81 'resize_mode'=> $this->params->get('pro_api_resize_mode', '1') ? 'auto' : 'manual',82 'webp'=> (bool)$this->params->get('pro_next_gen_images', '1'),83 'avif'=> (bool)$this->params->get('gen_avif_images', '1'),84 'lossy'=> (bool)$this->params->get('lossy', '1'),85 'save_metadata' => (bool)$this->params->get('save_metadata', '0'),86 'quality'=> $this->params->get('quality', '85'),87 'cropgravity'=> $this->params->get('cropgravity', []),88 'responsive'=> (bool)$this->params->get('pro_gen_responsive_images', '1')89 ]);71 'auth' => [ 72 'dlid' => $this->params->get('pro_downloadid', ''), 73 'secret' => '11e603aa', 74 ], 75 'resize_mode' => $this->params->get('pro_api_resize_mode', '1') ? 'auto' : 'manual', 76 'webp' => (bool)$this->params->get('pro_next_gen_images', '1'), 77 'avif' => (bool)$this->params->get('gen_avif_images', '1'), 78 'lossy' => (bool)$this->params->get('lossy', '1'), 79 'save_metadata' => (bool)$this->params->get('save_metadata', '0'), 80 'quality' => $this->params->get('quality', '85'), 81 'cropgravity' => $this->params->get('cropgravity', []), 82 'responsive' => (bool)$this->params->get('pro_gen_responsive_images', '1') 83 ]); 90 84 91 85 … … 93 87 $noProID = __('Please enter your Download ID on the Configurations tab.', 'jch-optimize'); 94 88 95 $script = <<<JS89 $script = <<<JS 96 90 window.jchOptimizeImageData ={ 97 91 message : '{$message}', … … 101 95 JS; 102 96 wp_add_inline_script('jch-platform-wordpress', $script, 'before'); 103 104 97 } 105 98 } -
jch-optimize/trunk/tmpl/pagecache_filters.php
r3402713 r3412704 26 26 </button> 27 27 <button id="recache-button" type="submit" name="action" value="recache" class="btn btn-outline-dark ms-1" 28 <?php if (!JCH_PRO) : ?>28 <?php if (!JCH_PRO) : ?> 29 29 disabled 30 30 <?php endif; ?> 31 31 > 32 32 <span class="fa fa-refresh"></span> Recache 33 <?php if (!JCH_PRO): ?> 34 <span style="font-size: 0.6em; display: inline; padding-left: 5px;"><span class="fa fa-lock"></span> Pro</span> 33 <?php if (!JCH_PRO) : ?> 34 <span style="font-size: 0.6em; display: inline; padding-left: 5px;"> 35 <span class="fa fa-lock"></span> 36 Pro 37 </span> 35 38 <?php endif; ?> 36 39 </button> … … 48 51 <i>Storage: <span class="badge bg-primary"><?= $adapter ?></span> </i> 49 52 <i class="ms-2">Http Request: 50 <?php if ($httpRequest == 'yes') : ?>53 <?php if ($httpRequest == 'yes') : ?> 51 54 <span class="badge bg-success">On</span> 52 <?php else : ?>55 <?php else : ?> 53 56 <span class="badge bg-danger">Off</span> 54 57 <?php endif; ?> … … 77 80 <?= $filterAdapterSelectHtml ?> 78 81 <?php if (JCH_PRO) : ?> 79 <?= $filterHttpRequestSelectHtml ?>82 <?= $filterHttpRequestSelectHtml ?> 80 83 <?php endif; ?> 81 84 <button id="clear-search" type="submit" class="btn btn-secondary ms-2">Clear Filters</button> 82 85 <script> 83 86 document.getElementById('clear-search').addEventListener('click', function (event) { 84 document.getElementById('filter_search').value = '' ;85 document.getElementById('filter_time-1').value = '' ;86 document.getElementById('filter_time-2').value = '' ;87 document.getElementById('filter_device').value = '' ;88 document.getElementById('filter_adapter').value = '' ;89 document.getElementById('filter_http-request').value = '' ;90 document.getElementById('list_limit').value = '' ;91 document.getElementById('list_fullordering').value = '' ;87 document.getElementById('filter_search').value = '' 88 document.getElementById('filter_time-1').value = '' 89 document.getElementById('filter_time-2').value = '' 90 document.getElementById('filter_device').value = '' 91 document.getElementById('filter_adapter').value = '' 92 document.getElementById('filter_http-request').value = '' 93 document.getElementById('list_limit').value = '' 94 document.getElementById('list_fullordering').value = '' 92 95 }) 93 96 </script> -
jch-optimize/trunk/vendor/composer/installed.php
r3402713 r3412704 4 4 'pretty_version' => 'dev-master', 5 5 'version' => 'dev-master', 6 'reference' => ' 4541b13d2299de32ec35f80a1b6ae3a4f2ed960a',6 'reference' => '781c9a7e436505de7b07b7f0532d685389ad31db', 7 7 'type' => 'library', 8 8 'install_path' => __DIR__ . '/../../', … … 14 14 'pretty_version' => 'dev-master', 15 15 'version' => 'dev-master', 16 'reference' => ' 4541b13d2299de32ec35f80a1b6ae3a4f2ed960a',16 'reference' => '781c9a7e436505de7b07b7f0532d685389ad31db', 17 17 'type' => 'library', 18 18 'install_path' => __DIR__ . '/../../', -
jch-optimize/trunk/version.php
r3402713 r3412704 15 15 defined('_JCH_EXEC') or die; 16 16 17 const JCH_VERSION = '5.1. 0';18 const JCH_DATE = '2025-1 1-25';17 const JCH_VERSION = '5.1.1'; 18 const JCH_DATE = '2025-12-06'; 19 19 const JCH_PRO = '0'; 20 20 const JCH_DEVELOP = '0';
Note: See TracChangeset
for help on using the changeset viewer.